SELECT ... FOR UPDATE 在 WHERE 条件无有效索引时会锁整张表,因全表扫描导致聚簇索引全锁;需用 EXPLAIN 检查执行计划、统一加锁顺序、慎用间隙锁,并优先使用原子操作或唯一约束替代。
SELECT ... FOR UPDATE 会直接锁整张表这不是“可能”,而是 InnoDB 的确定行为:当 SELECT ... FOR UPDATE 的 WHERE 条件无法命中有效索引(比如字段无索引、用了函数如 DATE(create_time)、或发生隐式类型转换),MySQL 就会退化为全表扫描,并对所有扫描过的行加锁——在可重复读(RR)隔离级别下,这往往等价于锁住整个聚簇索引,也就是事实上的表级锁定。
常见错误现象:
• 并发稍高时大量事务卡在 Waiting for table metadata lock 或 Waiting for global read lock
• EXPLAIN FORMAT=JSON 显示 key_locks 字段为空或范围极大
• SHOW ENGINE INNODB 中看到 
lock_mode X locks rec but not gap 变成 lock_mode X locks table
EXPLAIN FORMAT=JSON 检查关键 FOR UPDATE 语句,确认 key 和 key_locks 字段是否符合预期user_id = ? AND status = ? 就不能只依赖 user_id 单列索引SELECT ... FOR UPDATE WHERE name LIKE '%abc' —— 这种前导通配符必然走不了索引死锁不是“运气差”,而是两个事务以相反顺序访问同一组主键。比如事务 A 先 SELECT ... FOR UPDATE WHERE id IN (5, 2)(实际按索引顺序锁了 2→5),事务 B 同时执行 SELECT ... FOR UPDATE WHERE id IN (2, 5) 却因查询优化器路径不同锁了 5→2,就形成 A 等 B 释放 5、B 等 A 释放 2 的循环等待。
使用场景:
• 批量状态更新(如“把一批订单设为已发货”)
• 分页任务队列消费(SELECT ... FOR UPDATE LIMIT 10)
ORDER BY id ASC,哪怕业务逻辑本身不需要排序orders 再锁 inventory,支付回调却反过来——这是环形等待的温床SELECT ... FOR UPDATE SKIP LOCKED 替代 LIMIT 分页取数,它能跳过已被锁的行,天然规避部分顺序冲突在默认的可重复读(RR)下,SELECT ... FOR UPDATE 不仅锁匹配的行,还会锁住“值之间的空隙”。例如 WHERE age BETWEEN 20 AND 30,即使表中没有 age=25 的记录,InnoDB 也会锁住 (20,30) 这个区间,阻止其他事务插入 age=25 的新行——这就是间隙锁,它本意是防幻读,但副作用是大幅提高锁冲突概率。
参数差异:
• READ COMMITTED 隔离级别下,间隙锁被禁用,只锁实际存在的行
• SERIALIZABLE 下则更激进,会将范围查询升级为临键锁(Next-Key Lock),覆盖行+间隙
status),不要单独建索引——它容易引发大面积间隙锁;改用联合索引前置该字段,或改用乐观锁SELECT ... FOR UPDATE 再 INSERT 同一索引范围,比如先查 WHERE status = 0,再插一条 status = 0 的新记录,极易触发间隙锁冲突SELECT ... FOR UPDATE 当“安全保险丝”,它本身就是锁冲突源头很多团队误以为“先查再更”比直接 UPDATE 更可控,结果反而更危险:一次 SELECT ... FOR UPDATE 锁的范围,常常比后续 UPDATE 语句本身需要的还大。尤其是当 SELECT 走的是非唯一索引,而 UPDATE 用的是主键时,前者锁了一片,后者只改一行,白白扩大了竞争面。
性能影响:
• 锁持有时间 = 从 SELECT ... FOR UPDATE 执行完到事务 COMMIT 的整个时长
• 一个 200ms 的计算逻辑放在事务内,就等于让锁多持有了 200ms
INSERT ON DUPLICATE KEY UPDATE 或 UPDATE ... WHERE 直接完成的,就别拆成两步UNIQUE(user_id, order_date))替代 SELECT ... FOR UPDATE,失败走应用层重试,既免锁又快最容易被忽略的一点:锁不是加在 SQL 上,而是加在索引结构上;你写的 SQL 看似简单,但执行计划稍有偏差,锁的粒度就可能从“一行”变成“一万个间隙”。所以别信直觉,每个 FOR UPDATE 都得拿 EXPLAIN 和 INNODB STATUS 对着看。