mysql 5.7+ 中 update ... join 慢主因是优化器选错驱动表,尤其 on 含函数、无索引字段时退化为嵌套循环全表扫描;应避免函数索引失效、确保 join 字段有匹配索引、用 explain format=tree 查真实驱动顺序、临时表须显式建索引且字段宜窄,过滤条件尽量下推至 on 子句并确认索引存在。

UPDATE 带多表 JOIN 时为什么慢得像卡住
MySQL 5.7+ 虽支持 UPDATE ... JOIN,但优化器常无法正确选择驱动表,尤其当 JOIN 条件含函数、子查询或非索引字段时,会退化成嵌套循环 + 全表扫描。你看到的“执行 10 分钟没反应”,大概率是它在对右表做重复全扫描。
- 避免在
ON条件里用UPPER()、DATE()等函数——这会让索引失效 - 确认所有 JOIN 字段都建了单独索引或联合索引(顺序要和
ON中一致) - 用
EXPLAIN FORMAT=TREE看实际驱动顺序,别信EXPLAIN默认输出的“假驱动表”
临时表不是万能缓存,建错反而更慢
把中间结果塞进 CREATE TEMPORARY TABLE 是常见思路,但很多人忽略两点:临时表默认无索引、且生命周期绑定会话。如果后续 JOIN 频繁靠某个字段查,不建索引就等于白建。
- 建完临时表立刻加索引:
CREATE INDEX idx_tmp_uid ON tmp_user_data (user_id) - 别用
SELECT * INTO #tmp(SQL Server 风格),MySQL 必须显式CREATE TEMPORARY TABLE ... AS SELECT - 临时表字段类型尽量窄——用
TINYINT别用INT,少 3 字节 × 百万行 = 几十 MB 内存省下来
用 INSERT … SELECT + REPLACE 替代 UPDATE JOIN 的真实代价
当更新逻辑涉及聚合、窗口函数或跨库关联时,原生 UPDATE ... JOIN 直接报错或不准。这时改走“先查后写”路径更稳,但要注意唯一约束和事务隔离。
- 目标表必须有主键或唯一键,否则
REPLACE INTO会当成新插入,数据翻倍 -
INSERT INTO t1 SELECT ... FROM t2 JOIN t3比UPDATE t1 JOIN t2 JOIN t3更容易被优化器选对执行计划 - 若需原子性,整个操作包在
START TRANSACTION里,但注意长事务会阻塞其他写入
WHERE 条件里藏了个隐形性能杀手
你以为 UPDATE t1 JOIN t2 ON t1.id = t2.t1_id SET t1.status = 'done' WHERE t2.created_at > '2024-01-01' 很干净?其实 MySQL 可能先 JOIN 所有行,再过滤 WHERE,而不是下推到 t2 上提前剪枝。
- 把可下推的过滤条件尽量挪到
ON子句里(如ON t2.t1_id = t1.id AND t2.created_at > '2024-01-01') - 确认
t2.created_at有索引——否则ON里加了也白加 - 用
SHOW PROFILE对比 “Sending data” 和 “Copying to tmp table” 阶段耗时,定位是计算慢还是 IO 慢
事情说清了就结束。最常被跳过的其实是 EXPLAIN FORMAT=TREE 和临时表建索引这两步——不看执行树,你永远不知道优化器在想什么;不给临时表加索引,等于把硬盘当内存使。










