ExecuteBatch 和 Prepared Statements 的奇怪行为
Posted
技术标签:
【中文标题】ExecuteBatch 和 Prepared Statements 的奇怪行为【英文标题】:Curious behaviour with ExecuteBatch and Prepared Statements 【发布时间】:2012-06-23 06:39:12 【问题描述】:我有一个奇怪的问题,涉及使用 JDBC 将大量数据放入 PostgSQL 数据库的 executeBatch 和 Prepared Statements。我每次使用 executeBatch 向数据库发送 50,000 条语句。
我知道执行批处理和准备好的语句正在工作;一些数据正在进入数据库。 准备好的语句是
INSERT INTO time ( time_id, log_id, phenomenon_time, qc_phenomenon_time )
SELECT nextval( 'time_seq' ), ?, ?, ?
将其与执行批处理一起使用,可以在数据库中找到数据。
当使用以下准备好的语句时,
INSERT INTO result_3d ( result_3d_id, time_id, variable_id, value, qc_value )
SELECT nextval( 'result_3d_seq' ), ( SELECT t.time_id
FROM time t
WHERE t.log_id = ?
AND t.phenomenon_time = ? ), ?, ?, ?
执行批处理后,数据库中没有数据。我什至打开了数据库日志记录,发现第一个的所有内容,但第二个什么都没有。第二个准备好的语句依赖于第一个的数据,但数据库甚至没有看到第二个。
没有抛出异常。唯一奇怪的是,对于第二个准备好的语句,返回的数组的大小为零。执行批处理立即返回。第二个准备好的语句中的子查询是否允许?
我使用 postgres-9.1-901.jdbc4.jar 作为 PostgreSQL v8.3.19 数据库的 JDBC 驱动程序。
请帮忙。
【问题讨论】:
set log_statement = 'all'
在 postgresql.conf 中并重新启动/重新加载 Pg,如果您还没有的话。将loglevel = 2
添加到您在创建连接时传递的PgJDBC 参数中。然后检查 PgJDBC 日志(通过您现有的任何 java 日志记录)和 Pg 日志(在 datadir 中的 pg_log
或 /var/log/
中)。
更多信息也会有所帮助:在针对该数据库测试旧 PgJDBC 时也会发生这种情况吗?在针对当前 Pg 测试新的 PgJDBC 时怎么样?您需要测试这些配置以隔离故障。
【参考方案1】:
我将回答我自己的问题,因为我描述的症状与执行批处理或准备语句无关。在一些重构过程中,我遗漏了一个重要的声明。
this.preparedStatement.addBatch();
所以这是我的错。症状很好地描述了缺少语句的代码行为。
感谢您的 cmets 所付出的努力。有些人给了我新的学习领域。谢谢欧文。
【讨论】:
【参考方案2】:我不知道 JDBC 驱动程序,但您使用 9.1 版并连接到过时的 PostgreSQL 8.3.19 看起来很可疑。将 PostgreSQL 升级到 9.1 可能会解决您的问题。
通常,如果您已将列 time.time_id
和 result_3d.result_3d_id
定义为 serial
列(您可能应该这样做),或者您已在相应序列上手动将这些列的 DEFAULT 值设置为 nextval()
,则无需从 sequences 中获取 id。这些值将自动填写。
第二个预处理语句中的子查询是否允许?
是的,原则上是这样。但它可能永远不会返回超过一行。您必须保证(t.log_id, t.phenomenon_time)
的唯一性或添加LIMIT 1
:
(SELECT t.time_id
FROM time t
WHERE t.log_id = ?
AND t.phenomenon_time = ?
LIMIT 1)
在 PostgreSQL 9.1 中,您可以将两个 INSERT
命令与 data-modifying CTE 链接在一起,这应该会更快一些,并且不需要以子查询开头:
WITH data (log_id, phenomenon_time, qc_phenomenon_time
,variable_id, value, qc_value ) AS (
VALUES(?, ?, ?, ?, ?, ?) -- cast to appropriate types!
)
, i AS (
INSERT INTO time (log_id, phenomenon_time, qc_phenomenon_time)
SELECT log_id, phenomenon_time, qc_phenomenon_time
FROM data
RETURNING time_id, log_id, phenomenon_time
)
INSERT INTO result_3d (time_id, variable_id, value, qc_value)
SELECT i.time_id, d.variable_id, d.value, d.qc_value
FROM data d
JOIN i USING (log_id, phenomenon_time);
所有这些可能会或可能不会解决根本问题,但很有可能会。
我的第一个概念是潜在问题可能是并发问题 - 这意味着第二个 INSERT
在第一个提交之前启动。但是,如果数据库甚至没有看到第二次调用,那么这里肯定有其他东西在起作用。
【讨论】:
我正在尝试进行回归测试,因此使用较早数据库的较晚驱动程序。 第二个子查询很有可能只返回一行,但是由于数据库甚至没有看到它,所以数据库无法检查约束。虽然数据修改 CTE 很有趣,但它不适合这里。第一条语句被执行,比如说,1M 时间来填充时间表。第二条语句对无数个变量执行 1M 次。第二条语句确实发生了一些事情,因为我的数据类型不匹配。但它现在根本没有执行。 JDBC驱动版本较早的服务器版本可以配合使用;只是不要反过来做。 jdbc.postgresql.org/download.html#current 在子查询中添加LIMIT
是一个非常不安全的解决方法,而没有ORDER BY
也是如此;它将导致不可预测和不可重复的行为。抄送@BrettWalker
@CraigRinger:添加LIMIT 1
只是为了修复错误。如果子查询可以返回多行,则整个设置需要重新考虑。提议的 CTE 将消除潜在的故障点。以上是关于ExecuteBatch 和 Prepared Statements 的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章
Postgresql:prepared statement "S_1" already exists
使用 addBatch 和 executeBatch 时是不是必须使用相同的 prepareCall?为啥?
JDBC批量插入数据优化,使用addBatch和executeBatch
JDBC批量插入数据优化,使用addBatch和executeBatch
如何使用 SimpleJdbcInsert 和 executeBatch 和 MYSQL JDBC 驱动程序获取生成的密钥?