精准索引是唯一治本解法,其他都是临时止痛;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 / INSERT ON DUPLICATE KEY 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无索引 → 全表扫描 + 每行加锁 → 实际退化为表级竞争 -
INSERT INTO orders (...) ON DUPLICATE KEY UPDATE ...:必须确保ON DUPLICATE KEY依赖的字段(如order_no)有唯一索引,否则查找阶段会锁住整个可能插入的间隙 - 多个唯一索引(如
UNIQUE(email)和UNIQUE(phone))并发冲突时,InnoDB 内部加锁顺序不一致,也可能死锁
UPDATE/DELETE 多行操作没加 ORDER BY → 死锁温床
死锁不是凭空来的。两个事务更新同一组数据,但一个按主键升序加锁,另一个按降序或无序加锁,再加个间隙锁,环形等待立刻成型。
- 所有涉及多行更新的语句,强制加上
ORDER BY id ASC,保证加锁顺序全局一致 - 避免
WHERE status IN (1,2)这类范围条件——它会触发Next-Key Lock(记录锁 + 间隙锁),锁住整个索引区间 - 用
EXPLAIN FORMAT=JSON看used_range,确认是否真的只锁目标行
事务里混了 RPC、sleep、日志 → 锁持有时间被人为拉长
一个事务从BEGIN到COMMIT的每一毫秒,都在持续持有锁。日志打点、发 MQ、调第三方 HTTP 接口、甚至file_put_contents写本地文件——这些都不该出现在事务块里。
- 把通知类逻辑移出事务,用异步任务或最终一致性补偿
- 批量更新拆成小批:比如 10 万条记录,用分页提交,每批控制在 100–500 行
- 避免“先
SELECT再UPDATE”:改成SELECT ... FOR UPDATE WHERE id = ?一步到位,且必须带ORDER BY id - 长事务监控:查
information_schema.INNODB_TRX,重点关注trx_started和trx_state = 'RUNNING'的记录
真正难的不是加索引,而是让所有开发在写 SQL 时下意识检查EXPLAIN,并接受“不能用函数包字段”“不能漏最左前缀”这些硬约束。业务越复杂,越容易在某个角落悄悄绕过规范——那里就是下一次死锁爆发的起点。











