explain无法判断加锁范围,它只显示执行计划;实际锁行为需通过information_schema.innodb_trx和performance_schema.data_locks(mysql 8.0+)查看lock_mode字段确认,如x,rec_not_gap为行锁、table为表锁。

EXPLAIN 能看出全表扫描,但看不出加锁范围
EXPLAIN 只反映查询计划,不体现锁行为。它能告诉你是否走索引(type=ALL、key=NULL、rows接近总行数),但无法告诉你 InnoDB 实际锁了哪些行——哪怕 rows=1000,只要 WHERE 条件没命中索引,InnoDB 就会对这 1000 行逐个加锁,效果等同于锁表。
常见误判:看到 EXPLAIN 显示 rows=5 就以为只锁 5 行,其实若这 5 行来自全表扫描(type=ALL),说明优化器根本没用索引,那锁的就是所有被扫描的行,不是“满足条件的 5 行”,而是“扫描路径上的全部 5 行”——此时已无间隙锁区分,纯记录锁叠加。
- 优先看
type字段:ALL是危险信号,range/ref才可能安全 -
key为空或显示NULL,基本等于没走索引 -
Extra出现Using where且没索引支撑,大概率触发全扫描加锁
真正验证锁范围得查 information_schema.INNODB_TRX + INNODB_LOCKS(MySQL 8.0+)
在事务未提交时执行更新语句后,立刻查当前活跃事务和锁信息,才能确认实际加锁情况。MySQL 8.0 后 INNODB_LOCKS 表已弃用,改用 performance_schema.data_locks。
典型操作流程:
- 会话 A 执行
UPDATE users SET status = 2 WHERE phone LIKE '%138%'(无索引),不提交 - 会话 B 执行
SELECT * FROM performance_schema.data_locks WHERE LOCK_TRX_ID IN (SELECT TRX_ID FROM information_schema.INNODB_TRX) - 观察
LOCK_DATA字段:若出现大量不同主键值(如id=1、id=2、id=3…),说明是多行记录锁;若只有lock_mode=TABLE,则是显式表锁(极少见)
注意:data_locks 中每条记录对应一个锁对象,LOCK_MODE 为 X,REC_NOT_GAP 是行锁,X,GAP 是间隙锁,X,INSERT_INTENTION 是插入意向锁——全表扫描不会产生 GAP 锁,但会密集出现 REC_NOT_GAP。
WHERE 条件里有函数或隐式转换?锁范围直接失控
哪怕字段有索引,只要 WHERE 中对它做了运算或类型强转,索引就失效,InnoDB 只能全表扫——这不是“慢”,是“锁得多”。
-
WHERE YEAR(create_time) = 2024→ 改为WHERE create_time >= '2024-01-01' AND create_time -
WHERE phone = 13812345678(phone 是VARCHAR)→ 改为WHERE phone = '13812345678' -
WHERE name COLLATE utf8mb4_0900_as_cs = 'Alice'→ 检查列默认校对规则是否匹配,避免隐式转换
这类写法在 EXPLAIN 里常表现为 type=ALL 或 key=NULL,但开发者容易忽略,以为“只是慢一点”,实则并发更新时其他事务全被阻塞。
联合索引没覆盖查询条件?锁的行数远超预期
例如 UPDATE orders SET paid = 1 WHERE user_id = 123 AND status = 'unpaid',如果只建了 (user_id) 单列索引,InnoDB 会先通过 user_id 索引找到所有 user_id = 123 的行(可能上千条),再逐行判断 status,过程中对所有扫描到的行加锁——哪怕最终只更新 1 行。
- 应建联合索引
(user_id, status),让索引直接定位目标行 - 若
status值分布极不均匀(如 99% 是 'unpaid'),优化器仍可能放弃索引走全表扫描,需结合FORCE INDEX强制使用 - 加锁范围取决于“索引扫描路径”,不是“WHERE 最终结果集”
最隐蔽的坑在于:你看到 UPDATE 只影响一行,却不知道它背后锁了 500 行——因为那 500 行都在索引扫描路径上,而这个路径由索引结构和查询条件共同决定。











