)
第84篇MySQL事务与锁2026版系列导航《Java 100 天进阶之路》完整目录 |⬅️ 上一篇第83篇MySQL索引 |➡️ 下一篇第85篇SQL优化实战待发布一、核心知识点事务四大特性ACID原子性、一致性、隔离性、持久性隔离级别读未提交RU、读已提交RC、可重复读RR、串行化Serializable并发问题脏读、不可重复读、幻读、读偏斜Read SkewMVCC多版本并发控制InnoDB 实现 RR 和 RC 的核心通过隐藏列和 Undo Log 构建一致性读视图锁机制行锁、间隙锁Gap Lock、临键锁Next-Key Lock、表锁、元数据锁MDL、意向锁当前读 vs 快照读SELECT ... FOR UPDATE/LOCK IN SHARE MODE为当前读普通SELECT为快照读死锁产生条件、排查方法MySQL 8.0 新视图及重试策略MySQL 8.0 新特性锁等待超时控制、废弃INNODB_LOCKS表改用performance_schema.data_locks二、通俗讲解1分钟开心学1. 事务是什么事务是一组 SQL 操作的逻辑单元要么全部成功要么全部失败。就像银行转账A 扣钱、B 加钱两步必须同时成功或同时失败。2. 隔离级别与并发问题隔离级别脏读不可重复读幻读说明读未提交✅ 可能✅ 可能✅ 可能性能最高基本不用读已提交❌✅ 可能✅ 可能Oracle 默认同一事务内多次读结果可能不同可重复读❌❌⚠️ InnoDB 通过间隙锁解决MySQL 默认同一事务内多次读结果一致串行化❌❌❌读写都加锁性能最低脏读读到其他事务未提交的数据。不可重复读同一事务内两次读同一行数据结果不同因其他事务更新并提交。幻读同一事务内两次查询记录数不同因其他事务插入或删除。读偏斜Read Skew事务中先后读取多行数据由于其他事务的并发更新导致读到不一致的快照。MVCC 能解决部分场景但当前读仍可能遇到。生活类比脏读你看到别人草稿箱里的工资还没发结果人家改了。不可重复读你第一次查余额 100别人存了 50你再查变成 150。幻读你查工资低于 5000 的员工有 3 人别人新增一名低工资员工你再查变成 4 人。3. MVCC 原理核心InnoDB 每行数据有两个隐藏列DB_TRX_ID最后修改事务ID、DB_ROLL_PTR指向 Undo Log 版本链。事务开始时生成ReadView活跃事务ID列表。查询时根据可见性规则如事务ID小于最小活跃ID则可见从版本链中选择合适的版本实现非阻塞读。4. InnoDB 锁类型行锁锁定单行记录基于索引实现。不通过索引条件更新会锁表。间隙锁锁定索引记录之间的间隙不含记录本身防止幻读。仅在 RR 隔离级别下存在。临键锁行锁 间隙锁锁定索引记录及其前间隙。表锁LOCK TABLES ... WRITE/READ或意向锁InnoDB 自动加。元数据锁MDL保护表结构DDL 时会阻塞 DML。MySQL 8.0 增强performance_schema.data_locks提供更详细的锁信息取代废弃的INNODB_LOCKS。口诀RC 只有行锁RR 有行锁间隙锁唯一索引等值查询且记录存在时退化为行锁否则为临键锁。三、实操代码案例 场景说明3.1 验证隔离级别-- 查看当前隔离级别SELECTtransaction_isolation;-- 设置会话隔离级别为读已提交SETSESSIONTRANSACTIONISOLATIONLEVELREADCOMMITTED;-- 设置全局隔离级别重启后失效需改配置文件SETGLOBALTRANSACTIONISOLATIONLEVELREPEATABLEREAD;3.2 演示不可重复读RC 级别-- 会话ASETSESSIONTRANSACTIONISOLATIONLEVELREADCOMMITTED;STARTTRANSACTION;SELECTbalanceFROMaccountWHEREid1;-- 假设返回 100-- 会话B 更新并提交UPDATEaccountSETbalance200WHEREid1;COMMIT;-- 会话A 再次查询SELECTbalanceFROMaccountWHEREid1;-- 返回 200不可重复读COMMIT;3.3 演示幻读RR 级别下间隙锁避免-- 会话ASETSESSIONTRANSACTIONISOLATIONLEVELREPEATABLEREAD;STARTTRANSACTION;SELECT*FROMuserWHEREageBETWEEN20AND30;-- 假设返回 2 行-- 会话B 插入满足条件的行INSERTINTOuser(age)VALUES(25);COMMIT;-- 会话A 再次查询SELECT*FROMuserWHEREageBETWEEN20AND30;-- 仍是 2 行快照读无幻读-- 会话A 当前读加锁SELECT*FROMuserWHEREageBETWEEN20AND30FORUPDATE;-- 此时会阻塞间隙锁生效3.4 死锁模拟与排查MySQL 8.0 推荐写法-- 会话ASTARTTRANSACTION;UPDATEaccountSETbalancebalance-100WHEREid1;-- 锁住 id1-- 未提交-- 会话BSTARTTRANSACTION;UPDATEaccountSETbalancebalance-50WHEREid2;-- 锁住 id2-- 未提交-- 会话A 尝试锁 id2阻塞UPDATEaccountSETbalancebalance-100WHEREid2;-- 会话B 尝试锁 id1死锁出现UPDATEaccountSETbalancebalance-50WHEREid1;-- 此时 MySQL 检测到死锁回滚其中一个事务通常回滚较小代价的事务排查死锁2026 版推荐-- 【MySQL 8.0】使用 Performance Schema 查看锁等待SELECT*FROMperformance_schema.data_locks;SELECT*FROMperformance_schema.data_lock_waits;-- 使用 sys schema更友好SELECT*FROMsys.innodb_lock_waits\G-- 查看最近一次死锁详细信息SHOWENGINEINNODBSTATUS;应用层重试策略高并发场景下捕获1213错误码死锁进行有限次数重试通常 3 次避免依赖数据库自动回滚导致的业务中断。 RC 与 RR 隔离级别加锁机制对比图下图直观展示了在 RC读已提交和 RR可重复读隔离级别下不同查询操作等值查询、范围查询、更新的加锁范围差异。图解要点RC 级别仅对满足条件的索引记录加行锁X 锁不锁间隙 → 幻读可能但并发度更高。RR 级别对满足条件的索引记录及其前间隙加临键锁行锁 间隙锁部分场景如唯一索引等值查询且记录存在退化为行锁 → 避免幻读但并发度降低。四、避坑要点误区/错误后果正确做法事务中长时间不提交锁不释放阻塞其他事务事务尽量短小及时提交更新/删除不走索引行锁升级为表锁并发极差确保 WHERE 条件能用到索引或明确加索引RR 级别下使用SELECT ... FOR UPDATE范围条件可能锁住间隙影响并发评估是否需要 RR读已提交可减少间隙锁先查询后判断再更新非原子产生并发问题丢失更新使用乐观锁版本号或悲观锁FOR UPDATE不注意 MVCC 与当前读的区别误以为 RR 完全避免幻读当前读仍可能遇到理解快照读与当前读死锁发生后不处理业务报错用户体验差重试机制优化 SQL 顺序加索引减少锁范围在 MySQL 8.0 中使用废弃的INNODB_LOCKS表查询报错改用performance_schema.data_locks或sys.innodb_lock_waits五、面试高频考点Q1MySQL 默认隔离级别是什么如何解决幻读默认 REPEATABLE READ。InnoDB 通过间隙锁和MVCC解决幻读快照读普通SELECT用 MVCC 保证一致性视图当前读SELECT ... FOR UPDATE用间隙锁锁定范围防止其他事务插入。Q2MVCC 如何实现每行记录包含DB_TRX_ID和DB_ROLL_PTR。事务开始时生成ReadView活跃事务ID列表。查询时根据可见性算法如DB_TRX_ID 最小活跃ID 则可见若在活跃列表中则不可见需沿 Undo 链回溯找到合适版本。Q3RR 级别下是否完全避免了幻读快照读可以避免但当前读FOR UPDATE仍可能出现幻读实际 InnoDB 通过间隙锁已基本解决。严格说RR 下通过间隙锁锁住了索引记录和间隙因此当前读也能避免幻读。若查询条件不是索引或索引不够密集仍可能漏锁但主键/唯一键场景是安全的。此外MySQL 8.0 在死锁检测和加锁算法上有所优化减少了不必要的间隙锁。Q4行锁什么时候升级为表锁当 WHERE 条件没有使用索引或用了索引但优化器认为全表扫描更快时InnoDB 会对所有聚簇索引记录加锁效果等同于表锁。因此更新/删除务必走索引。Q5死锁产生原因及排查方法多个事务互相持有对方需要的锁。排查SHOW ENGINE INNODB STATUS查看最近死锁信息performance_schema.data_locks和data_lock_waits查询当前锁等待关系。预防固定加锁顺序如先锁 id 小的、减少事务粒度、使用乐观锁。Q6SELECT ... FOR UPDATE和LOCK IN SHARE MODE区别FOR UPDATE是排他锁X锁其他事务不能读取加锁读或修改LOCK IN SHARE MODE是共享锁S锁其他事务可读但不能修改。两者都属于当前读。Q7MySQL 如何保证原子性和持久性原子性通过 Undo Log回滚日志实现执行回滚时恢复到修改前版本。持久性通过 Redo Log重做日志实现事务提交时先写 Redo Log即使宕机也能重放恢复。Q8MySQL 8.0 在锁监控方面有哪些改进废弃了INNODB_LOCKS和INNODB_LOCK_WAITS统一使用performance_schema.data_locks和data_lock_waits提供更详细的锁信息锁模式、锁状态、锁数据等。同时sys.innodb_lock_waits视图提供了更友好的输出。六、练习题附解题思路场景题在 RR 隔离级别下SELECT * FROM t WHERE id 10 FOR UPDATEid 为主键表中有 id5,12,20。请问锁了哪些范围思路由于 id 是主键聚簇索引RR 下当前读使用临键锁。锁定的范围包括记录 id12 及前间隙 (10,12]记录 id20 及前间隙 (12,20]最大正边界 (20, ∞)若使用辅助索引还会锁住索引记录及对应主键的间隙。实际生产中可通过performance_schema.data_locks验证。代码模拟一个死锁场景使用SHOW ENGINE INNODB STATUS分析死锁日志。思路参照 3.4 节的模拟 SQL执行后通过SHOW ENGINE INNODB STATUS查看LATEST DETECTED DEADLOCK部分分析事务 SQL、等待资源、被回滚的事务。设计某秒杀系统库存数量从 Redis 预减最后异步落库。若使用 MySQL 行锁更新库存如何避免超卖写出 SQL。思路使用乐观锁版本号或悲观锁FOR UPDATE。悲观锁示例STARTTRANSACTION;SELECTstockFROMproductWHEREid?FORUPDATE;-- 检查 stock 购买量UPDATEproductSETstockstock-?WHEREid?;COMMIT;注意必须在索引条件下执行避免锁表。同时结合应用层重试机制。 你的学习进度当前第84篇 / 共108篇 ·进阶篇数据库与持久层框架第83~90篇✅ 已完成基础篇44篇 第91~ 96篇Redis/MQ 第83~84篇 正在学第84篇⏳ 待学习第85~ 90篇SQL优化/MyBatis等 第97~108篇 完整目录 学习指南 | 订阅本专栏不错过每一篇 本专栏每篇都包含避坑表 面试高频考点 练习题。每天30分钟100天拿offer 下一篇文章预告《第85篇SQL优化实战2026版》内容简介慢查询日志分析、EXPLAIN执行计划详解、索引优化案例、分页优化、COUNT 优化、JOIN 优化、SQL 改写技巧。 学完这篇你将掌握 SQL 调优全流程让数据库性能翻倍。福利提醒评论区留言“MySQL锁”可领取《MySQL 锁与事务隔离级别超全对比图》PDF。《Java 100 天进阶之路 | 从入门到上岗就业》每天一篇建议收藏 关注一起100天拿offer 点击关注我更新后第一时间收到推送