You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免PL/pgSQL循环?大表数据迁移高性能替代方案

高性能PostgreSQL数据迁移方案(2亿条记录场景)

核心问题分析

你当前的嵌套循环脚本逐行处理1.8亿条记录(3000万订单+1.5亿状态历史),每次循环都执行两次单条INSERT,事务开销极大,导致耗时数小时。批量插入的关键是一次性处理多行数据,并正确关联两个新表的依赖关系(history_info的ID作为order_history的rev字段)。

正确的批量插入脚本

下面的脚本通过CTE(公共表表达式)实现批量插入,同时保证history_info生成的ID能准确对应到order_history的记录:

WITH OSHO_DATA AS (
    -- 先获取所有需要迁移的状态历史数据,添加唯一行标识用于后续关联
    SELECT 
        row_number() OVER () AS row_id,
        osho.order_id,
        osho.new_status,
        osho.modified_date,
        osho.status_changer  -- 旧表中负责修改状态的字段,需确保存在
    FROM order_status_history_old osho
    -- 关联orders_old过滤无效订单(如果允许无对应订单的状态历史,可改为LEFT JOIN)
    JOIN orders_old oo ON osho.order_id = oo.id
),
HI_INSERT AS (
    -- 批量插入history_info,返回生成的rev ID和对应的行标识
    INSERT INTO history_info (id, date, updated_by)
    SELECT 
        nextval('HISTORY_INFO_SEQ'),
        modified_date,
        status_changer
    FROM OSHO_DATA
    ORDER BY row_id  -- 保证插入顺序与OSHO_DATA一致,确保关联准确
    RETURNING id AS rev, row_id
)
-- 批量插入order_history,通过row_id关联HI_INSERT的rev ID
INSERT INTO order_history (id, rev, rev_type, status)
SELECT 
    od.order_id::BIGINT,
    hi.rev,
    1,
    GET_STATUS_CODE(od.new_status)  -- 替换为你的状态转换函数
FROM OSHO_DATA od
JOIN HI_INSERT hi ON od.row_id = hi.row_id;

性能优化措施

1. 临时禁用约束与索引

迁移前关闭目标表的非必要约束和索引,大幅提升插入速度:

  • 禁用触发器:ALTER TABLE history_info DISABLE TRIGGER ALL; ALTER TABLE order_history DISABLE TRIGGER ALL;
  • 禁用外键约束:ALTER TABLE order_history DISABLE CONSTRAINT <外键名称>;(如果有)
  • 删除目标表索引:DROP INDEX <索引名称>;(迁移完成后重建)

2. 分批次处理超大表

如果一次性处理1.5亿行内存不足,可按订单ID分批次处理:

DO $$
DECLARE
    batch_size INT := 100000;  -- 每批次处理10万条状态历史,可根据服务器配置调整
    max_order_id INT;
    current_min_id INT := (SELECT MIN(id) FROM orders_old);
BEGIN
    SELECT MAX(id) INTO max_order_id FROM orders_old;
    
    WHILE current_min_id <= max_order_id LOOP
        WITH OSHO_DATA AS (
            SELECT 
                row_number() OVER () AS row_id,
                osho.order_id,
                osho.new_status,
                osho.modified_date,
                osho.status_changer
            FROM order_status_history_old osho
            JOIN orders_old oo ON osho.order_id = oo.id
            WHERE oo.id BETWEEN current_min_id AND current_min_id + batch_size - 1
        ),
        HI_INSERT AS (
            INSERT INTO history_info (id, date, updated_by)
            SELECT nextval('HISTORY_INFO_SEQ'), modified_date, status_changer
            FROM OSHO_DATA
            ORDER BY row_id
            RETURNING id AS rev, row_id
        )
        INSERT INTO order_history (id, rev, rev_type, status)
        SELECT od.order_id::BIGINT, hi.rev, 1, GET_STATUS_CODE(od.new_status)
        FROM OSHO_DATA od
        JOIN HI_INSERT hi ON od.row_id = hi.row_id;
        
        current_min_id := current_min_id + batch_size;
        COMMIT;  -- 每批次提交事务,避免事务过大导致内存溢出
    END LOOP;
END $$;

3. 调整PostgreSQL配置(临时)

修改postgresql.conf后重启服务(迁移完成后恢复原值):

  • maintenance_work_mem = 64MB → 改为512MB(提升排序和索引重建性能)
  • work_mem = 4MB → 改为32MB(提升批量操作的内存分配)
  • max_wal_size = 1GB → 改为4GB(减少检查点频率,提升写入速度)
  • fsync = on → 改为off(仅迁移期间使用,风险:服务器断电可能丢失数据)

4. 精简查询字段

避免SELECT *,只查询需要的字段:

  • 原循环脚本中SELECT * FROM orders_old → 改为SELECT id FROM orders_old
  • SELECT * FROM order_status_history_old → 改为SELECT order_id, modified_date, status_changer, new_status FROM order_status_history_old

数据一致性验证

迁移完成后,务必验证数据正确性:

  1. 检查记录数匹配:
    -- 旧状态历史记录数应等于新表的记录数
    SELECT COUNT(*) FROM order_status_history_old;
    SELECT COUNT(*) FROM history_info;
    SELECT COUNT(*) FROM order_history;
    
  2. 抽样验证关联:
    -- 随机选一个订单,检查新旧表数据是否对应
    WITH sample_order AS (SELECT id FROM orders_old ORDER BY RANDOM() LIMIT 1)
    SELECT 
        osho.modified_date, osho.new_status,
        hi.date, oh.status, oh.rev
    FROM order_status_history_old osho
    JOIN sample_order so ON osho.order_id = so.id
    JOIN order_history oh ON oh.id = so.id
    JOIN history_info hi ON hi.id = oh.rev;
    

恢复约束与索引

迁移完成后,恢复目标表的约束和索引:

  • 启用触发器:ALTER TABLE history_info ENABLE TRIGGER ALL; ALTER TABLE order_history ENABLE TRIGGER ALL;
  • 启用外键约束:ALTER TABLE order_history ENABLE CONSTRAINT <外键名称>;
  • 重建索引:CREATE INDEX <索引名称> ON <表名>(<字段>);

内容的提问来源于stack exchange,提问作者Oguzhan Cevik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 14:15:27