精准索引是解决高并发行锁问题的根本:WHERE条件未命中索引会导致行锁退化为表级竞争,引发死锁与严重竞争;必须用EXPLAIN验证type非ALL/index,避免隐式转换、函数包裹及最左前缀失效,并确保SELECT FOR UPDATE和INSERT ON DUPLICATE KEY UPDATE依赖的字段有唯一索引。
索引没建对,锁就锁不准;锁不准,死锁和竞争就根本停不下来。 所有高并发下的行锁问题,90% 都能靠精准索引解决,而不是调参数、加重试或降隔离级别。
这是最隐蔽也最致命的锁扩大源头。哪怕你只更新一行,只要 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 < '2026-05-02'
(status, category),但只查 WHERE category = 'A',照样全表扫这两个语句看似原子,实际内部都依赖“快速定位”。一旦定位不准,就会在查找阶段误加大量间隙锁(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 内部加锁顺序不一致,也可能死锁死锁不是凭空来的。两个事务更新同一组数据,但一个按主键升序加锁,另一个按降序或无序加锁,再加个间隙锁,环形等待立刻成型。
ORDER BY id ASC,保证加锁顺序全局一致WHERE status IN (1,2) 这类范围条件——它会触发 Next-Key Lock(记录锁 + 间隙锁),锁住整个索引区间EXPLAIN FORMAT=JSON 看 used_range_optimizer_pruning 和 range_analysis,确认是否真的用了索引范围扫描WHERE id BETWEEN ? AND ? 或 WHERE id IN (1,2,...,500),单批控制在 100–500 行它确实能砍掉间隙锁,让 FOR UPDATE 只锁匹配行、语句执行完就释放不匹配的锁。但前提是——你的查询本身得能准确定位到那几行。如果还是全表扫,READ COMMITTED 下照样锁一堆无关行,只是不锁间隙而已。
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED,避免全局改影响 binlog 复制逻辑真正容易被忽略的点是:索引是否覆盖了「所有查询条件 + 排序 + 更新字段」。很多人建了索引却忘了 ORDER BY 或 SELECT 中的非主键字段,结果还是回表、还是锁扩大。索引不是建了就完事,得用 EXPLAIN 验证执行路径,用 INFORMATION_SCHEMA.INNODB_TRX 查真实持锁时间,才能闭环。