索引决定mysql行锁范围:无索引或失效时退化为表锁;仅当可命中、可下推的二级索引配合主键索引时才能精准加行锁;常见失效场景包括函数操作、联合索引误用、隐式类型转换。

索引如何影响 MySQL 的行锁范围
没有索引或索引失效时,MySQL 的 UPDATE 或 DELETE 语句会退化为表锁(实际是聚簇索引全扫描 + 记录锁),哪怕只改一行,也可能锁住整个表。只有在使用**可命中、可下推的二级索引 + 主键索引配合**时,InnoDB 才能精准加行锁。
常见踩坑点:
-
WHERE条件中对索引字段用了函数(如WHERE YEAR(create_time) = 2024)→ 索引失效 → 全表扫描 → 锁升级 - 联合索引
(a, b),查询只用WHERE b = ?→ 无法命中索引 → 行锁变表级扫描锁 - 字符串字段未加引号:
WHERE user_id = 123对应VARCHAR类型 → 隐式类型转换 → 索引失效
gap lock 和 next-key lock 在什么场景被触发
只要事务隔离级别是 REPEATABLE READ(默认),且 SQL 走了索引(即使是唯一索引),InnoDB 就可能加 gap lock 或 next-key lock —— 目的是防止幻读,不是为了“锁数据”,而是锁“索引间隙”。
典型表现:
-
SELECT ... FOR UPDATE WHERE id > 100:即使id=105不存在,也会锁住 (100, +∞) 这个间隙 - 唯一索引等值查询(
WHERE id = 105)且记录存在 → 只加记录锁(record lock) - 唯一索引等值查询但记录不存在 → 加 gap lock(锁 (104, 106) 这样的空隙)
- 非唯一索引范围查询(
WHERE status = 1)→ 必然触发 next-key lock(记录锁 + 前面 gap lock)
READ COMMITTED 隔离级别下索引和锁的关系变化
切换到 READ COMMITTED 后,InnoDB 会禁用 gap lock 和 next-key lock(除外键检查和唯一约束检查外),只保留 record lock。这意味着索引依然决定“锁哪几行”,但不再锁间隙。
关键影响:
- 幻读可能复现(比如两次
SELECT之间插入新行) - 并发插入冲突减少:多个事务同时
INSERT不同值到同一索引间隙不再互相阻塞 - 死锁概率下降,但业务逻辑需自行规避幻读问题(例如用
SELECT ... FOR UPDATE+ 应用层校验) - 注意:
UPDATE ... WHERE若走不到索引,哪怕在 RC 级别,仍可能因全表扫描锁大量记录
如何快速判断某条 SQL 实际加了哪些锁
不能只看执行计划,得查 INFORMATION_SCHEMA.INNODB_TRX 和 INFORMATION_SCHEMA.INNODB_LOCKS(8.0+ 已移除后者),改用 performance_schema.data_locks。
实操建议:
- 开启监控:
SET GLOBAL performance_schema = ON;,并确保data_locks和data_lock_waitsconsumer 已启用 - 复现并发操作后,立即查:
SELECT * FROM performance_schema.data_locks;
- 重点关注
LOCK_MODE(如REC_NOT_GAP、GAP、NEXT_KEY)、INDEX_NAME、LOCK_DATA(显示被锁的具体索引值或间隙) - 配合
SHOW ENGINE INNODB STATUS\G查当前等待关系,但输出较乱,适合快速定位阻塞源头
真正难的不是加锁规则本身,而是索引设计与业务查询模式不匹配时,锁行为会偏离直觉——比如一个看似简单的 UPDATE,因为没走索引或走了错误索引,实际锁住了几千行甚至整张表。











