锁机制本质是并发控制的“交通规则”,用于保障事务隔离级别,防止脏读、不可重复读和幻读;其类型与范围取决于索引命中情况、隔离级别及WHERE条件。

锁机制本质是啥?不是功能,是并发控制的“交通规则”
MySQL 锁机制不是可选插件,而是事务隔离级别的底层执行保障。它解决的核心问题只有一个:当多个事务同时读写同一份数据时,如何避免脏读、不可重复读、幻读——说白了,就是不让数据“变魔术”。InnoDB 默认用行锁,但实际加什么锁、锁多大范围,完全取决于查询是否命中索引、隔离级别(RR 还是 RC)、甚至 WHERE 条件里有没有范围(比如 BETWEEN 或 LIKE 'a%')。没索引?哪怕只改一行,也可能触发全表扫描+全表行锁,看起来像锁表。
行锁 vs 表锁:别光看粒度,要看“谁在真正执行”
行锁不是万能并发解药,表锁也不是性能毒药——关键看场景和实现方式:
-
SELECT ... FOR UPDATE和SELECT ... LOCK IN SHARE MODE触发的是行锁(前提是 WHERE 走索引),但若索引失效,InnoDB 会退化为对所有扫描行加锁,10 万行就加 10 万把锁,CPU 和内存压力陡增 -
LOCK TABLES t1 WRITE是显式表锁,简单粗暴,适用于批量导入、DDL 前置保护等短时独占操作;但它会阻塞所有其他线程对这张表的任何读写,包括SELECT - InnoDB 默认不升级行锁为表锁,但“逻辑上锁全表”很常见:比如在 RR 隔离级别下执行
SELECT * FROM users WHERE status = 0 FOR UPDATE,而status列无索引 → 全表扫描 → 每行都加 X 锁 → 效果≈表锁,但开销比真表锁大得多
死锁不是偶发事故,是加锁顺序错位的必然结果
InnoDB 能自动检测死锁并回滚其中一个事务(报错 ERROR 1213 (40001): Deadlock found when trying to get lock),但这只是止损,不是根治。真正要防,得从代码层干预:
- 所有涉及多行更新的业务(比如分账、库存扣减),必须统一按主键或唯一索引升序加锁:
SELECT * FROM orders WHERE id IN (15, 7, 22) FOR UPDATE→ MySQL 内部会自动按7, 15, 22排序加锁;但如果写成两条语句分别锁id=15和id=7,就极易和另一事务形成循环等待 - 避免在事务中混合使用快照读(普通
SELECT)和锁定读(FOR UPDATE),尤其在 RR 级别下,前者不加锁,后者可能触发 gap lock,边界模糊易冲突 - 关键业务开启
innodb_print_all_deadlocks = ON,让死锁详情进 error log;配合SHOW ENGINE INNODB STATUS\G查最近一次死锁链,重点看 “WAITING FOR THIS LOCK TO BE GRANTED” 和 “HOLDS THE LOCK(S)” 两段,直接定位哪两行、哪两个事务卡住了
最容易被忽略的坑:间隙锁(gap lock)根本不在你查的那行上
你以为锁的是数据行?在 RR 隔离级别下,InnoDB 默认用 next-key lock(行锁 + 前面的间隙锁)。比如 id 索引值是 1, 5, 10,执行 SELECT * FROM t WHERE id > 3 AND id < 8 FOR UPDATE,实际锁住的是 (1,5)、(5,10) 两个间隙,以及记录 5 —— 意味着其他事务无法插入 id=2、id=4、id=6 等任何落在这些区间的值。这个行为在唯一索引的等值查询(如 WHERE id = 5)中会退化为纯 record lock,但只要条件含范围、或用的是非唯一索引,gap lock 就如影随形。线上调优时,宁可降级到 RC 隔离级别砍掉 gap lock,也别硬扛它带来的隐式锁膨胀。


















