最稳方式是用 EXISTS + SELECT ... INTO OUTFILE 直接拼 UPDATE 语句,避免应用层组装;需确保关联字段有索引、显式处理 NULL、用 QUOTE() 转义,并验证执行计划为 eq_ref/ref。

用 EXISTS + SELECT ... INTO OUTFILE 生成增量 UPDATE 语句最稳
直接拼 UPDATE 语句而不是走应用层组装,能避免内存溢出和网络传输瓶颈。核心思路是:把「目标表中存在、但源表中字段值不同」的记录找出来,再用字符串拼接生成对应 UPDATE 语句。别用 JOIN 做大表对比——MySQL 在 JOIN 时容易触发临时表和文件排序,500 万行以上就明显卡顿。
-
EXISTS比IN更安全:不惧NULL值,执行计划通常走主键/索引查找 - 必须确保两张表的关联字段(如
id)都有索引,否则EXISTS子查询会全表扫描 - 字段值对比要显式排除
NULL:写成(t1.col != t2.col) OR (t1.col IS NULL) != (t2.col IS NULL),不能只用!= - 生成语句建议用
SELECT CONCAT('UPDATE tbl SET col1=', QUOTE(t2.col1), ', col2=', QUOTE(t2.col2), ' WHERE id=', t2.id, ';') ...,QUOTE()自动处理引号和转义
MySQL 8.0+ 可用 JSON_OBJECT + JSON_CONTAINS 快速定位变更字段
当表字段多(>20 列)、但每次只改其中几列时,硬写所有字段的 != 判断既难维护又低效。这时可以把整行转成 JSON 对比,只对真正变化的字段生成 SET 子句。
- 先用
JSON_OBJECT('col1', t1.col1, 'col2', t1.col2, ...)分别构造源表和目标表的 JSON 行 - 用
JSON_CONTAINS或JSON_EXTRACT配合!=找出差异项,再用CONCAT动态拼SET部分 - 注意
JSON_OBJECT会忽略NULL键,如果业务要求保留NULL字段,得提前用IFNULL(col, '__NULL__')占位 - 该方法在 MySQL 5.7 不可用;8.0.1+ 支持,但 JSON 构造本身有开销,单行超 10KB 时性能下降明显
导出语句到文件时,INTO OUTFILE 比 SELECT ... LIMIT 分页更可靠
想把百万级 UPDATE 语句存成 SQL 文件再执行?别用应用层循环查 LIMIT —— offset 越大越慢,且容易漏数据。MySQL 原生 INTO OUTFILE 是原子性写入,不经过客户端缓冲。
- 必须确保 MySQL 用户有
FILE权限,且secure_file_priv允许写入目标路径(查SHOW VARIABLES LIKE 'secure_file_priv';) - 语句结尾要显式加换行:
CONCAT(..., ';', '\n'),否则所有语句挤在一行 - 不要用
INTO OUTFILE写到 NFS 或网络盘——写入失败不会报错,只会静默截断 - 生成后用
head -n 5 xxx.sql和tail -n 5 xxx.sql快速核对首尾格式是否合法
WHERE 条件里漏掉 AND t1.id = t2.id 是最常踩的坑
写完 EXISTS 子查询,下意识以为外层 t1 和子查询里的 t2 是自动按主键关联的——其实不是。如果没写明确等值条件,子查询会变成笛卡尔积式校验,10 万 × 10 万 = 100 亿次比较,连 EXPLAIN 都跑不完。
- 务必检查执行计划:
type列要是eq_ref或ref,绝不能是ALL或index - 如果关联字段名不一致(比如源表叫
user_id,目标表叫id),要在子查询里显式写t2.user_id = t1.id - 测试阶段加
LIMIT 10看输出是否符合预期,但上线前必须删掉——INTO OUTFILE不支持LIMIT
真正麻烦的不是语法怎么写,而是确认「哪些行确实该更新」——业务上是否允许并发修改、时间戳字段是否参与比对、空字符串和 NULL 是否算相同值,这些逻辑一旦错,生成的 SQL 就是批量破坏数据的工具。

















