update未走索引会导致innodb退化为全表逐行加锁,等效锁整张表;应通过explain确保type为const/eq_ref/ref且rows≤1,避免在索引列上使用函数。

UPDATE没走索引导致锁全表
这是最常见也最容易被忽略的锁争用源头。InnoDB默认行级锁,但WHERE条件没命中索引时,会退化为逐行加锁——实际效果等同于锁整张表,尤其在大表上,QPS瞬间归零。
实操建议:
- 对每个高频
UPDATE语句执行EXPLAIN,确认type是const、eq_ref或ref,且rows≤ 1;若出现ALL或index,立刻补索引 - 避免在索引列上用函数:
WHERE DATE(created_at) = '2026-09-30'→ 改成WHERE created_at >= '2026-09-30' AND created_at - 检查隐式类型转换:比如
user_id是BIGINT,但传入字符串'123',会导致索引失效
事务里混了HTTP调用或日志写入
锁在COMMIT才释放,而事务中一次curl或file_put_contents可能耗时几百毫秒——这期间所有想改同一行的请求全卡住,连接池满、超时、P99飙升。
实操建议:
-
BEGIN和COMMIT之间只留必要SQL,把RPC、JSON序列化、printf日志、文件写入全部挪到事务外 - 批量更新必须分页:用
UPDATE t SET state = 2 WHERE id BETWEEN ? AND ?,单次控制在100–500行,及时COMMIT - 别用“查-改-更”模式:
SELECT FOR UPDATE后做业务逻辑再UPDATE,锁持有时间不可控;优先改用UPDATE t SET qty = qty - 1 WHERE id = ? AND version = ?原子更新
高并发更新单行引发热点锁
像UPDATE stock SET qty = qty - 1 WHERE product_id = 123这种语句,所有请求都挤在product_id = 123这一行X锁上,本质是单点瓶颈,索引再好也没用。
实操建议:
- 建表加
shard_id字段,主键设为(product_id, shard_id) - 初始化均分:1000件库存分10槽,每槽100件;扣减时随机选槽:
shard_id = FLOOR(RAND() * 10) - 更新语句必须带
shard_id:UPDATE stock SET qty = qty - 1 WHERE product_id = 123 AND shard_id = 7,并用EXPLAIN确认key显示PRIMARY
隔离级别还在用REPEATABLE READ
默认RR级别启用间隙锁(Gap Lock),即使你只更新一行,也可能锁住(3,5)、(5,10)这些“空隙”,导致插入/更新附近ID的事务被无辜阻塞。
实操建议:
- 确认业务能接受“不可重复读”,就设为
READ COMMITTED:SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED - RC下普通
SELECT不加锁(快照读),UPDATE/DELETE只锁命中的行,不加间隙锁 - 慎用
SELECT FOR UPDATE:90%场景可用原子UPDATE替代,减少锁持有时间
锁优化不是调参,是把数据库从“业务执行器”还原成“数据持久层”。真正扛住流量的从来不是MySQL单点——Redis预扣、队列限速、前端防重,这些才是第一道防线。











