【问题标题】:How to insert 100,000 parent rows each with 200 child rows super fast?如何超快速地插入 100,000 个父行和 200 个子行?
【发布时间】:2019-03-29 13:53:12
【问题描述】:

我有一个名为 OrderEvent 的父实体和一个名为 PreCondition 的子实体。一个 OrderEvent 可以有多个前提条件(>= 200)。我需要保存 100000 OrderEvent + 100000 * 200 PreCondition。我使用 Repository.save(list Of OrderEvents) 并为每 1000 条记录保存到数据库中。插入 1000 个 OrderEvent 大约需要 30 秒。

保存所有 100000 个 OrderEvent 需要将近一个小时。

有没有办法把时间降到 2 分钟以下?

尝试了存储库的保存实体方法

    public  void parseOrder(String path, String collectionName) throws ParseException {
        BufferedReader reader;
        Connection conn = (Connection) em.unwrap(java.sql.Connection.class);
        System.out.println(conn);
        try {
            reader = new BufferedReader(new FileReader(
                    path));
            String line = reader.readLine();

            String jobNumber =  line.substring(0, 7).trim();
            String recordType =  line.substring(7, 9).trim();
            Integer len = line.length();
            preId = 0L;
            postId = 0L;
            eventId = 0L;

            OrderEvent orderEvent = this.paraseHeader(line,len,jobNumber,collectionName);
            Integer count = 1;
            Integer batch = 0;
            long startTime = System.nanoTime();

            List<OrderEvent> list = new ArrayList<OrderEvent>();
            while (line != null) {
                line = reader.readLine();
                if (line == null) {
                    continue;
                }
                jobNumber =  line.substring(0, 7).trim();
                recordType =  line.substring(7, 9).trim();
                len = line.length();

                if (recordType.equals("0H")) { 

                    count++;
                    batch++;
                    if (batch.equals(1000)) {
                        orderRepository.save(list);
                        list.clear();
                        long estimatedTime = System.nanoTime() - startTime;
                        System.out.println("Processed " +  batch + " records in " +  estimatedTime / 1_000_000_000.  +  " second(s).");

                        batch = 0;
                        startTime = System.nanoTime();
                    }


                    list.add(orderEvent);
                    //orderRepository.saveAndFlush(orderEvent);
                    orderEvent = this.paraseHeader(line,len,jobNumber,collectionName);

                } else if (recordType.equals("2F")) { 
                    this.paraseFeature(line,len,jobNumber,orderEvent);
                }
            }
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private  OrderEvent paraseHeader (String line,Integer len,String jobNumber,String collectionName) throws ParseException {

            String model = line.substring(9, 16).trim();
            String processDate =  line.substring(len-11,len-3).trim();
            String formattedProcessDate =  processDate.substring(0,4) + "-" + 
                    processDate.substring(4,6) +"-" + processDate.substring(6,8) + " 00:00:00";

            //eventId++;

            OrderEvent orderEvent = new OrderEvent(jobNumber,UUID.randomUUID().toString(),collectionName,
                    formatter.parse(formattedProcessDate));

        //  preId++;
            //postId++;
            orderEvent.fillPrecondition("Model", "Stimulus", "OP_EQ", model);
            orderEvent.fillPostcondition("Add_Fact","Coded","Response","True");


            return orderEvent;
    }
    private  void paraseFeature (String line,Integer len, String jobNumber, OrderEvent orderEvent) {

    //  preId++;
        String feature = line.substring(len-7,len).trim();
        orderEvent.fillPrecondition("Feature", "Stimulus", "OP_EQ", feature);
    }

【问题讨论】:

  • 如果你确定你没有违反任何约束,你可以禁用约束然后插入行然后重新激活约束。
  • 顺便说一句,您没有保存可能少于 1000 个的“剩余”批次。(除非您始终只需要准确处理 n x 1000 订单...)

标签: java database jpa


【解决方案1】:

这通常取决于数据库设置,例如客户端的延迟是多少,表上的索引是什么,查询如何锁定表等等。

确保您了解网络运营所花费的时间。这可能是限制因素,尤其是当您的数据库位于世界的另一端时。

首先确定客户端和数据库服务器之间的延迟是多少。如果它是 10 毫秒,那么逐行插入此行将是:100,000 * 200 * 10ms = 200000s ~ 56h。这非常慢,因此请确保您使用的是带有 JDBC 的批量插入。

有时通过创建影子表可以显着加快插入过程:

  1. 创建与OrderEventsPreCondition 表相同的新表。一些 RDBMS 允许使用 CREATE TABLE ... AS SELECT ... FROM ... 语法。
  2. 禁用影子表上的外键和索引。
  3. 批量插入所有数据。
  4. 在影子表上启用外键和索引。这有望确保导入的数据正确无误。
  5. 从影子表插入到实际表中,例如通过运行INSERT INTO ... SELECT ... FROM ...
  6. 删除影子表。

但是最好的选择是跳过 JDBC 并切换到数据库提供的批量加载实用程序,例如Oracle DB 有External TablesSQL*Loader。这些工具专门设计用于高效地摄取大量数据,而 JDBC 是一个通用接口。

【讨论】:

  • 客户端和数据库服务器在同一台机器上。
  • 数据也存储在文本文件中,应用程序逐行读取并创建填充 OrderEvent 和 PreConditions 对象并保存。那么如何在这里使用批量加载实用程序呢?
  • 而不是使用 JDBC 将格式转换为批量工具可以使用的东西,例如格式正确的 CSV。比使用批量工具从文件系统加载此 CSV。
【解决方案2】:

在 c# 中,我可以将SqlBulkCopy 用于此类任务。

也许在java中有一个等效的API.. 像这样:com.microsoft.sqlserver.jdbc.SQLServerBulkCopy

【讨论】:

    【解决方案3】:

    使用数据库服务器 BULK 处理操作来做类似的事情会更好。 是的,它完全不同的过程,但它需要几秒钟。甚至几分钟。

    不幸的是,HOWTO 非常依赖 SQL-Server

    MS SQL:批量插入:https://docs.microsoft.com/en-us/sql/t-sql/statements/bulk-insert-transact-sql?view=sql-server-2017

    PostgreSQL:复制:https://www.postgresql.org/docs/current/sql-copy.html

    【讨论】:

      猜你喜欢
      • 2023-03-21
      • 2017-06-16
      • 2022-01-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多