跳至主要內容

记录锁+间隙锁可以防止删除操作而导致的幻读吗?

张威大约 4 分钟mysqlmysql锁机制

记录锁+间隙锁可以防止删除操作而导致的幻读吗?

MySQL 是怎么解决幻读的?

MySQL InnoDB 引擎的默认隔离级别虽然是「可重复读」,但是它很大程度上避免幻读现象(并不是完全解决了),解决的方案有两种:

  • 针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。
  • 针对当前读(select … for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select … for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题

加锁分析

问题来了,A 事务在执行 select … for update 语句时,具体加了什么锁呢?

如果对 age 没有索引,事务 A 这条查询会加什么锁呢?

我们可以通过 select * from performance_schema.data_locks\G; 这条语句,查看事务执行 SQL 过程中加了什么锁。

从上面输出的信息可以看到,共加了两种不同粒度的锁,分别是:

表锁(LOCK_TYPE: TABLE):X 类型的意向锁; 行锁(LOCK_TYPE: RECORD):X 类型的 next-key 锁; 这里我们重点关注「行锁」,图中 LOCK_TYPE 中的 RECORD 表示行级锁,而不是记录锁的意思:

如果 LOCK_MODE 为 X,说明是 next-key 锁; 如果 LOCK_MODE 为 X, REC_NOT_GAP,说明是记录锁; 如果 LOCK_MODE 为 X, GAP,说明是间隙锁

因此,此时事务 A 在主键索引(INDEX_NAME : PRIMARY)上加了 10 个 next-key 锁,如下:

X 型的 next-key 锁,范围:(-∞, 1] X 型的 next-key 锁,范围:(1, 2] X 型的 next-key 锁,范围:(2, 3] X 型的 next-key 锁,范围:(3, 4] X 型的 next-key 锁,范围:(4, 5] X 型的 next-key 锁,范围:(5, 6] X 型的 next-key 锁,范围:(6, 7] X 型的 next-key 锁,范围:(7, 8] X 型的 next-key 锁,范围:(8, 9] X 型的 next-key 锁,范围:(9, +∞]

这相当于把整个表给锁住了,其他事务在对该表进行增、删、改操作的时候都会被阻塞。

如果对 age 建立索引,事务 A 这条查询会加什么锁呢?

继续通过 select * from performance_schema.data_locks\G; 这条语句,查看事务执行 SQL 过程中加了什么锁。

因为表中有两个索引,分别是主键索引和 age 索引,所以会分别对这两个索引加锁。

总结

在 MySQL 的可重复读隔离级别下,针对当前读的语句会对索引加记录锁+间隙锁,这样可以避免其他事务执行增、删、改时导致幻读的问题。