背景故事
事务A | 事务B |
---|---|
begin; | |
select count(*) from t_user where uid > 20; // 返回1 |
|
insert into t_user values(30,‘AA’,20); | |
update t_user set age = 30 where uid > 20; // 显示有两条记录修改成功 |
上面两个事务,首先先开启事务A,查询id大于20的记录数,结果返回1,说明只有1条记录uid大于20,随后事务B插入了一条uid为30的记录并提交,此时事务A开始执行修改操作,将所有uid大于20的记录的age字段修改为30,执行后显示有两条记录修改成功。这就是常说的幻读形象。
而常见的解决幻读的方法有两种:
- 将隔离级别升级到SERIALIZABLE;
- 加锁。
一般不会将数据库隔离级别设置为SERIALIZABLE,那么加锁的话,加什么锁呢?Next-Key Lock。
Next-Key Lock是什么?Next-Key Lock是如何解决幻读的?
下面就来仔细看看。
InnoDB下有三种锁算法:
- Record Lock :单个行记录加锁
- Gap Lock :间隙锁,锁定一个范围,但不含记录本身
- Next-Key Lock :Record Lock + Gap Lock
MySQL InnoDB存储引擎中默认使用的是Next-Key Lock,先使用Gap Lock,当通过对应唯一索引查询时,会将Gap Lock降级为Record Lock。
假设有如下数据:
节点结构可以简单看作如下形式:
在上面数据中,Recored Lock对应的就是这三个节点,也就是对应uid为1,10,30的记录,而Gap Lock就是这几个节点之间的范围。整个锁范围可以分为(-∞,1) U 1 U (1,10) U 10 U (10,30) U 30 U (30,+∞)。
下面通过特定的几个场景来看下实际的效果:
表结构
CREATE TABLE `t_user` (
`uid` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`uname` varchar(30) DEFAULT NULL COMMENT '用户名',
`age` int(11) DEFAULT NULL COMMENT '年龄',
PRIMARY KEY (`uid`),
KEY