我正在使用JDBC的批处理来插入一百万行。我遇到了Oracle驱动程序无法按预期工作的情况-批量插入需要很长时间才能工作。 我决定通过Wireshark嗅探应用程序的流量。我看到了什么?
insert into my_table...
insert into my_table...
为什么会这样?我该如何解决?
表
create table my_table (val number);
代码
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class scratch_1 {
@Test
public void foo() throws SQLException {
String sql = "insert into my_table (val) values (?)";
try (Connection con = getConnection()) {
con.setAutoCommit(false);
try (PreparedStatement ps = con.prepareStatement(sql)) {
for (long i = 0; i < 100_000; i++) {
ps.setBigDecimal(1, BigDecimal.valueOf(i));
ps.addBatch();
}
ps.executeBatch();
ps.clearBatch();
}
con.commit();
}
}
private Connection getConnection() throws SQLException {
String url = "jdbc:oracle:thin:@localhost:1521:orcl";
String user = "my_user";
String password = "my_password";
return java.sql.DriverManager.getConnection(url, user, password);
}
}
Wireshark代码说明发生了什么:
环境
$ java -version
java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)
Oracle Database 12.2.0.1 JDBC Driver
服务器:Oracle Database 11g企业版11.2.0.4.0-64位
多次运行查询无济于事-结果相同。
在250k
中插入了465s
行“批量”
在服务器端v$sql
:
SELECT *
FROM
(SELECT REGEXP_SUBSTR (sql_text, 'insert into [^\(]*') sql_text,
sql_id,
TRUNC(
CASE
WHEN SUM (executions) > 0
THEN SUM (rows_processed) / SUM (executions)
END,2) rows_per_execution
FROM v$sql
WHERE parsing_schema_name = 'MY_SCHEMA'
AND sql_text LIKE 'insert into%'
GROUP BY sql_text,
sql_id
)
ORDER BY rows_per_execution ASC;
答案 0 :(得分:2)
问题解决了
感谢您的所有回复。我非常感谢您!
我之前的示例未描述实际问题。抱歉,无法立即显示全部图片。
我将其简化为无法处理空值的状态。
请检查上面的示例,我已经对其进行了更新。
如果我使用java.sql.Types.NULL
的Oracle JDBC驱动程序将theVarcharNullBinder
用作null
的值,那么它会导致某种奇怪的工作。我认为Driver用于批处理,直到未指定类型的第一个null
为止,为null后将回退到一对一的插入。
将java.sql.Types.NUMERIC
列驱动程序使用的number
更改为theVarnumNullBinder
后,并正确使用它-完全批处理。
代码
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class scratch_1 {
@Test
public void foo() throws SQLException {
String sql = "insert into my_table (val) values (?)";
try (Connection con = getConnection()) {
con.setAutoCommit(false);
try (PreparedStatement ps = con.prepareStatement(sql)) {
for (long i = 0; i < 100_000; i++) {
if (i % 2 == 0) {
//the real problem was here:
//ps.setNull(1, Types.NULL); //wrong way!
ps.setNull(1, Types.NUMERIC); //correct
} else {
ps.setBigDecimal(1, BigDecimal.valueOf(i));
}
ps.addBatch();
}
ps.executeBatch();
ps.clearBatch();
}
con.commit();
}
}
private Connection getConnection() throws SQLException {
String url = "jdbc:oracle:thin:@localhost:1521:orcl";
String user = "my_user";
String password = "my_password";
return java.sql.DriverManager.getConnection(url, user, password);
}
}
答案 1 :(得分:0)
我不确定此限制来自何处。但是,Oracle JDBC Developer's Guide提出了以下建议:
Oracle建议将批大小保持在100或更小范围内。较大的批次几乎没有或根本没有改善性能,并且由于处理大量批次所需的客户端资源,实际上可能会降低性能。
当然可以使用更大的批处理大小,但不一定像您所看到的那样提高性能。一个人应该使用最适合用例和所用JDBC驱动程序/数据库的批处理大小。您可能应该在每次使用2500个批次的情况下才能获得最佳性能收益。