InnoDB的默认事务隔离级别是可重复读
一、幻读
1.SQL场景复现
建一个测试表:
CREATE TABLE `t` (
`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c` (`c`)
) ENGINE=InnoDB;
insert into t values(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);
2. 概念引入
写锁(更新锁):在对一行更新时(或者在select后加
for update
),对该行加写锁,加锁期间其他行不可以查询或修改该行。
间隙锁(Gap Lock):间隙锁 锁的是两个值之间的空隙。比如:比如文章开头的表t,初始化插入了6个记录,这就产生了7个间隙。
间隙锁特性: 跟间隙锁产生冲突的,是“往这个间隙中插入一条记录”的操作。间隙锁之间不存在冲突关系。 也就是说,间隙锁可以重复叠加。这个特性可能会导致死锁问题。
间隙锁是在可重复读隔离级别下才会生效的
PS:如果把隔离级别设置为读提交的话,就没有间隙锁了。但同时,你要解决可能出现的数据和日志不一致问题,需要把binlog格式设置为row。这也是现在不少公司使用的配置组合。
3.场景分析
可以看到,session A里执行了三次查询,分别是Q1、Q2和Q3。它们的SQL语句相同,都是select * from t where d=5 for update
。查所有d=5的行,而且使用的是当前读,并且加上写锁。
MySQL中,带有
for update
的select语句会给扫描过的每一行记录加写锁 。
MySQL中,给记录加写锁时,会把该记录两边的间隙加间隙锁。
因此,T1时刻,sessionA执行的语句将给表t
所有的行加写锁,T2时刻,sessionB的更新语句由于表t
整个都加了写锁,所以会等到sessionA事务提交 释放写锁后 才会执行。
T4时刻,sessionC的插入语句由于表t
整个都加了间隙锁,所以无法插入,会等到sessionA事务提交后 释放间隙锁后 才会执行插入操作。