处理文件的最快方式&;DB insert-Java多线程

处理文件的最快方式&;DB insert-Java多线程,java,multithreading,h2,Java,Multithreading,H2,我是多线程编码的新手 这是我的要求: 我有一个档案,里面有50000到300000条记录 它是基于列的数据(4列),以空格作为分隔符。我需要使用空格分割行,并将记录保存在DB中的4列中 我想开发一个多线程应用程序,它用4列(使用JDBC/其他任何东西?)将数据插入H2 DB,大约只需2秒钟。 我需要根据收到的记录数动态更改线程池大小 我正在使用Java开发一个桌面应用程序。(不是基于web的应用程序) 我不知道是否有更好的并发类可以更快地完成这项任务 如果不是多线程,还有其他方法吗?还是其他框架

我是多线程编码的新手

这是我的要求: 我有一个档案,里面有50000到300000条记录

它是基于列的数据(4列),以空格作为分隔符。我需要使用空格分割行,并将记录保存在DB中的4列中

我想开发一个多线程应用程序,它用4列(使用JDBC/其他任何东西?)将数据插入H2 DB,大约只需2秒钟。 我需要根据收到的记录数动态更改线程池大小

我正在使用Java开发一个桌面应用程序。(不是基于web的应用程序)

我不知道是否有更好的并发类可以更快地完成这项任务

如果不是多线程,还有其他方法吗?还是其他框架

添加批处理后,对于250000条记录,大约需要5秒钟:

    BufferedReader in = new BufferedReader(new FileReader(file));
    java.util.List<String[]> allLines = new ArrayList<String[]>(); // used for something else

    String sql = "insert into test (a, b, c, d)” +
            " values (?,?,?,?)";

    PreparedStatement pstmt = conn.prepareStatement(sql);
    int i=0;
    while ((line = in.readLine()) != null) {

        line = line.trim().replaceAll(" +", " ");
        String[] sp = line.split(" ");
        String msg = line.substring(line.indexOf(sp[5]));
        allLines.add(new String[]{sp[0] + " " + sp[1], sp[4], sp[5], msg});

        pstmt.setString(1, sp[0] + " " + sp[1]);
        pstmt.setString(2, sp[4]);
        pstmt.setString(3, sp[5]);
        pstmt.setString(4, msg);

        pstmt.addBatch();

        i++;

        if (i % 1000 == 0){
            pstmt.executeBatch();
            conn.commit();
        }
    }

    pstmt.executeBatch();
BufferedReader in=new BufferedReader(new FileReader(file));
java.util.List allLines=new ArrayList();//用于其他用途
String sql=“插入测试(a、b、c、d)”+
“值(?,?,?)”;
PreparedStatement pstmt=conn.prepareStatement(sql);
int i=0;
而((line=in.readLine())!=null){
line=line.trim().replaceAll(“+”,”);
字符串[]sp=line.split(“”);
字符串msg=line.substring(line.indexOf(sp[5]);
add(新字符串[]{sp[0]+“”+sp[1],sp[4],sp[5],msg});
pstmt.setString(1,sp[0]+“”+sp[1]);
pstmt.setString(2,sp[4]);
pstmt.setString(3,sp[5]);
pstmt.setString(4,msg);
pstmt.addBatch();
i++;
如果(i%1000==0){
pstmt.executeBatch();
conn.commit();
}
}
pstmt.executeBatch();

例如,我创建了一个csv文件,其中包含300000条记录,读取和添加到数据库的时间为所用时间=2625。用于从文件中读取记录,然后像这样将记录放入数据库。 将用户准备的语句和executeBatch()放入DB时

//尝试块,连接。。。
PreparedStatement PreparedStatement=connection.prepareStatement(查询);
对于(int i=0;i
带有executeBatch()的PreparedStatement比executeQuery快,因为您没有创建很多查询。

通过以下方式改进逻辑:

  • PreparedStatement
    的实例上创建,并在每次插入时使用它
  • 使用批处理仅发送插入的大数据包
这将通过以下方式完成:

private PreparedStatement pstmt;

public BatchInsertion(String sql) throws SQLException{
    pstmt = conn.prepareStatement(sql)
}

public int insert(String a, String b, String c, String d) throws SQLException{
    pstmt.setString(1, a);
    pstmt.setString(2, b);
    pstmt.setString(3, c);
    pstmt.setString(4, d);

    pstmt.addBatch();
    return batchSize++;
}

public void sendBatch() throws SQLException{
    pstmt.executeBatch();
}
在那里,您只需要管理该实例的插入,当您到达批中的最后一项或1000项时,发送它

我用它来不强制先插入
集合

注意:您需要在最后关闭语句,我将在这样的类上实现
AutoCloseable
,您可以尝试使用资源来确保安全


如果需要多线程插入,我建议采用以下体系结构:

创建一个线程池,每个线程都有一个连接和一个批来插入数据。 使用一个队列插入以从文件中推送数据。 每个线程将获取一个值并将其添加到批处理中

使用此体系结构,您可以轻松增加线程数

首先,一个轻量级的
BatchInsert
类可以运行:

class BatchInsert implements AutoCloseable {

    private int batchSize = 0;
    private final int batchLimit;

    public BatchInsert(int batchLimit) {
        this.batchLimit = batchLimit;
    }

    public void insert(String a, String b, String c, String d) {
        if (++batchSize >= batchLimit) {
            sendBatch();
        }
    }

    public void sendBatch() {
        System.out.format("Send batch with %d records%n", batchSize);
        batchSize = 0;
    }

    @Override
    public void close() {
        if (batchSize != 0) {
            sendBatch();
        }
    }
}
然后,我使用某种平衡器来提供一个队列和共享同一队列的多个
线程

class BalanceBatch {
    private final List<RunnableBatch> threads = new ArrayList<>();

    private Queue<String> queue = new ConcurrentLinkedQueue<>();
    private static final int BATCH_SIZE = 50_000;

    public BalanceBatch(int nbThread) {
        IntStream.range(0, nbThread).mapToObj(i -> new RunnableBatch(BATCH_SIZE, queue)).forEach(threads::add);
    }

    public void send(String value) {
        queue.add(value);
    }

    public void startAll() {
        for (RunnableBatch t : threads) {
            new Thread(t).start();
        }
    }

    public void stopAll() {
        for (RunnableBatch t : threads) {
            t.stop();
        }
    }
}
笔记: 警告:
createDummy
函数将创建一个包含25行的文件(我已经对其进行了注释)。这大约是一个1GB数据的文件

我将需要更多的时间来做一些基准测试,我没有任何数据库的大规模插入的时刻


将此多线程文件读取器与批处理混合使用会产生很好的效果。

请注意,这可能不是多线程的最佳实现,我从来没有做过这方面的工作。我愿意接受建议/改进。

示例中的问题是,您为每个值条目创建了准备好的语句

批处理执行是一个选项,但我将构建一个包含多个值的insert语句,如下所示:

insert into data (a, b, c, d) 
values (a1, b1, c1, d1), (a2, b2, c2, d2), (a3, b3, c3, d3)...

然后您可以执行它一次,就这样。

其他答案已经指出,您应该使用批插入。我认为对于最快的导入,您实际上不应该使用Java

参见H2文档中的:

< >为了加快大型导入,考虑临时使用以下选项:

  • 设置日志0
    (禁用事务日志)
  • 设置缓存大小
    (大缓存速度更快)
  • 设置锁定模式0
    (禁用锁定)
  • 设置撤消日志0
    (禁用会话撤消日志)
这些选项可以在数据库URL中设置:
jdbc:h2:~/test;LOG=0;CACHE_SIZE=65536;LOCK_MODE=0;UNDO_LOG=0
。大多数选项不建议经常使用,这意味着您需要在使用后重置它们

如果必须导入大量行,请使用
PreparedStatement
或使用CSV导入。请注意,
CREATE TABLE(…)…AS SELECT…
CREATE TABLE(…);INSERT in…SELECT…

我从其他一些数据库中知道的另一个技巧是在插入之前删除索引,然后在插入之后重新创建索引。我不知道这是否对H2有任何影响,但我想作为一个潜在的尝试提一下

您可以简单地使用:

INSERT INTO MY_TABLE(...) SELECT * FROM CSVREAD('data.csv');
H2文档提到

public static void main(String[] args) throws IOException {
    createDummy("/tmp/data.txt", 25_000_000);

    BalanceBatch balance = new BalanceBatch(10);

    balance.startAll();
    try (Stream<String> stream = Files.lines(Paths.get("/tmp/data.txt"))) {
        stream.forEach(balance::send);
    } catch (Exception e1) {
        e1.printStackTrace();
    }
    balance.stopAll();
}

public static void createDummy(String file, int nbLine) throws IOException {
    Files.write(Paths.get(file), (Iterable<String>) IntStream.range(0, nbLine).mapToObj(i -> String.format("A%d;B%d;C%d;D%d", i, i, i, i))::iterator);
}
Send batch with 50000 records
...
Send batch with 50000 records
Send batch with 15830 records
Send batch with 15844 records
Send batch with 2354 records
Send batch with 14654 records
Send batch with 40181 records
Send batch with 44994 records
Send batch with 38376 records
Send batch with 17187 records
Send batch with 27047 records
Send batch with 33533 records
insert into data (a, b, c, d) 
values (a1, b1, c1, d1), (a2, b2, c2, d2), (a3, b3, c3, d3)...
INSERT INTO MY_TABLE(...) SELECT * FROM CSVREAD('data.csv');