慢更新最常见原因是where条件字段未走索引或扫描行数远超预期,应通过explain分析type、rows、key及隐式类型转换等问题,并确保字段类型严格一致。

先看执行计划,别猜索引有没有用上
慢更新最常见原因是 WHERE 条件字段没走索引,或者走了索引但实际扫描行数远超预期。直接跑 EXPLAIN FORMAT=TRADITIONAL 加你的 UPDATE 语句(把 UPDATE 换成 SELECT *,保持 WHERE 和 JOIN 完全一致):
- 重点看
type:如果是ALL或index,基本等于全表扫描 - 看
rows:这个值是 MySQL 预估扫描行数,如果比实际匹配行数高几倍甚至几十倍,说明统计信息过期,运行ANALYZE TABLE table_name - 看
key是否为NULL,或用了错误的索引(比如本该用idx_status_created,却用了PRIMARY)
检查 WHERE 条件里有没有隐式类型转换
字段是 VARCHAR,但 WHERE 里写了数字(比如 WHERE user_id = 123),MySQL 会把整列转成数字做比较,索引失效。常见触发场景:
-
user_id是字符串类型,但应用层传了整型参数 -
phone字段加了索引,但查询写成WHERE phone = 13800138000(没加引号) - 使用函数包装条件字段,例如
WHERE DATE(created_at) = '2026-08-10'—— 即使created_at有索引也用不上
修复方式:确保类型严格一致,改写为 WHERE phone = '13800138000' 或 WHERE created_at >= '2026-08-10' AND created_at 。
确认是否被锁住,尤其是长事务或未提交事务
更新慢不一定是执行慢,很可能是卡在等待锁。查 INFORMATION_SCHEMA.INNODB_TRX 和 INNODB_LOCK_WAITS:
- 运行
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX ORDER BY TRX_STARTED DESC LIMIT 5,看有没有TRX_STATE = 'RUNNING'但TRX_ROWS_LOCKED很大、或TRX_STARTED时间很早的事务 - 如果有长时间未提交的事务(比如忘记
COMMIT或程序异常退出),它持有的锁会让后续UPDATE一直阻塞 - 注意:
AUTOCOMMIT=0的连接下,哪怕只执行一条SELECT,也可能开启隐式事务并长期持有共享锁(取决于隔离级别)
UPDATE 自身逻辑是否触发了额外开销
有些写法会让 MySQL 做远超预期的工作:
- 更新字段包含子查询,比如
UPDATE t SET status = (SELECT max(status) FROM t2 WHERE t2.id = t.ref_id)—— 每行都执行一次子查询,复杂度 O(N×M) - 更新涉及触发器(
BEFORE UPDATE/AFTER UPDATE),尤其触发器里又做了INSERT或远程调用 - 更新了被频繁用于二级索引的字段(比如给
email加了唯一索引),MySQL 要校验唯一性 + 维护索引 B+ 树,开销明显上升 - 批量更新时用了
UPDATE ... WHERE id IN (1,2,3,...10000),但IN列表太长导致解析/优化耗时增加;建议拆成每次 500–1000 行
真正难排查的点往往不在 SQL 写法本身,而在“谁在同时读这张表”——比如一个报表任务正在扫全表 SELECT,用的是 REPEATABLE READ,它会 hold 一堆 gap lock,让后续更新排队等。这种问题不会出现在 EXPLAIN 里,得结合 SHOW ENGINE INNODB STATUS 的 LATEST DETECTED DEADLOCK 和 TRANSACTIONS 部分一起看。











