UPDATE中函数导致慢的主因是WHERE条件对字段用函数(如UPPER())使索引失效,引发全表扫描;应改写为字段=值、建计算列索引或预计算到临时表再JOIN更新。

UPDATE 里用函数导致慢,先看执行计划有没有全表扫描
带 UPPER()、CONCAT()、DATE_FORMAT() 这类函数的 UPDATE,MySQL/PostgreSQL 很可能放弃索引,走全表扫描——尤其当函数作用在 WHERE 条件字段上时。比如 WHERE UPPER(name) = 'JOHN',哪怕 name 有索引也没用。
- 用
EXPLAIN UPDATE ...(MySQL 8.0.19+ 支持)或把 UPDATE 改成 SELECT 再加EXPLAIN看type是不是ALL - 函数尽量挪到等号右边:把
UPPER(name) = 'JOHN'换成name = 'john'(配合大小写敏感校对集) - 实在要函数过滤,考虑生成持久化计算列 + 索引:PostgreSQL 用
GENERATED ALWAYS AS (UPPER(name)) STORED,MySQL 5.7+ 用GENERATED ALWAYS AS (UPPER(name)) STORED并建索引
批量更新前先预计算,别在 SQL 里反复调函数
比如要给 10 万行更新 status_code,规则是根据 created_at 和 region 查配置表算出值——如果写成 UPDATE orders SET status_code = (SELECT code FROM rules WHERE ...),子查询会执行 10 万次。
- 先把结果预计算到临时表:
CREATE TEMPORARY TABLE tmp_update AS SELECT id, calc_status_code(created_at, region) AS new_code FROM orders WHERE ... - 再用 JOIN 更新:
UPDATE orders o JOIN tmp_update t ON o.id = t.id SET o.status_code = t.new_code - 避免在 UPDATE 的 SET 或 WHERE 里调用标量函数(尤其是自定义函数),它们无法向量化,且 MySQL 不缓存函数返回值
临时列不是万能解法,注意存储开销和事务一致性
有人喜欢加个 tmp_calc_value 列,先 UPDATE 填值,再用它驱动后续逻辑。这看似简单,但容易埋坑。
- 临时列必须加索引才能加速 JOIN,否则又回到全表扫描
- 如果更新中途失败,临时列会残留脏数据;建议用
DROP TEMPORARY TABLE或显式ALTER TABLE ... DROP COLUMN清理 - PostgreSQL 中临时列若用
GENERATED,不能直接 UPDATE;MySQL 的 STORED 列不允许在 UPDATE 的 SET 子句中被赋值 - 大表加列会锁表(MySQL 5.6+ ALGORITHM=INPLACE 可缓解,但仍有元数据锁)
用 WITH 语句替代嵌套子查询(PostgreSQL / MySQL 8.0+)
老写法:UPDATE t1 SET x = (SELECT y FROM t2 WHERE t2.id = t1.ref_id AND t2.flag = 'A'),性能差还难读。
- 改用 CTE 预聚合一次:
WITH calc AS ( SELECT ref_id, MAX(y) AS new_y FROM t2 WHERE flag = 'A' GROUP BY ref_id ) UPDATE t1 SET x = calc.new_y FROM calc WHERE t1.ref_id = calc.ref_id;
- MySQL 8.0+ 不支持 UPDATE + WITH 直接 JOIN,得套一层:
UPDATE t1 JOIN (SELECT ...) calc ON ... SET ... - CTE 在 PostgreSQL 中可物化(取决于
ENABLE/MATERIALIZED设置),但 MySQL 的 CTE 默认不物化,重复引用仍会重算
真正卡住的往往不是语法怎么写,而是没意识到函数调用位置决定了执行次数——一次还是 N 次。临时列省事,但多一次磁盘写、一次锁、一次清理责任。预计算这步跳不过,只是换地方做而已。

















