SQL UPDATE 批量更新优化方案

2次阅读

update慢主因是全表扫描或索引失效,需用explain确认索引命中;避免where中函数操作、子查询及NULL值;优先用join或values列表代替子查询;注意事务设置与超时配置。

SQL UPDATE 批量更新优化方案

UPDATE 语句慢得离谱?先看执行计划有没有走索引

没加 WHERE 条件的全表扫描,或者 WHERE 字段没索引,是批量更新变慢最常见原因。mysql/postgresql 都会默默全扫一遍再改,哪怕只改 10 行,也可能锁住上万行。

  • EXPLAIN UPDATE ...(MySQL)或 EXPLAIN ANALYZE UPDATE ...(PostgreSQL)确认是否命中索引
  • WHERE 条件里别对字段做函数操作,比如 WHERE YEAR(created_at) = 2024 会让索引失效,改成 WHERE created_at >= '2024-01-01' AND created_at
  • 复合索引要注意字段顺序:如果 WHERE a = ? AND b = ?,索引应为 (a, b),反过来就可能用不上

一次更新 10 万行,为什么锁表又超时?

大事务 = 长时间持有行锁/表锁 + binlog 增长 + 主从延迟。尤其在 MySQL 的 REPEAtable READ 隔离级别下,大 UPDATE 可能触发间隙锁,连不相关的行都锁住。

  • 拆成小批次更新,比如每次 1000–5000 行,用 WHERE id BETWEEN ? AND ?WHERE id > ? ORDER BY id LIMIT 5000
  • 显式加 for UPDATE 不必要,普通 UPDATE 本身就会加行锁;但若逻辑依赖 select 再 UPDATE,记得用 SELECT ... FOR UPDATE 避免并发覆盖
  • 避免在事务里混用 DDL(如 ALTER TABLE),DDL 会锁整个表,和你的 UPDATE 死锁概率陡增

用 JOIN 更新多张表,MySQL 和 PostgreSQL 写法完全不同

MySQL 支持 UPDATE t1 JOIN t2 ON ... SET t1.x = t2.y,PostgreSQL 必须写成 UPDATE t1 SET x = t2.y FROM t2 WHERE t1.id = t2.t1_id。写错直接报语法错误,不是逻辑错。

  • MySQL 多表 UPDATE 中,SET 只能赋值给目标表字段,不能写 t2.y = ...
  • PostgreSQL 的 FROM 子句里,如果 t2 有重复关联行,UPDATE 会执行多次——结果取决于哪一行最后生效,行为不确定,务必加 DISTINCT ON 或聚合过滤
  • 两者都不支持在 JOIN UPDATE 中用子查询当右值(比如 SET x = (SELECT ...)),会报错或性能极差,提前把子查询结果落临时表更稳

WHERE 条件含子查询?小心 N+1 和全表扫描

UPDATE t SET status = 'done' WHERE id IN (SELECT id FROM temp_list) 看似简洁,实际在 MySQL 5.6 之前会转成嵌套循环,每行都查一次子查询;PostgreSQL 虽优化较好,但子查询无索引照样慢。

  • 把子查询结果先插入临时表,并在该表的关联字段上建索引,再用 JOIN 方式更新
  • 避免 WHERE col IN (SELECT ...) 中的 SELECT 返回 NULL:NULL 会导致整行被跳过(SQL 标准中 x IN (NULL) 永远为 UNKNOWN)
  • 如果子查询来自应用层,优先考虑用 VALUES 列表代替,比如 WHERE id IN (1, 5, 8, 12) —— 这个比子查询快一个数量级,且可控

批量 UPDATE 最容易被忽略的,其实是 autocommit 开关和连接超时设置。应用代码里没关自动提交,每条 UPDATE 都单独事务,速度慢还容易被打断;数据库侧的 wait_timeoutstatement_timeout 设得太短,大批次还没跑完就断连了。这些不在 SQL 里,但决定你能不能跑通。

text=ZqhQzanResources