PostgreSQL中用UPDATE...FROM需确保JOIN字段有索引并避免无主键WHERE;SQL Server用UPDATE...FROM或MERGE时须建联合索引、分批(如TOP 5000)并禁用非必要触发器,所有数据库均须控制事务大小、分片提交以防锁表和日志溢出。

UPDATE ... FROM 在 PostgreSQL 和 SQL Server 中怎么写才不锁表?
直接用 UPDATE 配合子查询或 JOIN 更新几十万行,容易触发全表扫描或长事务锁表。PostgreSQL 用 UPDATE ... FROM,SQL Server 用 UPDATE ... FROM 或 MERGE,但必须确保关联字段有索引,否则执行计划会退化成嵌套循环+全表扫描。
实操建议:
- 先在
WHERE条件字段和JOIN字段上建联合索引,例如更新语句按user_id匹配,则确保(user_id)或(id, user_id)有索引 - 避免在
FROM子句里用未索引的视图或复杂 CTE;PostgreSQL 12+ 支持LATERAL,但仅当右侧依赖左侧时才用,别滥用 - SQL Server 中
MERGE有隐式锁升级风险,单次更新超 5000 行建议拆成批次,用TOP (5000)+WHERE循环
MySQL 批量更新几十万行时为什么卡死?INSERT ... ON DUPLICATE KEY UPDATE 能不能替代?
MySQL 的普通 UPDATE 在无主键/唯一键条件下走全表扫描,且 InnoDB 默认事务隔离级别下会加间隙锁,极易阻塞其他读写。用 INSERT ... ON DUPLICATE KEY UPDATE 是可行路径,但前提是目标表有合适的唯一约束(如 UNIQUE(user_id)),否则会报错或行为不可控。
实操建议:
- 确认表结构已有
UNIQUE或PRIMARY KEY覆盖你要匹配的字段,否则先加索引再操作 - 批量插入数据用临时表中转:先
CREATE TEMPORARY TABLE tmp_data (...),LOAD DATA INFILE或批量INSERT进去,再用INSERT INTO t SELECT ... FROM tmp_data ON DUPLICATE KEY UPDATE - 注意
max_allowed_packet和innodb_log_file_size,大批量会导致日志写满或包超限,错误信息类似Packet for query is too large或Lock wait timeout exceeded
Oracle 用 MERGE INTO 更新几十万行,为什么执行计划显示 FULL TABLE SCAN?
MERGE INTO 不等于自动高效,如果 USING 子句的数据源没走索引,或者 ON 条件字段没索引,Oracle 优化器大概率选全表扫描。更麻烦的是,MERGE 默认是单事务,几十万行一提交,undo 表空间和 redolog 压力极大。
实操建议:
-
ON子句里的字段必须有索引,且最好是组合索引前导列,例如ON (t.id = s.id)就要求t(id)有索引 - 把大批次拆成每次 1 万行:用
ROWNUM 或游标分页(WHERE id > :last_id)循环执行 - 考虑用
DBMS_PARALLEL_EXECUTE分块并行,但要注意目标表是否允许 DML 并行(需ALTER TABLE ENABLE PARALLEL DML)
所有数据库通用的避坑点:事务大小、日志增长、回滚段爆掉
几十万行不是数量问题,而是事务持续时间和资源占用问题。一个没控制的 UPDATE 可能占满 undo 表空间、撑爆 pg_xlog / MySQL binlog、触发 Oracle ORA-01555 快照过旧错误。
关键动作要同步做:
- 提前检查:PostgreSQL 查
pg_stat_progress_update,MySQL 查SHOW PROCESSLIST和INFORMATION_SCHEMA.INNODB_TRX,Oracle 查V$TRANSACTION和V$UNDOSTAT - 无论哪种数据库,都别在一个事务里干完——用主键/时间戳/自增 ID 分片,每次更新 5k~10k 行,中间
COMMIT - 更新前禁用非必要触发器(
DISABLE TRIGGER/SET FOREIGN_KEY_CHECKS=0),但记得恢复,否则数据一致性可能出问题
最常被忽略的是日志归档模式下的磁盘空间:一次大更新可能生成数 GB 归档日志,没监控的话半夜就告警了。










