前言
前几天在和老杨分析一个生产问题,老杨说,你这个update在事务里面还是有并发问题(两个事务同时执行,事务都没提交,都更新成功了)。这句话让猝不及防,我一直都是这么做的呀,于是我又下来了解一下锁知识和事务这块的知识。(老杨说的在事务同时执行update是不存在,update是会行加写锁的,直到事务commit,在此期间其他事务更新会直接阻塞)
特别是事务这块,看到了一些博客上没有的东西,还有关于可重复的幻读问题,比如下面是美团技术团队很早发的一篇技术博客(mysql解决了可重复读的幻读问题)。
其他博客很多的都是直接说可重复读会出现幻读,早在几年前我就测试过没有出现幻读的情况,面试的时候还给面试官说我没有复现过这个问题。
可重复读到底会不会出现幻读呢,下面我们从官网对事务的介绍,重新认识一下事务的隔离级别
事务隔离级别 (官方文档)
每个事务隔离级别,都使用了不同的锁策略。
| 隔离级别 | 脏读(读取事务未提交的数据) | 不可重复读(多次读取相同数据结果不一样,被其他事务修改并提交) | 幻读(读取到新插入的行) |
|---|---|---|---|
| 读未提交 | 可能 | 可能 | 可能 |
| 读已提交 | 不会 | 可能 | 可能 |
| 可重复读 | 不会 | 不会 | 正常情况不会 |
| 串行化 | 不会 | 不会 | 不会 |
可重复读正常情况不会出现幻读比如Select * from t; 就算其他事务插入数据之后,这个语句也不会读取到新行发生幻读。(ps:哪种情况会出现幻读呢,下面介绍可重复读的时候举了例子,可以看看)
串行化(SERIALIZABLE)
此级别类似于可重复读。但如果autocommit被禁用,InnoDB会隐式地将所有普通SELECT语句转换为SELECT... FOR SHARE,其他事务就不能修改这些数据了。如果autocommit被启用呢, 以一致性非锁定读取方式执行, 不需要阻塞其他事务就可以实现串行化。
可重复读(REPEATABLE READ)
-
普通非锁定语句:
同一事务中的 [一致读取]将读取由第一次读取建立的[快照][SELECT]。这意味着,如果您 在同一事务中发出多个普通(非锁定)语句,这些 [SELECT]语句彼此之间也是一致的(没有幻读)。既然普通select语句每次读取的都是第一次建立得快照,是不是就不会出现幻读了;当然我自己做了测试,是没有出现幻读的,而且官方文档也没有说会出现幻读
-
锁定读取 (select使用for update 或者 for share、update、delete)
对于具有唯一搜索条件的唯一索引,InnoDB仅锁定找到的索引记录;
对于其他搜索条件,InnoDB锁定扫描的索引范围,使用 [间隙锁]或 [下一个键锁] 阻止其他会话插入范围覆盖的间隙(防止幻读发生);因此我们更新数据的时候where条件尽量用索引,减少锁范围;在所范围内,其他事务无法插入数据。
(如果锁定读取中where条件没有索引,就算不更新这个行,也会被锁住,直到commit/rollback)
发生幻读情况
上面两种情况都测试,都没有幻读发生啊,什么情况会发生幻读;
-
非锁定语句读的是第一次的快照数据,锁定读取没有说要读取第一次快照数据呀。假设同样的Select 语句同样的条件,第一次使用非锁定读取,第二次使用锁定读取呢?
同样的sql按照两种方式读取,确实会读取到新插入的数据,这种算不算幻读呢?幻读指的是同一个select语句。这就不算吧
-
普通锁定 会发生幻读
每次读取的都是快照数据,只有快照数据更新后,才会发生幻读。所以说正常情况还真的复现不了幻读的情况,但是
update会更新到新插入的数据,再次select也就出现幻读了,难怪我复现不出来。。。。。。。
读已提交(READ COMMITTED)
每次一致性读取(即使是在同一个事务中)都会设置并读取自己的新快照(因此发生不可重复读问题);
对于锁定读取语句,InnoDB仅锁定索引记录,而不锁定它们之前的间隙,从而允许在锁定的记录旁边自由插入新记录,发生幻读问题。
对于[
UPDATE]语句,InnoDB仅对更新或删除的行保持锁定。在 MySQL 评估条件后,将释放不匹配行的记录锁定WHERE。这大大降低了死锁的可能性,但仍有可能发生死锁。
读未提交(READ UNCOMMITTED)
[SELECT]语句以非锁定方式执行,可能会使用新的早期版本。因此,使用此隔离级别,此类读取不一致。