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

如何用PostgreSQL内置方法快速导入百万级列表数据

用PostgreSQL Copy Manager替代saveAll实现百万级数据高效导入

Spring Data JPA的saveAll方法在处理百万级数据时,会因频繁JDBC交互、ORM映射开销导致导入速度极慢。PostgreSQL的Copy Manager基于原生COPY命令,能直接将数据流批量写入数据库,是当前最快的批量导入方式之一,完全适配你的场景。

1. 核心依赖确认

确保项目引入PostgreSQL JDBC驱动(Spring Boot项目通常已自动引入),若未添加,可补充Maven依赖:

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <scope>runtime</scope>
</dependency>

2. 改造代码实现Copy Manager导入

核心思路

将List<Order>转换为PostgreSQL兼容的CSV格式数据流,通过Copy Manager执行COPY FROM STDIN命令直接导入数据库,跳过ORM中间映射开销。

改造后的完整代码:

@Service
public class OrderProcessingService {

    @Autowired
    private DataSource dataSource;

    public void process_data(String hfrcode, List<Order> order) {
        // 准备CSV格式数据流
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
             PrintWriter writer = new PrintWriter(baos);
             Connection conn = dataSource.getConnection()) {

            // 遍历数据生成CSV行,字段顺序必须与数据库表列顺序完全一致
            for (Order data : order) {
                Order order_data = new Order(); // 修正原代码中new Visit()的类型错误
                order_data.setUuid(UUID.randomUUID().toString());
                order_data.setOrderID(data.getOrderID());
                order_data.setOrderDate(data.getOrderDate());
                order_data.setWeight(data.getWeight());
                order_data.setOrderQuantity(data.getOrderQuantity());
                order_data.setOrderLocation(data.getOrderLocation());

                // 生成CSV行,处理特殊字符避免格式错误
                writer.println(String.join(",",
                        escapeCsvField(order_data.getUuid()),
                        escapeCsvField(order_data.getOrderID()),
                        escapeCsvField(order_data.getOrderDate().toString()), // 日期格式需与数据库兼容,可按需用DateTimeFormatter格式化
                        escapeCsvField(String.valueOf(order_data.getWeight())),
                        escapeCsvField(String.valueOf(order_data.getOrderQuantity())),
                        escapeCsvField(order_data.getOrderLocation())
                ));
            }
            writer.flush();
            byte[] csvBytes = baos.toByteArray();

            // 使用Copy Manager执行批量导入
            CopyManager copyManager = new CopyManager((BaseConnection) conn);
            // 替换为你的实际表名和列名,列顺序需与CSV字段顺序一致
            String copySql = "COPY orders (uuid, order_id, order_date, weight, order_quantity, order_location) FROM STDIN WITH CSV DELIMITER ','";
            copyManager.copyIn(copySql, new ByteArrayInputStream(csvBytes));

        } catch (SQLException | IOException e) {
            // 按需处理异常,比如回滚事务、记录错误日志
            throw new RuntimeException("批量导入数据失败", e);
        }
    }

    // 辅助方法:处理CSV字段中的特殊字符,避免格式错位
    private String escapeCsvField(String field) {
        if (field == null) {
            return "";
        }
        // 若字段包含逗号、引号或换行,用双引号包裹并转义内部双引号
        if (field.contains(",") || field.contains("\"") || field.contains("\n") || field.contains("\r")) {
            return "\"" + field.replace("\"", "\"\"") + "\"";
        }
        return field;
    }
}

3. 关键优化提示

  • 字段顺序一致性:CSV字段顺序必须与COPY命令中指定的列顺序完全匹配,否则会导致数据错位。
  • 日期格式兼容:确保order_date的字符串格式与数据库日期类型兼容,可通过DateTimeFormatter统一格式化。
  • 分批处理:若数据量超千万级,建议将List拆分为若干子批次(如每批次10万条)导入,避免内存溢出。
  • 索引与约束:导入前临时禁用表的索引和外键约束,导入完成后重新启用,可大幅减少写入时的索引维护开销。
  • 事务控制:如需原子性,可在获取Connection后关闭自动提交,导入完成手动提交,异常时回滚。

内容的提问来源于stack exchange,提问作者Janeth Jackson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 11:43:32