大表update卡死主因是where未走索引导致全表扫描及行锁升级为表锁或长事务阻塞;应确保where使用覆盖索引、单事务≤5000行、用主键游标分片更新、禁用order by rand(),并上线前在从库验证、模拟锁、确认row格式与autocommit开启。

大表 UPDATE 为什么卡死或锁表
直接 UPDATE 百万级以上表,大概率触发全表扫描 + 行锁升级为表锁(尤其 MyISAM)或长事务阻塞(InnoDB)。MySQL 默认在事务中对所有匹配行加 next-key lock,哪怕只改 10 行,若 WHERE 条件没走索引,照样扫全表、锁全表。
常见错误现象:SHOW PROCESSLIST 看到状态长期卡在 Updating;其他查询被堵住,SELECT ... FOR UPDATE 超时;监控显示 Innodb_row_lock_time_avg 突增。
- WHERE 必须走索引,且最好是覆盖索引(避免回表放大锁范围)
- 避免在大事务里批量更新,单次事务修改行数控制在 5000 行以内
- 不要用
ORDER BY RAND()或子查询做条件——会强制临时表+全表扫描
用 LIMIT 分片更新的实操要点
分片本质是把“一次大更新”拆成多个小事务,降低单次锁持有时间与日志写入压力。但 LIMIT 在 UPDATE 中不能直接用偏移量(如 LIMIT 10000, 5000),因为 MySQL 不支持带 OFFSET 的 UPDATE 语法。
正确做法是用主键/唯一递增字段做游标分页:
UPDATE orders SET status = 'shipped' WHERE id > 100000 AND id
- 必须用闭区间或半开区间(推荐
> last_id AND ),避免漏数据或重复更新 - batch_size 建议 1000–5000,太大仍可能锁太久;太小则网络和事务开销占比高
- 每次执行后记录本次最大
id值,作为下一批起点(不是靠SELECT COUNT算总页数) - WHERE 中保留业务过滤条件(如
status = 'pending'),否则可能更新到已变更的行
UPDATE 没走索引?立刻查执行计划
分片再规范,如果 WHERE 条件不走索引,照样全表扫描。别信“我加了索引”,要亲眼确认。
用 EXPLAIN FORMAT=TREE(MySQL 8.0+)或 EXPLAIN 看实际执行路径:
EXPLAIN SELECT * FROM users WHERE create_time > '2023-01-01' AND deleted = 0;
- 关注
type字段:要是ALL或index,基本就是全表/全索引扫描 -
key为空或不是你预期的索引名,说明没命中 - 复合索引顺序很重要:
(deleted, create_time)可用,(create_time, deleted)对deleted = 0单独条件可能失效 - 隐式类型转换会让索引失效,比如
user_id是BIGINT,却传字符串'123'
线上执行前必须做的三件事
再稳妥的方案,上线前不验证等于裸奔。
- 在从库或影子库上跑一遍完整分片逻辑,观察慢查询日志和
innodb_rows_updated计数是否符合预期 - 用
SELECT ... FOR UPDATE加相同 WHERE 条件模拟锁行为,看是否真只锁目标行(SELECT * FROM information_schema.INNODB_TRX查当前事务锁) - 确认 binlog 格式是
ROW(binlog_format = ROW),语句模式下大更新会写巨量日志,主从延迟爆炸
最常被忽略的是:没检查 autocommit 是否开启。脚本里如果手动 BEGIN 却忘了 COMMIT,一个分片卡住,后面全堵死。










