做一年的网站能赚多少钱,个人网站认证,进入公众号主页,上传网站模板建站昨天有位读者在美团二面的时候#xff0c;被问到关于幻读的问题#xff1a;面试官反问的大概意思是#xff0c;MySQL 记录锁间隙锁可以防止删除操作而导致的幻读吗#xff1f;答案是可以的。接下来#xff0c;通过几个小实验来证明这个结论吧#xff0c;顺便再帮大家复习…昨天有位读者在美团二面的时候被问到关于幻读的问题面试官反问的大概意思是MySQL 记录锁间隙锁可以防止删除操作而导致的幻读吗答案是可以的。接下来通过几个小实验来证明这个结论吧顺便再帮大家复习一下记录锁间隙锁。什么是幻读首先来看看 MySQL 文档是怎么定义幻读Phantom Read的:The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times.翻译当同一个查询在不同的时间产生不同的结果集时事务中就会出现所谓的幻象问题。举个例子假设一个事务在 T1 时刻和 T2 时刻分别执行了下面查询语句途中没有执行其他任何语句SELECT * FROM t_test WHERE id 100;只要 T1 和 T2 时刻执行产生的结果集是不相同的那就发生了幻读的问题比如T1 时间执行的结果是有 5 条行记录而 T2 时间执行的结果是有 6 条行记录那就发生了幻读的问题。T1 时间执行的结果是有 5 条行记录而 T2 时间执行的结果是有 4 条行记录也是发生了幻读的问题。MySQL 是怎么解决幻读的MySQL 可重复读隔离级别是解决幻读问题查询数据的操作有两种方式所以解决的方式是不同的针对快照读普通 select 语句是通过 MVCC 方式解决了幻读因为可重复读隔离级别下事务执行过程中看到的数据一直跟这个事务启动时看到的数据是一致的即使中途有其他事务插入了一条数据是查询不出来这条数据的所以就很好了避免幻读问题。针对当前读select ... for update 等语句是通过 next-key lock记录锁间隙锁方式解决了幻读因为当执行 select ... for update 语句的时候会加上 next-key lock如果有其他事务在 next-key lock 锁范围内插入了一条记录那么这个插入语句就会被阻塞无法成功插入所以就很好了避免幻读问题。实验验证接下来来验证「 MySQL 记录锁间隙锁可以防止删除操作而导致的幻读问题」的结论。实验环境MySQL 8.0 版本可重复读隔离级。现在有一张用户表t_user表里只有一个主键索引表里有以下行数据现在有一个 A 事务执行了一条查询语句查询到年龄大于 20 岁的用户共有 6 条行记录。然后 B 事务执行了一条删除 id 2 的语句此时B 事务的删除语句就陷入了等待状态说明是无法进行删除的。因此MySQL 记录锁间隙锁可以防止删除操作而导致的幻读问题。加锁分析问题来了A 事务在执行 select ... for update 语句时具体加了什么锁呢我们可以通过 select * from performance_schema.data_locks\G; 这条语句查看事务执行 SQL 过程中加了什么锁。输出的内容很多共有 11 行信息我删减了一些不重要的信息从上面输出的信息可以看到共加了两种不同粒度的锁分别是表锁LOCK_TYPE: TABLEX 类型的意向锁行锁LOCK_TYPE: RECORDX 类型的 next-key 锁这里我们重点关注「行锁」图中 LOCK_TYPE 中的 RECORD 表示行级锁而不是记录锁的意思如果 LOCK_MODE 为 X说明是 next-key 锁如果 LOCK_MODE 为 X, REC_NOT_GAP说明是记录锁如果 LOCK_MODE 为 X, GAP说明是间隙锁然后通过 LOCK_DATA 信息可以确认 next-key 锁的范围具体怎么确定呢根据我的经验如果 LOCK_MODE 是 next-key 锁或者间隙锁那么LOCK_DATA 就表示锁的范围最右值而锁范围的最左值为 LOCK_DATA 的上一条记录的值。因此此时事务 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, ∞]这相当于把整个表给锁住了其他事务在对该表进行增、删、改操作的时候都会被阻塞。只有在事务 A 提交了事务事务 A 执行过程中产生的锁才会被释放。为什么只是查询年龄 20 岁以上行记录而把整个表给锁住了呢这是因为事务 A 的这条查询语句是全表扫描锁是在遍历索引的时候加上的并不是针对输出的结果加锁。因此在线上在执行 update、delete、select ... for update 等具有加锁性质的语句一定要检查语句是否走了索引如果是全表扫描的话会对每一个索引加 next-key 锁相当于把整个表锁住了这是挺严重的问题。如果对 age 建立索引事务 A 这条查询会加什么锁呢接下来我对 age 字段建立索引然后再执行这条查询语句接下来继续通过 select * from performance_schema.data_locks\G; 这条语句查看事务执行 SQL 过程中加了什么锁。具体的信息我就不打印了我直接说结论吧。因为表中有两个索引分别是主键索引和 age 索引所以会分别对这两个索引加锁。主键索引会加如下的锁X 型的记录锁锁住 id 2 的记录X 型的记录锁锁住 id 3 的记录X 型的记录锁锁住 id 5 的记录X 型的记录锁锁住 id 6 的记录X 型的记录锁锁住 id 7 的记录X 型的记录锁锁住 id 8 的记录分析 age 索引加锁的范围时要先对 age 字段进行排序。age 索引加的锁X 型的 next-key lock锁住 age 范围 (19, 21] 的记录X 型的 next-key lock锁住 age 范围 (21, 21] 的记录X 型的 next-key lock锁住 age 范围 (21, 23] 的记录X 型的 next-key lock锁住 age 范围 (23, 23] 的记录X 型的 next-key lock锁住 age 范围 (23, 39] 的记录X 型的 next-key lock锁住 age 范围 (39, 43] 的记录X 型的 next-key lock锁住 age 范围 (43, ∞] 的记录化简一下age 索引 next-key 锁的范围是 (19, ∞]。可以看到对 age 字段建立了索引后查询语句是索引查询并不会全表扫描因此不会把整张表给锁住。总结一下在对 age 字段建立索引后事务 A 在执行下面这条查询语句后主键索引和 age 索引会加下图中的锁。事务 A 加上锁后事务 B、C、D、E 在执行以下语句都会被阻塞。总结在 MySQL 的可重复读隔离级别下针对「当前读」的查询语句会对索引加记录锁间隙锁这样可以避免其他事务执行「增、删、改」时导致幻读的现象。有一点要注意的是在执行 update、delete、select ... for update 等具有加锁性质的语句一定要检查语句是否走了索引如果是全表扫描的话会对每一个索引加 next-key 锁相当于把整个表锁住了这是挺严重的问题。这次教了大家如何分析事务具体加了什么锁以后大家可以多做实验然后自己尝试分析分析掌握分析的方法远比记住加锁规则强完