事务的四大隔离级别中的幻读问题

本文以InnoDB存储引擎为例,介绍了数据库并发访问中delete和insert操作可能出现的幻读现象,即事务A结果集因事务B改变而增减。还阐述了幻读的解决方案,如将隔离级别设为SERIALIZABLE,以及RR级别下快照读和当前读避免幻读的原理。

1. 什么是幻读?

大部分学习过数据库的人应该都了解幻读这个概念,我在这里帮大家复习下。以下都是以InnoDB存储引擎为例进行说明。数据库并发访问中,针对delete和insert操作可能出现幻读这种现象。假设数据库的隔离级别是RC( READ COMMITTED),而且其中存在下图所示的数据,表名为account:

如果session 1 中操作:

1.start transaction;  

2.select count(id) from account;

3.commit;

session2中操作:

1.start transaction; 

2.insert into accout values(6,'徐六',1200);

3.commit;

如果现在先后执行上述两个session中的1,随后执行session 2中的2和3,最后执行session 1中的2,最后的结果是6,这个结果是符合逻辑的,即在插入一行数据之后,读取所有的数据行数是5+1,但是大家不妨仔细想一想,在session 1是在session 2之前开启事务的,所以session 1中应该读取的结果是5而不是6,这是因为session 2改变了session 1本来的结果集,从而导致了session 1的结果增加一条行记录,我们称上述现象为“幻读”。

幻读:事务A读取其符合条件的若干行,但是其他事务B改变了事务A的结果集,导致在事务A的结果集的个数增加或者减少,这种现象就叫做幻读。

2.幻读的解决方案

将数据库的隔离级别变成SERIALIZABLE就可以避免幻读现象的发生,REPEATABLE READ在某些情况下也可以避免幻读。其中SERIALIZABLE是数据隔离级别中最高的隔离级别,顾名思义,串行化,即数据库已经不存在并发执行,事务间是以串行方式执行,这种隔离级别的安全级别极高,但是并发性低,数据库的效率较低,适用于安全级别要求较高,性能要求较低的场合,而REPEATABLE是Mysql InnoDB存储引擎的默认隔离级别,可以在特殊情况下避免幻读。上述论述留下了一个问题,这就是在InnoDB存储引擎的REPEATABLE READ(RR)级别下如何避免幻读,原理是什么?

(1)在RR级别下避免幻读的发生,如果是在A事务中使用快照读先读取数据,无论事务B是否提交,在事务A中再一次快照读,读取的都是上一次快照读的数据;(这个是由快照生成的时间决定,具有不稳定性);

(2)如果是使用当前读读取事务,则内部原理就是使用next-key锁机制。next-key锁有两种,其一是行锁,其二是gap锁。如果是走的是唯一键索引,则如果是where条件全部命中,则仅需要使用行锁(为什么?原因是如果全部命中而且还是唯一索引则另一个事务无法对该事务中where后面的范围中的数据再一次进行操作,因为是唯一的),反之,需要行锁+gap合力作用,锁住可能出现幻读的数据范围。如果走的是非唯一索引或者是不加索引,则需要是行锁+gap锁两种锁。

  • 下面详细分析下如果是非唯一索引的情况下加gap锁的细节:

                                               

以上述例子为说明,如果事务A要删除id = 9的行,这个时候会在id = 9的行上加上Gap锁,其中在区间(6,9]和(9,11]的区间上加上Gap锁,这个区间在事务A提交之前是无法操作的。如果事务B对上述区间操作中的增加id=9的数据,是会出现幻读的。

由于B+树的叶子节点的数据是按照顺序进行存放的,所以对于边界条件上的情况分辨如下:

可能出现幻读的区域为:{id,name}:({6,c}--{9,b}] ({9,b}--{9,d}] ({9,d}--{11,f}]

故而:{6,b}不会加锁,{6,dd}加锁,无法插入 {6,c}<{6,xx}都是会被加上Gap锁

同理:{11,g}不会被加锁,{11,f}会被加上锁,{11,yy}<={11,f}都是会被加上Gap锁

  • 如果不加索引的情况下加gap锁的细节:

                                              

当前读操作如果不使用索引,则所有的区间都会被Gap锁锁住,类似于锁表,这种情况需要避免,因为这样数据库的效率很低。

综上所述,在RR级别下使用当前读操作可以避免出现幻读,快照读操作是可能无法彻底避免幻读的产生,这个和快照生成的时机有着关系。

 

个人公众号:王者研究中心,更多计算机知识可关注微信公众号了解,还有个人原创学习心得免费分享。

 

 

 

 

### 事务四大隔离级别 数据库事务隔离级别用于控制多个事务并发执行时可能出现的数据不一致问题,主要包括脏、不可重复。以下是四种主要的隔离级别及其特点: #### 1. 未提交(READ UNCOMMITTED) 这是最低的隔离级别,允许一个事务取另一个事务尚未提交的数据。在这种隔离级别下,可能会出现脏、不可重复问题。 ```sql SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT age FROM users WHERE name = '小明'; -- 可能取到其他事务未提交的更改[^1] ``` #### 2. 已提交(READ COMMITTED) 此隔离级别确保一个事务只能取另一个事务已经提交的数据,避免了脏的发生。然而,在这种隔离级别下,仍然可能出现不可重复的现象。 ```sql SET TRANSACTION ISOLATION LEVEL READ COMMITTED; BEGIN TRANSACTION; SELECT age FROM users WHERE name = '小明'; -- 第一次取年龄为20岁 -- 其他事务将小明的年龄更新为30岁并提交 SELECT age FROM users WHERE name = '小明'; -- 第二次取年龄为30岁[^3] COMMIT; ``` #### 3. 可重复(REPEATABLE READ) 在该隔离级别下,事务内的多次取结果是一致的,能够避免脏和不可重复问题。然而,仍然可能发生。 ```sql SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; BEGIN TRANSACTION; SELECT age FROM users WHERE name = '小明'; -- 第一次取年龄为20岁 -- 其他事务将小明的年龄更新为30岁并提交 SELECT age FROM users WHERE name = '小明'; -- 第二次取年龄仍为20岁[^2] COMMIT; ``` #### 4. 串行化(SERIALIZABLE) 这是最高的隔离级别,通过强制事务串行执行来避免所有类型的并发问题,包括脏、不可重复。然而,这种方式会带来较大的性能开销。 ```sql SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; BEGIN TRANSACTION; SELECT age FROM users WHERE name = '小明'; -- 确保数据一致性 -- 其他事务无法插入或修改相关数据,直到当前事务完成 COMMIT; ``` ### 总结 不同的隔离级别适用于不同的应用场景。较低的隔离级别通常提供更高的性能,但可能引入更多的数据不一致问题;而较高的隔离级别则提供了更强的一致性保障,但可能降低系统的并发性能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值