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)有唯一索引,否则查找阶段会锁住整个可能插入的间隙
事务里混了非数据库操作,锁就持得太久
一个事务从BEGIN到COMMIT的每一毫秒,都在持续持有锁。日志打点、发 MQ、调第三方 HTTP 接口、甚至file_put_contents写本地文件——这些都不该出现在事务块里。
- 把通知类逻辑移出事务,用异步任务或最终一致性补偿
- 批量更新拆成小批:比如 10 万条记录,用
UPDATE ... WHERE id IN (1,2,...,500)分页提交,每批控制在 100–500 行 - 避免“先
SELECT再UPDATE”:改成SELECT ... FOR UPDATE WHERE id = ?一步到位,且必须带ORDER BY id ASC防死锁
隔离级别和自增锁模式选错,锁就锁得不稳
默认的REPEATABLE READ启用间隙锁和临键锁,哪怕你只更新一行,也可能锁住前后空隙;而innodb_autoinc_lock_mode = 1在批量插入时仍持表级自增锁至语句结束。
- 确认业务是否容忍“不可重复读”:多数场景可接受,改用
READ COMMITTED后FOR UPDATE只锁匹配行,不锁间隙 -
innodb_autoinc_lock_mode = 2只对简单INSERT(如INSERT INTO t VALUES ())彻底跳过AUTO_INC锁;INSERT SELECT、混合模式插入仍会退化 - 开
mode = 2必须同步检查binlog_format = 'ROW',否则主从不一致
真正卡住你的往往不是并发量本身,而是那一行没走索引的UPDATE、事务里那一次没意识到的HTTP调用、或者那个被当成业务序号使用的自增ID。这些细节不抠清楚,调参数只是把报错从50秒提前到5秒而已。


















