探索数据库的事务隔离级别
事务的基本要素
先简单复习一下事务的四个基本要素:ACID
-
原子性:整个事务中的操作,要么全部完成, 要么全部不完成(全部撤销)。
-
一致性:事务开始之前和结束之后,数据库的完整性没有遭到破坏。
-
隔离性:在同一时间,只允许一个事务请求同一数据。
-
持久性:事务完成以后,该事务对数据库所做的操作持久化在数据库中,并不会被回滚。
事务中经常出现的并发问题
分析几个场景:
1、脏读:一个事务读取了另一个事务操作但未提交的数据。
比如A、B两个事务,都操作同一张表,A刚刚对数据进行了操作(插入、修改等)但还没有提交,这时B读取到了A刚刚操作的数据,因为A有可能回滚,所以这部分数据有可能只是临时的、无效的,即脏数据。
2、不可重复读:一个事务中的多个相同的查询返回了不同数据。
比如A、B两个事务,A中先后有两次查询相同数据的操作,第一次查询完之后,B对相关数据进行了修改,造成A事务第二次查询出的数据与第一次不一致。
3、幻读:事务并发执行时,其中一个事务对另一个事务中操作的结果集的影响。
比如A、B两个事务,事务A操作表中符合条件的若干行。事务B插入符合A操作条件的数据行,然后再提交。后来发现事务A并没有如愿对“所有”符合条件的数据行做了修改~~
SQL规范定义的四个事务隔离级别
以上都是事务中经常发生的问题,所以为了兼顾并发效率和异常控制,SQL规范定义了四个事务隔离级别:
1、Read uncommitted (读未提交):如果设置了该隔离级别,则当前事务可以读取到其他事务已经修改但还没有提交的数据。这种隔离级别是最低的,会导致上面所说的脏读
2、Read committed (读已提交):如果设置了该隔离级别,当前事务只可以读取到其他事务已经提交后的数据,这种隔离级别可以防止脏读,但是会导致不可重复读和幻读。这种隔离级别最效率较高,并且不可重复读和幻读在一般情况下是可以接受的,所以这种隔离级别最为常用。
3、Repeatable read (可重复读):如果设置了该隔离级别,可以保证当前事务中多次读取特定记录的结果相同。可以防止脏读、不可重复读,但是会导致幻读。
4、Serializable (串行化):如果设置了该隔离级别,所有的事务会放在一个队列中执行,当前事务开启后,其他事务将不能执行,即同一个时间点只能有一个事务操作数据库对象。这种隔离级别对于保证数据完整性的能力是最高的,但因为同一时刻只允许一个事务操作数据库,所以大大降低了系统的并发能力。
引用一张很经典的表格来按隔离级别由弱到强来标示为:
并且隔离级别越高,并发性能越弱:
前面的文章提到过,事务是由数据库连接所控制,并且只提供了两种事务提交模式:自动提交和手动提交,默认是自动提交(即把每个SQL语句的执行都当做是一个事务,每次执行完SQL语句都会立即将操作更新到数据库)。
设置事务隔离级别的方式有很多种,前文只简单提到了理论知识,这里数据库以MySQL为例,来动手设置一下事务的隔离级别并观察一下事务的隔离级别到底起到什么作用。
我们都知道,每启动一下MySQL,就会获得一个数据库连接,每个数据库连接有一个全局变量@@tx_isolation,表示当前连接中事务的隔离级别。
查看事务隔离级别
命令行登录mysql,查看当前事务隔离级别:
select @@tx_isolation;
- 1
或者
select @@session.tx_isolation;
- 1
可以看到mysql默认的事务隔离级别为REPEATABLE-READ
查看系统全局事务离级别可以用:
select @@global.tx_isolation;
- 1
设置事务隔离级别
设置当前事务隔离级别
set session transaction isolation level repeatable read;
- 1
设置系统全局(默认)事务隔离级别
set global transaction isolation level repeatable read;
- 1
read uncommitted测试
打开一个客户端A,并设置当前事务模式为read uncommitted(未提交读),查询表account的初始值:
在客户端A的事务提交之前,打开另一个客户端B,更新表account:
这时,虽然客户端B的事务还没提交,但是客户端A就可以查询到B已经更新的数据:
一旦客户端B的事务因为某种原因回滚,所有的操作都将会被撤销,那客户端A查询到的数据其实就是脏数据:
read committed测试
针对上面的问题,把客户端A的事务隔离级别设置为read committed,再重复上面的步骤,会发现B在事务结束之前,A并不能查询到B所做的操作。
客户端A的执行结果
客户端B的执行结果
但是正如上文所说,这种隔离级别下可能导致前事务中多次读取特定记录的结果不相同,比如客户端A事务隔离级别为read committed,在A的一个事务中,执行两次相同的查询,在这两次查询的中间,客户端B对数据进行更改并提交事务,那么会导致客户端A的两次查询结果不一致,导致“不可重复读”的麻烦。
客户端A的执行结果
客户端B的执行结果
repeatable read测试
同理,如果设置了repeatable read隔离级别,就可以保证在当前事务中多次执行相同查询的结果集相同,实现“可重复读”。
同样以客户端A设置事务隔离级别为repeatable read为例,执行结果如下:
客户端A的执行结果
客户端B的执行结果
上篇文章说到,这种隔离级别会导致“幻读”,比如客户端A中事务操作表中符合条件的若干行,同时客户端B中事务插入符合A操作条件的数据行,然后再提交。究竟会发生什么nie~~
小编为此也进行了测试:
客户端A的执行结果
客户端B的执行结果
结果却不像我们预测的那样,为客户端A中的事务设置隔离级别为repeatable read,但在客户端B中的事务插入数据后,A并没有出现“幻读”的现象。查了资料才知道,原来在mysql中,不会出现幻读。mysql的实现和标准定义的RR隔离级别有差别。
当然,上篇文章说到的只是SQL规范所规定的标准,不同的数据库具体的实现可能会有些差异,所以还是以实际为准~~
实践出真知,所以每接触到一些理论知识时,最好能够实践一把,自己动手去探索,总会带给你意想不到的收货哦。