update未走索引导致全表锁定:where字段无索引或索引因隐式转换、函数包裹失效,innodb全表扫描并逐行加记录锁与间隙锁;可通过show index、explain及避免函数/类型不匹配来排查优化。

UPDATE没走索引直接锁全表
这是最常见也最危险的情况:WHERE条件字段没索引,或者索引因隐式转换、函数包裹而失效,InnoDB被迫全表扫描——每扫一行就加一个记录锁,间隙锁也会自动激活,效果等同于锁整张表。
典型现象是:SHOW PROCESSLIST里大量连接卡在Locked或Waiting for table metadata lock;EXPLAIN显示type: ALL且rows接近表总行数。
- 先用
SHOW INDEX FROM table_name确认目标字段是否有可用索引(注意前缀索引是否覆盖查询长度) - 检查WHERE条件是否触发隐式转换,比如
mark_id是VARCHAR却传入数字123 - 避免
WHERE DATE(created_at) = '2026-06-01'这类写法,改用created_at >= '2026-06-01' AND created_at - 紧急时可用
FORCE INDEX强制走索引,但只是临时手段,长期要修复执行计划
批量UPDATE单次锁行太多
即使WHERE条件走了索引,一次性UPDATE几千上万行仍会持续持有大量X锁,极易引发锁等待甚至死锁。关键不是“有没有索引”,而是“单次事务锁住多少行+多久”。
- 按
OFFSET分页(如LIMIT 500 OFFSET 1000)不行——MySQL仍可能重复扫描前面的行,锁范围不可控 - 必须用主键范围切分:先
SELECT id FROM t WHERE ... ORDER BY id LIMIT 500拿到一批ID,再构造WHERE id IN (1,2,3,...) - 更稳妥的是
WHERE id BETWEEN ? AND ?,起始值取上一批的MAX(id),避免漏或重 - 单批控制在100–500行之间,但别硬套——若更新涉及多表JOIN或大字段,建议缩到100以内
- 每批后显式
COMMIT,并加SLEEP(0.01)缓解CPU和锁争抢
多表UPDATE顺序不一致
两个事务分别以不同顺序更新order和user表,InnoDB加锁就会形成环路。这不是概率问题,是确定性死锁源,占比超四成。
- 所有多表操作必须约定唯一顺序:按表名字母序(
order_item → order → user)或业务主次(订单流必须order → order_item → payment) - 禁止在代码里用
if/else动态拼接UPDATE顺序,例如不能根据type == 'refund'决定先更新user还是order - 每个表的访问都必须走主键或唯一索引,否则
SELECT FOR UPDATE可能升级为间隙锁,扩大冲突面
间隙锁在非唯一索引上意外生效
对非唯一字段(如status、name)做范围查询或等值查询时,InnoDB默认使用Next-Key Lock(记录锁+间隙锁),两个事务可能同时锁定同一间隙,互相阻塞。
-
SELECT ... FOR UPDATE WHERE status = 'pending'若status无索引或只有单列索引,可能锁住整个间隙,而非仅匹配行 - 务必为
SELECT FOR UPDATE的WHERE条件建联合索引,列顺序要匹配谓词,例如(user_id, status, id) - 如果业务允许,可考虑将隔离级别降为
READ COMMITTED(需确认业务一致性容忍度),该级别下不启用间隙锁 - 用
SELECT ... LOCK IN SHARE MODE替代FOR UPDATE时也要小心——它同样会触发间隙锁
EXPLAIN和SHOW ENGINE INNODB STATUS开始,而不是猜逻辑。











