where条件没走索引时,update等效锁全表;需用explain确认type非all、key非null,避免隐式转换与函数操作,优先基于主键或唯一索引更新,并控制事务时长。

WHERE条件没走索引,UPDATE就等于锁全表
库存扣减语句如 UPDATE product_stock SET stock = stock - 1 WHERE product_id = 10086 AND stock > 0,表面看只改一行,但若 product_id 列没建索引,或已有索引但被隐式转换/函数破坏(比如写成 WHERE product_id = '10086'),InnoDB 就无法定位目标行,只能全表扫描——每扫一条聚簇索引记录,就加一个行锁。在 REPEATABLE READ 下还可能触发间隙锁,实际效果和锁表无异。
常见错误现象:SHOW ENGINE INNODB STATUS 显示锁了上千行;并发稍高时 SELECT ... FOR UPDATE 卡住几秒甚至几十秒;慢查询日志里 rows_examined 动辄上万。
- 用
EXPLAIN确认type是const或ref,且key字段非NULL - 避免隐式类型转换:
product_id是INT,就别传字符串 - 别在索引列上套函数:
WHERE DATE(update_time) = ...→ 改成update_time BETWEEN ... AND ... - 联合索引要守最左前缀:
INDEX(product_id, status)能用于WHERE product_id = ? AND status = ?,但不能用于WHERE status = ?
SELECT FOR UPDATE不是万能锁,它只锁WHERE能精确命中的行
SELECT ... FOR UPDATE 本身不决定锁多少,真正起作用的是它的 WHERE 条件是否走索引、索引是否唯一、是否覆盖查询字段。很多人以为加了 FOR UPDATE 就安全,结果锁了一堆无关行。
使用场景集中在秒杀、订单状态流转、余额校验等强一致性读写混合操作,但必须满足:查询条件能命中主键或唯一索引,否则锁范围会失控。
- 优先用主键查:
SELECT * FROM product_stock WHERE id = 12345 FOR UPDATE只锁 1 行 - 避免用低基数字段(如
status)做唯一性不足的条件:WHERE status = 'pending'可能锁几百行 - 如果必须范围查询,加上
ORDER BY id LIMIT 1并确保排序字段在索引中,防止优化器选错执行计划 - 只是校验后更新?先用
SELECT ... LOCK IN SHARE MODE读取再判断,比直接FOR UPDATE更轻量(但需承担幻读风险)
热点行更新时,行锁反而成瓶颈
所有请求都争抢同一行(比如 product_id = 10086 的库存记录),哪怕用了行锁,也得串行执行。InnoDB 的聚簇索引+自增主键让这行物理位置固定,无法靠数据分布分散压力。事务提交慢 → 锁持有时间长 → 排队请求堆积 → innodb_lock_wait_timeout 被触发 → 大量重试雪崩。
这不是索引问题,是架构级热点。单靠优化 SQL 和索引解决不了。
- 应用层前置过滤:用 Redis 原子操作(
DECRBY)做库存预扣减,失败直接返回,不打数据库 - 库存分片:把单行拆成多行,例如
goods_stock_shard(goods_id, shard_id, stock),扣减时随机选shard_id更新 - 批量处理:别循环单条
UPDATE,改成UPDATE ... WHERE id IN (?, ?, ?)或用INSERT ... ON DUPLICATE KEY UPDATE - 调小
innodb_lock_wait_timeout到 3~5 秒(会话级设置),配合指数退避重试,别让它卡满默认 50 秒再一起炸
READ COMMITTED 隔离级别能关掉间隙锁,但别乱设
REPEATABLE READ 下,UPDATE product_stock SET stock = stock - 1 WHERE product_id > 10000 这种范围条件,InnoDB 会加临键锁(Next-Key Lock),锁住 product_id 索引上所有匹配值及其间隙,导致插入新商品也被阻塞。而 READ COMMITTED 只锁实际命中的行,不加间隙锁,锁冲突大幅下降。
但它不是开关一开就万事大吉。binlog 必须是 ROW 格式,否则主从数据会不一致;业务也得接受不可重复读——同一事务内两次 SELECT 可能拿到不同结果。
- 必须全局设置:
SET GLOBAL tx_isolation = 'READ-COMMITTED',会话级设置无效 - 检查 binlog 格式:
SHOW VARIABLES LIKE 'binlog_format',不是ROW就得改配置重启 - 确认应用能容忍“不可重复读”:比如订单页刷新看到库存变了,属于正常行为
- 别指望它解决长事务、索引失效、热点行等问题——它只管间隙锁这一块
锁竞争问题从来不是单点优化能根治的。索引没走对,再好的隔离级别也救不了;隔离级别调对了,热点行还是卡死;分片做完了,慢查询还在拖后腿。每个环节都得实打实验证,尤其 EXPLAIN 和 SHOW ENGINE INNODB STATUS 要成为日常动作,而不是等报警才翻日志。











