如何在不构建字符串的情况下使用 JDBC 进行扩展插入?
Posted
技术标签:
【中文标题】如何在不构建字符串的情况下使用 JDBC 进行扩展插入?【英文标题】:How do you do an extended insert using JDBC without building strings? 【发布时间】:2010-11-07 13:40:23 【问题描述】:我有一个应用程序可以解析日志文件并将大量数据插入数据库。它是用 Java 编写的,并通过 JDBC 与 mysql 数据库通信。我已经尝试了不同的方法来插入数据,以便为我的特定用例找到最快的方法。目前似乎表现最好的方法是发出扩展插入(例如,具有多行的单个插入),如下所示:
INSERT INTO the_table (col1, col2, ..., colN) VALUES
(v1, v2, v3, ..., vN),
(v1, v2, v3, ..., vN),
...,
(v1, v2, v3, ..., vN);
行数可以上万。
我尝试过使用准备好的语句,但它远没有那么快,可能是因为每个插入仍然单独发送到数据库并且需要锁定表等等。在我之前编写代码的同事尝试使用批处理,但效果也不够好。
问题在于,使用扩展插入意味着据我所知,我需要自己构建 SQL 字符串(因为行数是可变的),这意味着我打开了各种 SQL 注入向量我没有足够聪明的地方找到自己。必须有更好的方法来做到这一点。
显然,我对插入的字符串进行了转义,但仅使用 str.replace("\"", "\\\"");
之类的字符串(对于 '、? 和 \ 重复),但我确信这还不够。
【问题讨论】:
【参考方案1】:准备好的语句+批量插入:
PreparedStatement stmt = con.prepareStatement(
"INSERT INTO employees VALUES (?, ?)");
stmt.setInt(1, 101);
stmt.setString(2, "Paolo Rossi");
stmt.addBatch();
stmt.setInt(1, 102);
stmt.setString(2, "Franco Bianchi");
stmt.addBatch();
// as many as you want
stmt.executeBatch();
【讨论】:
发行一个插入还是多个?或者批处理是否消除了发出许多准备好的语句的开销? 批处理应该尽量减少准备好的语句的开销 对不起,我的同事告诉我批处理并没有解决问题(我已经编辑了问题以包含这个)。 然而,恕我直言,这是避免 SQL 注入并提供良好性能的唯一可行方法 如果批处理性能不够好,我会感到非常惊讶。对于大型数据集,您需要稍微调整一下批次。例如,批处理不能无限大,否则您将耗尽内存。但是,您的批次需要足够大才能获得所需的性能。尝试将批量大小增加到更大的值。但不要只听同事的话,自己试试吧。【参考方案2】:我会尝试批量插入,看看效果如何。
阅读本文 (http://www.onjava.com/pub/a/onjava/excerpt/javaentnut_2/index3.html?page=2) 了解有关批处理的更多信息。
【讨论】:
批处理似乎是一种解决方案,但根据我之前从事代码工作的同事的说法,它与使用扩展插入相比没有(我已经编辑了问题以包括这个)。跨度> 不知道您的同事在尝试批处理这些插入时做了什么,但使用批处理并不像循环遍历您要插入的所有数据,将所有数据添加到一个大批量并调用执行批处理。这是 MySQL 邮件列表 (lists.mysql.com/maxdb/14236) 中关于批处理的帖子。在您描述的情况下,您当然希望多次调用 executeBatch。 很公平,看起来批处理当然值得再试一次,但如果他说的是真的(而且他似乎从未提交过代码,只是尝试过并丢弃),那么性能会有很大差异在扩展插入和批处理之间。我想这一切都归结为瓶颈所在:是将数据发送到缓慢的数据库,还是锁定以及数据库内部的所有问题。如果是后者批处理不能解决问题,如果是前者,它可能会表现得一样好,我会得到更多的安全性。 怀疑可能两者兼而有之(发送数据和数据库本身)。我会使用不同的批量大小并查看表结构及其索引。【参考方案3】:如果您要加载数万条记录,那么最好使用批量加载器。
http://dev.mysql.com/doc/refman/5.0/en/load-data.html
【讨论】:
@dfa - 静态数据是什么意思? 我认为这是一个足够公平的建议。根据我的经验,编写 CSV 文件和使用 LOAD DATA INFILE 可以非常非常快。它有点复杂,因为它涉及编写 CSV 文件并确保 MySQL 可以找到它们。 在 SQL Server (bcp) 中使用类似的实用程序,我设法将大约 25k 行的插入时间从大约 50 秒减少到大约 3 秒。【参考方案4】:关于扩展插入和批处理单个插入之间的区别,我决定使用扩展插入的原因是因为我注意到我的代码插入大量行所花费的时间比 mysql 从终端插入的时间要长得多。即使我批量插入 5000 个插入也是如此。最终的解决方案是使用扩展插入。
我很快重新测试了这个理论。
我对一个有 120 万行的表进行了两次转储。一个使用通过 mysqldump 获得的默认扩展插入语句,另一个使用:
mysqldump --skip-extended-insert
然后我只是将文件再次导入新表并计时。
扩展插入测试在 1 分 35 秒内完成,另一个在 3 分 49 秒内完成。
【讨论】:
不过,这并没有考虑准备好的语句。为了进行公平比较,您需要将数据加载到首先准备 INSERT 语句的应用程序中,批量添加每一行并运行它。我仍然认为你最终是对的,但这实际上并没有说明准备好的语句 + 批处理是否与扩展插入一样快。【参考方案5】:完整的答案是使用rewriteBatchedStatements=true
配置选项以及 和dfa's answer 使用批处理语句。
相关mysqldocumentation
一个工作的 MySQL example
【讨论】:
以上是关于如何在不构建字符串的情况下使用 JDBC 进行扩展插入?的主要内容,如果未能解决你的问题,请参考以下文章
CORS 谷歌浏览器扩展有啥替代品吗?如何在不使用 CORS 的情况下成功发出 ajax 请求?
我可以在不重新构建的情况下对 Maven 构建中的文件进行更改吗?
如何在不运行实际查询的情况下检查 JDBC 语句的 SQL 语法?
如何在不将 LocalDateTime 字段转换为扩展的 json 对象的情况下将 java 对象转换为简单的 json 字符串?