三种行锁算法
InnoDB 支持行锁,包含三种行锁算法:
Record Lock: 单个记录上的锁;Gap Lock: 间隙锁,它锁住的是一个范围,不包含记录本身;Next-key Lock: 等效于 Record Lock + Gap Lock。
特别注意:InnoDB 行锁是通过 给索引上的索引项加锁来实现的。因此,在执行当前读的sql语句时,一定要记得带上索引条件,否则,mysql 将会执行全表扫描,将整个表锁住。
假设有一张测试表 t:
create table t (a int primary key);insert into t select 1;insert into t select 2;insert into t select 5;执行 select * from t for update(这里一定要使用 当前读),三种锁算法锁定的记录/范围可以表示为:
注:∞ 不是数学意义上的无穷大,只是 mysql 定义的一个不存在的最大值。
Next-key Lock
InnoDB 默认的事务隔离级别是 可重复读(RR),对于行的查询都是使用的 Next-key Lock 算法,当然,为了提高并发性,在特定的查询中,Next-key Lock 可能降级为 Record Lock 或者 Gap Lock。
实际的加锁规则是比较复杂的,而且可能因为版本的不同而有所差异,因此,这里不做深究,可以参考文章(MySQL锁详解),对加锁规则做了详细的介绍以及案例展示。再次强调,加锁规则由于版本不同而存在差异,本人机器安装 mysql 版本是 8.0.18,针对文章里第六节描述的 案例 3 和案例 6 就和本人实测结果不一致。
解决幻读问题
幻读指的是在同一事务下,多次执行同样的读取操作,读到的结果集不一致。而 Next-key 通过对范围加锁就可以解决这个问题。
还是用以上测试表 t,一个事务 T1 执行如下查询:
select * from t where a > 2 for update;
查询会返回 5 这一条记录。
此时,如果另一个事务 T2 想要执行插入语句: insert into t select 4,那么
如果没有 Next-key Lock,插入语句将会成功执行,那么事务 T1 再次执行以上查询语句将会返回 4 和 5 两条记录,与第一次返回的结果集不一样,不满足可重复读的事务隔离级别。
如果考虑 Next-key Lock,以上 sql 查询会对 (2,+∞)这个范围加锁,因此事务 T2 插入将会被阻塞,由此避免了幻读问题。