where条件未走索引会导致行锁退化为表级锁竞争;需用explain检查type是否为all或index,避免隐式类型转换和函数包裹索引字段。

WHERE条件没走索引 → 行锁退化成表级竞争
这是最隐蔽也最致命的锁扩大源头。哪怕你只更新一行,只要WHERE条件没命中索引,InnoDB就会全表扫描,并对每行加意向锁甚至行锁——其他事务一来就排队等这把“本不该存在”的锁。
用EXPLAIN查type字段:出现ALL或index就必须停手优化
- 避免隐式类型转换:
WHERE user_id = '123'(user_id是INT)会失效索引,改成WHERE user_id = 123 - 别用函数包裹索引字段:
WHERE DATE(create_time) = '2026-05-01'→ 改成WHERE create_time >= '2026-05-01' AND create_time - 联合索引注意最左前缀:建了
(status, category),但只查WHERE category = 'A',照样全表扫
SELECT FOR UPDATE锁不准 → 实际退化为表级竞争
SELECT ... FOR UPDATE看似只锁一行,实际依赖“快速定位”。一旦定位不准,就会在查找阶段误加大量间隙锁(Gap Lock),导致插入新记录也被阻塞,甚至引发死锁。
-
SELECT * FROM orders WHERE order_no = 'ORD-123' FOR UPDATE:如果order_no有唯一索引 → 只加记录锁,安全 -
SELECT * FROM orders WHERE user_id = 1001 FOR UPDATE:若user_id无索引 → 全表扫描 + 每行加锁 → 实际退化为表级竞争 - 多个唯一索引(如
UNIQUE(email)和UNIQUE(phone))并发冲突时,InnoDB内部加锁顺序不一致,也可能死锁
INSERT ON DUPLICATE KEY UPDATE依赖字段必须有唯一索引
这个语句不是原子黑盒。它先按ON DUPLICATE KEY字段查找,再决定插入还是更新。查找阶段若没索引,就会锁住整个可能插入的间隙,而非单条记录。
- 必须确保
ON DUPLICATE KEY依赖的字段(如order_no)有唯一索引,否则查找阶段会触发Next-Key Lock,锁住(5,10)这类开区间 - 若业务允许,可用
INSERT IGNORE替代,失败直接跳过,不加任何锁 - 不要在没有主键或唯一约束的表上用该语法,InnoDB会退化为隐式聚簇索引扫描,间隙范围失控
UPDATE/DELETE多行操作必须加ORDER BY保证加锁顺序
死锁不是凭空来的。两个事务更新同一组数据,但一个按主键升序加锁,另一个按降序或无序加锁,再加个间隙锁,环形等待立刻成型。
- 所有涉及多行更新的语句,强制加上
ORDER BY id ASC,保证加锁顺序全局一致 - 避免
WHERE status IN (1,2)这类范围条件——它会触发Next-Key Lock(记录锁 + 间隙锁),锁住整个索引区间 - 用
EXPLAIN FORMAT=JSON看used_range_optimizer_pruning和range_analysis,确认是否真的用了索引
真正麻烦的不是Gap Lock本身,而是它不报错、不提示具体锁区间,只让INSERT卡住或报Lock wait timeout exceeded。索引建错一毫米,锁就偏出一公里。











