1. 什么是事务?
数据库中的事务是指对数据库执行一批操作,而这些操作最终要么全部执行成功,要么全部失败,不会存在部分成功的情况。这个时候就需要用到事务。
最经典的例子就是转账,你要给朋友小白转 1000 块钱,而此时你的银行卡只有 1000 块钱。
转账过程具体到程序里会有一系列的操作,比如查询余额、做加减法、更新余额等,这些操作必须保证是一体的,不然等程序查完之后,还没做减法之前,你这 1000 块钱,完全可以借着这个时间差再查一次,然后再给另外一个朋友转账,如果银行这么整,不就乱了么?这时就要用到“事务”这个概念了。
简单来说,事务就是要保证一组数据库操作,要么全部成功,要么全部失败。
2.事务的几个特性(ACID)
ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性)。
原子性(Atomicity)
事务的整个过程如原子操作一样,最终要么全部成功,或者全部失败,这个原子性是从最终结果来看的,从最终结果来看这个过程是不可分割的。
一致性(Consistency)
一个事务必须使数据库从一个一致性状态变换到另一个一致性状态。
所谓一致性,指的是数据处于一种有意义的状态,这种状态是语义上的而不是语法上的。最常见的例子是转帐。例如从帐户A转一笔钱到帐户B上,如果帐户A上的钱减少了,而帐户B上的钱却没有增加,那么我们认为此时数据处于不一致的状态。
隔离性(Isolation)
一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(Durability)
一个事务一旦提交,他对数据库中数据的改变就应该是永久性的。当事务提交之后,数据会持久化到硬盘,修改是永久性的。
3.如何开启事务
事务分为隐式事务和显式事务。
隐式事务
mysql中事务默认是隐式事务,执行insert、update、delete操作的时候,数据库自动开启事务、提交或回滚事务。
是否开启隐式事务是由变量autocommit控制的。
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.01 sec)
--autocommit为ON表示开启了自动提交
显式事务
- 显式启动事务语句, begin 或 start transaction。配套的提交语句是 commit,回滚语句是 rollback。
- set autocommit=0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个 select 语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在直到你主动执行 commit 或 rollback 语句,或者断开连接。
建议你总是使用 set autocommit=1, 通过显式语句的方式来启动事务。
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update test set name='sss' where id=7;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
4.隔离级别
当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题。
(1)脏读:一个事务读到另外一个事务没有提交的数据
解释:例如两个并发的事务a和b,其中事务A查完数据库中的一条记录后,事务a继续修改了一条记录,此时事务A并未提交,此时并发事务b读取了事务a修改的记录,这就导致事务b读取到事务a未提交的数据
(2)不可重复读:一个事务先后读取同一条记录,但是两次读取的数据不同
解释:例如两个并发事务a和b,其中事务a读取数据库中的一条记录后,事务b对数据库的这条记录进行修改后,提交事务b,此时事务a继续读取这条记录,发现和上次读取的数据不一样
(3)幻读:一个事务按照条件查询数据,没有对应的数据行,准备插入数据时,发现这行数据存在
解释:例如两个并发事务a和事务b,其中事务a读取了id=1的数据时,发现没有这条记录,然后事务b插入id=1的数据,并且提交了事务b,此时事务a准备插入id=1的数据时发现已经存在这条数据,因为插入数据时报错,显示有这条记录
不可重复读,关注是其他事务修改数据并提交了事务,前后两次读取到的数据不一致的问题;幻读是新插入的行或删除行导致出现的问题。
解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。
为了解决这些问题,就有了“隔离级别”的概念。
mysql中一共有4中隔离级别,其中可重复读是默认级别。
- 读未提交是指,一个事务还没提交时,它做的变更就能被别的事务看到。
- 读提交是指,一个事务提交之后,它做的变更才会被其他事务看到。
- 可重复读是指,一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
- 串行化,顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
上面4中隔离级别越来越强,会导致数据库的并发性也越来越低。
--查看隔离级别(两条任一条都行,这是查询当前会话的)
show variables like 'transaction_isolation';
select @@transaction_isolation;
--查看全局隔离级别
SELECT @@GLOBAL.TRANSACTION_ISOLATION;
--设置隔离级别
SET [ SESSION | GLOBAL ] TRANSACTION ISOLATION LEVEL { READ UNCOMMITTED |
READ COMMITTED | REPEATABLE READ | SERIALIZABLE }
--MySQL的session和global一般使用在终端,用来对配置进行暂时设置,当数据库服务重启就会失效。session和global体现在新的设置生效的范围。
--session:当前会话,也就是当前连接立即生效。
--global:全局,不包含当前连接,之后新获取的连接都会生效。
5.隔离级别的演示
每个隔离级别可能出现的问题如下
读未提交级别
出现脏读
读已提交级别
解决脏读,出现不可重复读问题。
步骤5更新数据后还未提交,到步骤6查看,数据没有改变,说明读已提交解决了脏读问题。
而步骤6和步骤8查看的数据是不一致的,这个就出现了不可重复读问题。(在同一个事务先后读取同一条记录,但是两次读取的数据不同)。
可重复读级别
解决不可重复读问题,出现幻读。
解决了不可重复读问题,步骤3,6,8读到的数据都是一致的。在步骤9提交后,步骤10读到的数据就是更新后的数据。
出现幻读。
步骤3查看是没有id=7的行,步骤4中插入id=7的行(步骤4是隐性提交),步骤5中再次查看还是没有。之后步骤7插入数据,出现错误。这就是出现了幻读。幻读是针对新插入的行的。
步骤8提交后,步骤9中查看可以看到另一事务插入的id=7的数据了。
可串行化级别
解决所有问题,但也是性能最差的一个。
步骤5中会一直被卡住,等到超时。