在单次执行后关闭PreparedStatement - 这是一个设计缺陷吗?

afk*_*min 6 java jdbc eda

我已经调查了很多地方,并且听到了许多可疑的主张,不论在哪里都PreparedStatement应该是首选Statement,即使只是为了表现的好处; 一直到声称PreparedStatements应该专门用于批处理语句,而不是其他任何东西.

但是,我所遵循的(主要是在线)讨论似乎存在盲点.让我提出一个具体的方案.


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

除了那些人工生成的事件之外,没有关于需要持久性的事件如何到来的结构.

这个应用程序是在很久以前设计的(大约是2005年),并且支持多个DBMS.典型的事件处理程序(需要持久性):

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

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


最近,我收到一个意见,即准备(解析)一个语句,执行一次和关闭本质上是一种滥用的整个想法PreparedStatement,无论是否使用服务器或客户端预处理语句以及典型的DBMS,都提供零性能优势(Oracle,DB2,MSSQL,MySQL,Derby等)甚至不会将这样的语句提升到预处理语句缓存(或者至少,它们的默认JDBC驱动程序/数据源不会).

此外,我不得不在MySQL的开发环境中测试某些场景,似乎Connector/J使用分析器同意这个想法.对于所有非批处理的预处理语句,调用close()print:

PreparedStatement created, but used 1 or fewer times. It is more efficient to prepare statements once, and re-use them many times


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

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

PS显式指定useUsageAdvisor=truecachePrepStmts=true用于连接器/ J,并使用两种useServerPrepStmts=trueuseServerPrepStmts=false打电话时仍然导致警告有关效率close()PreparedStatement的情况下,每一个非成批的SQL语句.

Gor*_*son 3

准备-执行[一次]-关闭的逻辑是否有缺陷并且基本上不被鼓励?

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

典型的 DBMS(Oracle、DB2、MSSQL、MySQL、Derby 等)甚至不会将此类语句提升到准备好的语句缓存(或者至少,它们的默认 JDBC 驱动程序/数据源不会)。

不鼓励是这样的模式:

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

因为PreparedStatement只使用一次,并且相同的SQL语句被一遍又一遍地准备。(尽管如果 SQL 语句及其执行计划确实被缓存的话,这可能也没什么大不了的。)更好的方法是

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

...或者更好的是,“尝试使用资源”...

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

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