MySQL行锁实际是对索引项加锁,无索引或索引失效时会升级为全表扫描并加X锁,等效锁表;InnoDB默认临键锁(记录锁+间隙锁)防幻读,READ COMMITTED可降级为仅记录锁。

索引没建好,行锁会自动升级成表锁
MySQL 的行锁不是“对行加锁”,而是“对索引项加锁”。InnoDB 只能通过索引定位数据,如果 WHERE 条件字段没有索引(或索引失效),优化器无法快速定位记录,就会退化为全表扫描——此时为了保证一致性,InnoDB 会为**所有扫描过的记录**加锁;而最坏情况下,它可能对**聚簇索引的每条记录都加 X 锁**,效果等同于锁表。
- 常见错误现象:
UPDATE user SET status=1 WHERE phone='138xxx'执行极慢、阻塞其他事务,SHOW ENGINE INNODB STATUS显示大量lock_mode X locks rec but not gap且涉及成千上万行 - 实操建议:执行前先用
EXPLAIN确认是否走了索引;对高频查询/更新的字段(如phone、order_no)务必建立单独索引或作为联合索引的最左前缀 - 特别注意:
LIKE '%abc'、OR混合条件、函数包裹字段(如WHERE DATE(create_time) = '2025-01-01')都会导致索引失效,触发隐式锁表
为什么加了索引还锁不住“该锁的行”?临键锁(Next-Key Lock)才是默认行为
InnoDB 默认使用 REPEATABLE READ 隔离级别,其行锁实际是 Next-Key Lock(临键锁):即「记录锁 + 间隙锁」的组合。它不仅锁住匹配的索引记录,还会锁住该记录与前一条记录之间的「间隙」,目的是防止幻读。
- 使用场景:执行
SELECT * FROM order WHERE amount > 100 FOR UPDATE时,即使表中当前只有amount=150和amount=200两条记录,InnoDB 也会锁定 (100, 150)、(150, 200)、(200, +∞) 这三个间隙,阻止其他事务插入amount在这些范围内的新订单 - 参数差异:若改用
READ COMMITTED隔离级别,InnoDB 会退化为只加「记录锁」(Record Lock),不加间隙锁——幻读风险上升,但并发写入能力提升 - 容易踩的坑:业务以为
SELECT ... FOR UPDATE只锁查到的几行,结果发现插入操作被莫名阻塞,根源就是临键锁锁住了“不该插的位置”
共享锁(S锁)和排他锁(X锁)的兼容性决定并发读写能否并行
锁的本质是资源访问的互斥协议。InnoDB 中,S 锁(SELECT ... LOCK IN SHARE MODE)允许多个事务同时持有,但会阻塞任何 X 锁;而 X 锁(SELECT ... FOR UPDATE 或 UPDATE/DELETE)一旦加上,其他事务既不能加 S 锁 也不能加 X 锁。
- 常见错误现象:两个事务先后执行
SELECT ... FOR UPDATE查询同一行,第二个事务卡住直到第一个提交——这是正常行为;但如果两个事务都只执行SELECT ... LOCK IN SHARE MODE,则可以并发成功 - 实操建议:读多写少场景下,优先用
LOCK IN SHARE MODE替代FOR UPDATE,减少写冲突;更新前务必确认是否真需要独占锁,避免过度加锁拖慢整体吞吐 - 性能影响:S 锁之间不互斥,开销远小于 X 锁;但大量 S 锁仍会占用锁结构内存,极端情况下触发
Lock wait timeout exceeded
MVCC 不是“不用锁”,而是让读操作尽量避开锁
MVCC(多版本并发控制)让普通 SELECT(不带 FOR UPDATE 或 LOCK IN SHARE MODE)无需加锁,直接读取事务开启时刻的快照版本。但这只对「一致性非锁定读」有效;一旦涉及修改(UPDATE、DELETE)或显式加锁读,InnoDB 仍必须获取 X 锁或 S 锁来保证隔离性。
- 关键区别:
SELECT * FROM user WHERE id=123→ 走 MVCC,无锁;SELECT * FROM user WHERE id=123 FOR UPDATE→ 必须加 X 锁,哪怕数据没变 - 容易踩的坑:误以为“开了 MVCC 就不会锁表”,结果在高并发更新场景下,因索引缺失或临键锁范围过大,导致大量事务排队等待,监控看到
innodb_row_lock_waits持续上涨 - 验证方式:可通过
SELECT * FROM information_schema.INNODB_TRX查看当前活跃事务及其锁等待状态,结合INNODB_LOCKS和INNODB_LOCK_WAITS定位具体阻塞链
索引和锁的关系不是“有索引就安全”,而是“索引质量直接决定锁的粒度和范围”。一个没走索引的 UPDATE 可能锁全表,而一个设计不当的联合索引(比如把低区分度字段放最左)会让临键锁覆盖大片无关数据——这些细节,在压测和慢日志分析里才真正暴露出来。
