freeze使copy跳过事务可见性标记,直接冻结元组以避免后续vacuum,但仅适用于无并发写入的干净场景;它减少wal体积20%–40%,却增加崩溃恢复负担,且不加速约束检查或替代analyze。

FREEZE 在 COPY 中到底干了什么
FREEZE 是 PostgreSQL COPY 命令的一个可选参数,它让数据库跳过对新插入元组的事务可见性标记(即不设置 xmin 和 xmax),直接标记为“已冻结”,相当于告诉系统:“这批数据从一开始就是对所有事务可见的,且永远不会被 VACUUM 清理”。
这能省掉后续 VACUUM 的扫描负担,但前提是:数据必须来自一个**干净、无并发写入的上下文**——比如一次性导入历史快照。如果表正在被其他事务更新,FREEZE 会导致可见性混乱,查询结果可能错乱。
- 只在目标表完全空闲(无 UPDATE/DELETE/INSERT 并发)时启用
FREEZE -
FREEZE不影响索引构建,但会绕过事务日志中部分元数据记录,降低 WAL 体积 - 若配合
DISABLE TRIGGER和SET LOCAL synchronous_commit = 'off',效果更明显,但崩溃风险上升
COPY 事务边界怎么设才不拖慢批量插入
COPY 本身不是事务语句,它的事务行为完全由外层事务控制。很多人误以为单次 COPY 就是一次原子操作,其实不然:只要没提交,所有插入都锁在同一个事务里,长事务会阻塞 VACUUM、膨胀 pg_locks、拖慢其他查询。
真正影响性能的是“一次事务里塞多少行”——太小(如每 100 行一提交)导致 WAL 刷盘频繁;太大(如 100 万行一提交)则内存占用高、错误回滚代价大、锁表时间长。
- 推荐按 10k–100k 行分批,在客户端循环执行
COPY+COMMIT - 避免在大事务中混用
UPDATE或DELETE,否则COPY的行会受其他操作的事务 ID 影响,无法安全用FREEZE - 使用
psql时,\set ON_ERROR_STOP on配合BEGIN; COPY ... ; COMMIT;手动控制更可靠
FREEZE 和普通批量 INSERT 的 WAL 与恢复差异
开启 FREEZE 的 COPY 生成的 WAL 更少,因为跳过了 tuple header 初始化和 visibility map 更新。但这不是免费的:它把一部分工作转移到了恢复阶段——PostgreSQL 在 crash recovery 时必须重新校验这些“被跳过的可见性”,可能延长启动时间。
而普通 INSERT(哪怕用 INSERT ... VALUES (...), (...) 多值语法)仍走完整事务路径,WAL 更大,但恢复逻辑标准、可预测。
-
COPY ... FREEZE的 WAL 日志量通常比等量INSERT少 20%–40%,取决于列数和数据长度 - 主从复制中,
FREEZE不影响逻辑复制(它不复制 freeze 标记),但物理复制节点恢复时需额外处理 - 如果启用了
log_statement = 'all',FREEZE不会出现在日志里,容易让人误判实际执行路径
为什么有时加了 FREEZE 反而变慢
最常见原因是表上有唯一索引或外键约束。PostgreSQL 在 COPY ... FREEZE 前仍要逐行检查约束,而 FREEZE 本身不加速这个过程;更糟的是,如果索引页分裂频繁,FREEZE 还可能因减少缓冲区刷盘节奏,加剧 page contention。
另一个隐形坑是统计信息滞后:COPY 后不会自动触发 ANALYZE,优化器仍按旧行数估算执行计划,后续查询可能选错索引或 join 方式。
- 有唯一约束时,先
SET CONSTRAINTS ALL DEFERRED再COPY,最后SET CONSTRAINTS ALL IMMEDIATE触发批量校验 -
COPY完立即跑ANALYZE table_name,别依赖 autovacuum 的延迟响应 - 监控
pg_stat_progress_copy视图,确认是否真在用FREEZE路径(freeze字段为 t)
FREEZE 不是银弹,它把一部分运行时开销挪到了导入前的准备和导入后的校验上。真正省时间的地方,往往藏在你关掉什么、推迟什么、以及有没有漏掉那条 ANALYZE。











