我的应用程序有无数的查询,我想得到其中一些的执行计划。大多数如果不是 所有的查询都有多个参数,我找不到如何在 PostgreSQL 中获取任何非平凡查询的执行计划。
真实的案例要复杂得多,但这里有一个简单的、有代表性的查询案例(为简单起见,只有一个参数):
Connection conn = DriverManager.getConnection("...", "...", "...");
PreparedStatement ps1 = conn.prepareStatement(
"prepare x as select * from documents where content = $1");
ps1.execute();
PreparedStatement ps2 = conn.prepareStatement(
"explain (format json) execute x (?)");
ps2.setString(1, "Very long content here..."); // Binds the parameter
ResultSet rs = ps2.executeQuery(); // Error here!
while (rs.next()) {
System.out.println(rs.getString(1));
}
当我运行它时,出现错误:
错误:没有参数 $1 位置:34
如果我硬编码参数(例如用
$1
替换'a'
)一切正常,我得到了一个计划。但是,如果我尝试使用 JDBC 参数,它就不起作用。对参数进行硬编码对于我的用例来说是不现实的,因为它可能是一个巨大的参数,或者可能无法正确呈现为字符串(例如浮点值)。
我也尝试使用
null
而不是 ?
并且它没有崩溃,但是
它返回一个错误的执行计划;似乎它以某种方式使逻辑短路并返回完全脱离现实的东西。
我做错了什么?
您不需要用
绑定变量值替换
$1
,但是您显然不能在explain execute
语句中使用绑定变量——这里必须说明。
所以这个序列工作正常(伪代码)
prep = con.prepareStatement("prepare x(integer) as select id, pad from jdbn.document where id = $1")
prep.execute();
stmt = con.prepareStatement("explain execute x (42)")
rs = stmt.executeQuery()
如先前标记为重复的 question 中所述,对于前几次执行,您将获得一个 custom plan(即您在条件中看到变量,在这里
id = 42
),最后是一个 generic plan(即带有谓词id = $1
)
你的 trick 与
null
失败,因为 PostgreSql 知道 id = null
什么都不返回(我使用模糊的公式来避免既不是 true 也不是 false)并制定一个 dummy 计划,例如One-Time Filter: false
参数的通用计划
con.createStatement().execute("set plan_cache_mode = force_generic_plan")
在这种情况下,第一个解释的计划是通用的。似乎通过此设置,
null
参数不会导致虚拟计划(但我不确定是否没有其他警告)。
所以我想说你应该总是定义变量的数据类型
prepare x(integer) ...
但是我不确定并且没有经验如果你可以通过force_generic_plan
和传递null
的组合获得有意义的执行计划(类似于 Oracle 功能),或者如果您真的必须传递表示样本值或参数。
Final Note 这里所有的讨论都集中在精心设计的准备语句,即任何可能传递的值都将导致一个相同的执行计划。对于 other statements 它没有意义,因为没有 no single execution plan.