将 DDL 与 SELECT 通过 JDBC 混合时出现“错误:缓存计划不能更改结果类型”

Posted

技术标签:

【中文标题】将 DDL 与 SELECT 通过 JDBC 混合时出现“错误:缓存计划不能更改结果类型”【英文标题】:"ERROR: cached plan must not change result type" when mixing DDL with SELECT via JDBC 【发布时间】:2015-12-09 14:12:37 【问题描述】:

我在通过 JDBC 使用 PostgreSQL 时遇到了一个有趣的问题(无法在 JDBC 之外重现它),我得到了一个

“错误:缓存的计划不能改变结果类型”

重现此问题的最简单方法是使用以下代码:

Connection c = getConnection();
c.setAutoCommit(true);
List<String> statements = Arrays.asList(
    "create table t(a int)",
    "select * from t",
    "alter table t add b int",
    "select * from t",
    "alter table t add c int",
    "select * from t",
    "alter table t add d int",
    "select * from t",
    "alter table t add e int",
    "select * from t",
    "alter table t add f int",
    "select * from t"
);

for (String statement : statements)
    try (PreparedStatement s = c.prepareStatement(statement)) 
        System.out.println(s);
        s.execute();
    

以下代码运行良好的事实让我认为这是 JDBC 驱动程序中的一个非常微妙的错误(注意,我只是删除了批处理中的第六个 DDL 语句):

Connection c = getConnection();
c.setAutoCommit(true);
List<String> statements = Arrays.asList(
    "create table t(a int)",
    "select * from t",
    "alter table t add b int",
    "select * from t",
    "alter table t add c int",
    "select * from t",
    "alter table t add d int",
    "select * from t",
    "alter table t add e int",
    "select * from t"
);

for (String statement : statements)
    try (PreparedStatement s = c.prepareStatement(statement)) 
        System.out.println(s);
        s.execute();
    

似乎通过DISCARD ALL 丢弃所有缓存的计划应该可行,但它会使事情变得更糟:

Connection c = getConnection();
c.setAutoCommit(true);
List<String> statements = Arrays.asList(
    "create table t(a int)",
    "select * from t",
    "alter table t add b int",
    "select * from t",
    "alter table t add c int",
    "select * from t",
    "alter table t add d int",
    "select * from t",
    "alter table t add e int",
    "select * from t",
    "alter table t add f int",
    "discard all",
    "select * from t"
);

for (String statement : statements)
    try (PreparedStatement s = c.prepareStatement(statement)) 
        System.out.println(s);
        s.execute();
    

I'm running into another error message

“错误:准备好的语句“S_1”不存在”

有人知道解决方法吗?还是记录此错误的指针?有点意思,好像和default prepare threshold of 5有关

【问题讨论】:

您是否尝试过使用其他变量名?也许f 是某种保留关键字?此外,完整的堆栈跟踪也会有所帮助。 @a_horse_with_no_name:是的,但这是真正问题的简化版本,从游戏中删除准备好的语句并不容易 @aguibert ;) 好点。我试过了,但没有。无论如何,真实版本在名称周围使用引号...... 你检查过这个问题吗?看起来可能是重复:***.com/questions/2783813/… @aguibert:我确实有,而且它不是重复的,至少不是来自公认的答案(由 OP)声称事情是并行发生的。就我而言,所有语句都在一个会话中执行。 【参考方案1】:

这似乎与PostgreSQL's PREPARE_THRESHOLD, which defaults to 5 for the JDBC driver有关。

将其设置为零将解决/解决此特定问题:

 ((PGConnection) connection).setPrepareThreshold(0);

More info is also available in this stack overflow question

【讨论】:

您也可以通过 URL 参数更改此设置:jdbc.postgresql.org/documentation/94/…【参考方案2】:

禁用准备好的语句对于解决此问题而言过于激烈。您现在可以通过在 pgjdbc 连接设置上设置autosave=conservative 来解决具体问题,请参阅:https://***.com/a/48536394/924597

【讨论】:

以上是关于将 DDL 与 SELECT 通过 JDBC 混合时出现“错误:缓存计划不能更改结果类型”的主要内容,如果未能解决你的问题,请参考以下文章

GenerationTarget 遇到异常接受命令:通过 JDBC 语句执行 DDL 时出错

Spring Boot 应用程序 Heroku PostgreSQL 错误:GenerationTarget 遇到异常接受命令:执行 DDL 时出错 ...通过 JDBC 语句

无法打开 JDBC 连接以执行 DDL

通过JDBC语句执行DDL“drop sequence player_seq”时出错

org.hibernate.tool.schema.spi.CommandAcceptanceException:通过JDBC语句执行DDL时出错

HBASE基础使用Java API实现DDL与DML