Db2中PreparedStatement缓存导致DDL变更后ResultSet未更新问题
我有一个基于Java的应用,该应用使用PreparedStatement执行查询。这些PreparedStatement在连接实现层被缓存,并会根据淘汰规则被丢弃。
遇到的问题:
当我使用如下查询生成PreparedStatement后:
SELECT FUNCTION(..) as A, T.* FROM table t WHERE ...
若后续执行ALTER TABLE table ...语句添加新列,复用上述缓存的PreparedStatement时,其ResultSet中无法看到新增的列。但当该语句过期被关闭,或在ALTER后手动关闭并创建新的PreparedStatement时,就能看到新增的列。
我有几个问题:
- 能否解释此现象?我在其他数据库厂商中未发现该问题。
PreparedStatement的缓存是由驱动还是数据库层面控制?- 若由驱动层面控制,能否禁用此行为?
更新
我们明确连接的是Linux上的IBM Db2 11.5实例,使用的驱动如下:
<dependency> <groupId>com.ibm.db2</groupId> <artifactId>jcc</artifactId> <version>11.5.0.0</version> </dependency>
1. 现象解释
这是因为IBM Db2的JCC驱动在缓存PreparedStatement时,会预先解析并缓存查询的结果集元数据(ResultSet Metadata)。第一次创建PreparedStatement时,驱动会向Db2服务器请求该查询的结果结构(包括列名、类型、顺序等),并把这些元数据和PreparedStatement一起缓存起来。
当你执行ALTER TABLE添加新列后,缓存的PreparedStatement对应的元数据并没有更新——驱动默认认为已缓存的元数据是有效的,不会主动重新向服务器查询最新的结果结构。而T.*在第一次解析时对应的是当时表的列集合,后续新增的列不会被包含在缓存的元数据里,所以ResultSet中自然看不到新列。
其他数据库厂商的驱动可能在处理这类场景时,要么会在表结构变更后自动失效相关的PreparedStatement缓存,要么会在每次执行时重新校验元数据,所以你没遇到这个问题。
2. 缓存控制层面
这个缓存是由IBM Db2 JCC驱动层面控制的。Db2服务器本身虽有包缓存机制,但你遇到的元数据不更新问题,根源是驱动端对PreparedStatement及其元数据的本地缓存。
3. 禁用驱动端缓存的方法
可以通过修改JDBC连接属性来禁用驱动端的PreparedStatement缓存,具体需要设置以下属性:
disableStatementPooling=true:关闭驱动端的语句池(即PreparedStatement缓存)prepareThreshold=0:强制驱动每次都重新创建PreparedStatement,而不是复用缓存的实例
你可以在JDBC URL中添加这些参数,例如:
jdbc:db2://your-db-host:50000/your-db-name:disableStatementPooling=true;prepareThreshold=0;
如果不想完全禁用缓存,也可以考虑在执行ALTER TABLE后重新获取连接,绕过当前连接的缓存(JCC驱动没有直接提供清除缓存的API)。
内容的提问来源于stack exchange,提问作者Naros

