JDBC批处理插入性能


69

我需要将几亿条记录插入mysql db。我要一次插入一百万个。请在下面查看我的代码。它似乎很慢。有什么方法可以优化它吗?

try {
        // Disable auto-commit
        connection.setAutoCommit(false);

        // Create a prepared statement
        String sql = "INSERT INTO mytable (xxx), VALUES(?)";
        PreparedStatement pstmt = connection.prepareStatement(sql);

        Object[] vals=set.toArray();
        for (int i=0; i<vals.length; i++) {
            pstmt.setString(1, vals[i].toString());
            pstmt.addBatch();
        }

        // Execute the batch
        int [] updateCounts = pstmt.executeBatch();
        System.out.append("inserted "+updateCounts.length);

您的代码已损坏(过早地被截断了)
Uri 2010年

顺便说一句,您正在使用哪个驱动程序?通用JDBC还是JDBC-Mysql连接器?
Uri 2010年

我正在使用com.mysql.jdbc.Driver

多久时间?您认为比较慢的比较材料是什么?
BalusC 2010年

我只在本地PC(4gb ram)中进行了100万次测试插入,只花了10分钟左右,只是想知道是否还有改进的余地

Answers:


182

我在mysql中遇到类似的性能问题,并通过在连接URL中设置useServerPrepStmtsrewriteBatchedStatements属性来解决它。

Connection c = DriverManager.getConnection("jdbc:mysql://host:3306/db?useServerPrepStmts=false&rewriteBatchedStatements=true", "username", "password");

4
@Kimble-为什么不接受这个答案?谢了哥们!这就像魔术!
PeterPerháč13年

3
10秒而不是1小时。当之无愧的+1!
0x6B6F77616C74


1
我注意到在使用Java驱动程序时(至少在5.1.10版本的驱动程序中),对插入批处理有另一个要求:在SQL语句中,“ VALUES”部分必须后跟一个空格,而不是直接在右括号之后,否则驱动程序将退回到顺序插入。
Christian Semrau '16

3
您能解释为什么添加useServerPrepStmts = false有用吗?阅读了stackoverflow.com/questions/32286518/…之后,我认为情况恰恰相反。我确定您是对的,只是我不完全了解为什么/如何提高设置的性能?谢谢。
Stephane Grenier

63

由于我一直在尝试连接URL参数,因此我想扩展一下Bertil的答案。

rewriteBatchedStatements=true是重要参数。useServerPrepStmts默认情况下已经为false,即使将其更改为true也不会对批处理插入性能产生太大影响。

现在我认为是时候写如何rewriteBatchedStatements=true极大地提高性能了。它是通过rewriting of prepared statements for INSERT into multi-value inserts when executeBatch()Source)实现的。这意味着不是n每次都将以下INSERT语句发送到mysql服务器executeBatch()

INSERT INTO X VALUES (A1,B1,C1)
INSERT INTO X VALUES (A2,B2,C2)
...
INSERT INTO X VALUES (An,Bn,Cn)

它会发送一个INSERT语句:

INSERT INTO X VALUES (A1,B1,C1),(A2,B2,C2),...,(An,Bn,Cn)

您可以通过切换mysql日志记录(by SET global general_log = 1)来观察它,它将记录每个发送到mysql服务器的语句的文件。


@Vipin我不知道。
伊兰

Eran-关于Updates,rewriteBatchedStatements = true可以提高插入时的更新性能,因为更新的语法与插入时的语法不同,并且不能整体执行(据我所知)。之所以这样说,是因为我希望插入的行数超过10k,而更新时的行数不到1秒。
rpajaziti

13

您可以使用一个insert语句插入多行,一次执行几千个可以大大加快操作速度,也就是说INSERT INTO tbl_name (a,b,c) VALUES(1,2,3);,您不必执行例如3个形式的插入,而是这样做INSERT INTO tbl_name (a,b,c) VALUES(1,2,3),(1,2,3),(1,2,3);(可能是JDBC .addBatch()进行了类似的优化现在-尽管mysql addBatch以前没有经过全面优化,但无论如何都只是发出单个查询-我不知道最近的驱动程序是否仍然如此)

如果您确实需要速度,请使用LOAD DATA INFILE从用逗号分隔的文件中加载数据,这样做的速度比进行数千万次插入的速度快7到8倍。


加载数据infile可能是一个很好的选择,但是我的输入文件需要清理,我只想插入某些行,其中第二个令牌与字符串(以空格分隔的令牌)匹配,加载数据infile是否足够灵活以过滤行?

3
我认为它无法过滤,但是您可以自己清理数据,使用清理后的数据编写新文件并加载该文件。

现在我的插入速度快了10倍!
Matt Sgarlata 2013年

6

如果:

  1. 这是一个新表,或者要插入的数量大于已插入的数据
  2. 桌子上有索引
  3. 插入期间,您不需要其他访问表的权限

然后ALTER TABLE tbl_name DISABLE KEYS可以大大提高插入速度。完成后,请运行ALTER TABLE tbl_name ENABLE KEYS以开始建立索引,这可能需要一段时间,但所需时间不及每次插入操作的时间。


1

您可以尝试使用DDBulkLoad对象。

// Get a DDBulkLoad object
DDBulkLoad bulkLoad = DDBulkLoadFactory.getInstance(connection);
bulkLoad.setTableName(“mytable”);
bulkLoad.load(“data.csv”);

1
try {
        // Disable auto-commit
        connection.setAutoCommit(false);
        int maxInsertBatch = 10000;     
        // Create a prepared statement
        String sql = "INSERT INTO mytable (xxx), VALUES(?)";
        PreparedStatement pstmt = connection.prepareStatement(sql);

        Object[] vals=set.toArray();
        int count = 1;
        for (int i=0; i<vals.length; i++) {
            pstmt.setString(1, vals[i].toString());
            pstmt.addBatch();
            if(count%maxInsertBatch == 0){
                 pstmt.executeBatch();
            }
            count++;
        }

        // Execute the batch
        pstmt.executeBatch();
        System.out.append("inserted "+count);

而不是对此进行表决,为什么在执行多个批处理之间而不是一次执行所有批处理时可以或不能提高性能呢?
benez

看起来上面的答案有几乎相同的代码片段,这是一个问题。
suhas0sn07 '19
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.