java - 单次执行后关闭 PreparedStatement——这是设计缺陷吗?

标签 java jdbc eda

我调查了很多地方,听到了很多可疑的说法,从 PreparedStatement 应该比 Statement 更受欢迎,即使只是为了性能优势;一路声称 PreparedStatement 应该专门用于批处理语句,而不是其他。

但是,我关注的(主要是在线的)讨论似乎存在盲点。让我展示一个具体的场景。


我们有一个带有数据库连接池的 EDA 设计的应用程序。事件来了,有的需要坚持,有的不需要。有些是人为生成的(例如,每 X 分钟更新/重置一些内容)。 一些事件按顺序发生和处理,但其他类型的事件(也需要持久性)可以(并且将会)并发处理。

除了那些人为生成的事件之外,没有关于需要持久化的事件如何到达的结构。

此应用程序是很久以前(大约 2005 年)设计的,支持多个 DBMS。典型的事件处理程序(需要持久化的地方):

  • 从池中获取连接
  • 准备sql语句
  • 执行准备好的语句
  • 处理结果集,如果适用,关闭
  • 关闭准备好的声明
  • 准备一份不同的声明,如有必要,并以同样的方式处理
  • 返回连接池

如果事件需要批处理,语句准备一次并使用addBatch/executeBatch 方法。这是一个明显的性能优势,这些情况与此问题无关。


最近,我收到了一个意见,即准备(解析)一条语句、执行一次并关闭的整个想法本质上是对 PreparedStatement 的滥用,提供了零性能优势,无论是使用服务器还是客户端准备语句,典型的 DBMS(Oracle、DB2、MSSQL、MySQL、Derby 等)甚至不会将此类语句提升到准备语句缓存(或至少是它们的默认 JDBC 驱动程序/数据源)不会)。

此外,我不得不在 MySQL 的开发环境中测试某些场景,Connector/J usage analyzer 似乎同意这个想法。对于所有非批处理的准备好的语句,调用 close() 打印:

PreparedStatement 已创建,但使用次数不超过 1 次。一次准备语句,多次重复使用它们效率更高


由于前面概述的应用程序设计选择,拥有一个 PreparedStatement 实例缓存来保存连接池中每个连接的任何事件使用的每个 SQL 语句听起来是一个糟糕的选择。

有人可以进一步详细说明吗? 逻辑“准备-执行(一次)-关闭”是否存在缺陷并且基本上不鼓励?

附言为 Connector/J 明确指定 useUsageAdvisor=truecachePrepStmts=true 并使用 useServerPrepStmts=trueuseServerPrepStmts=false每个 非批处理 SQL 语句的 PreparedStatement 实例上调用 close() 时,仍然会导致效率警告。

最佳答案

Is the logic prepare-execute [once]-close flawed and essentially discouraged?

我不认为这是一个问题,本身。给定的 SQL 语句需要在某个时刻进行“准备”,无论是显式(使用 PreparedStatement)还是“即时”(使用 Statement)。如果我们使用 PreparedStatement 而不是 Statement 来处理只会执行一次的内容,可能会产生更多的开销,但所涉及的开销不太可能很大,特别是如果您引用的语句是正确的:

typical DBMSes (Oracle, DB2, MSSQL, MySQL, Derby, etc.) will not even promote such a statement to prepared statement cache (or at least, their default JDBC driver/datasource will not).

不鼓励使用这样的模式:

for (int thing : thingList) {
    PreparedStatement ps = conn.prepareStatement(" {some constant SQL statement} ");
    ps.setInt(1, thing);
    ps.executeUpdate();
    ps.close();
}

因为 PreparedStatement 只被使用一次,而且同一个 SQL 语句被一遍又一遍地准备。 (尽管如果确实缓存了 SQL 语句及其执行计划,即使那样也不是什么大问题。)更好的方法是

PreparedStatement ps = conn.prepareStatement(" {some constant SQL statement} ");
for (int thing : thingList) {
    ps.setInt(1, thing);
    ps.executeUpdate();
}
ps.close();

...甚至更好,“尝试使用资源”...

try (PreparedStatement ps = conn.prepareStatement(" {some constant SQL statement} ")) {
    for (int thing : thingList) {
        ps.setInt(1, thing);
        ps.executeUpdate();
    }
}

请注意,即使不使用批处理也是如此。 SQL语句仍然只准备一次,多次使用。

关于java - 单次执行后关闭 PreparedStatement——这是设计缺陷吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33065643/

相关文章:

eclipse - 访问 JFrame 时 JVM 以退出代码 1 终止

java - MySQL 在 Latin1 中工作——如何插入 UTF-8 编码的数据?

python - 删除任意列包含特定字符串的行

java - 从java访问外部.jar资源

java - 具有相同方法名称的两个接口(interface),当我覆盖时会发生什么?

jdbc - 由于设置类路径,java程序无法运行

java - 将数据从 DAO 传输到服务层的有效方法

vhdl - 转换函数 "To_bit"必须只有一个形参

java - 检查字符串是否包含其他字符串Java的一部分