java 如何获取带参数的PostgreSQL执行计划?

3z6pesqy  于 2023-05-12  发布在  Java
关注(0)|答案(1)|浏览(130)

我的应用程序有无数的查询,我想得到其中一些查询的执行计划。大多数(如果不是所有的话)查询都有多个参数,我无法找到如何在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参数,它就不起作用。硬编码参数对于我的用例来说是不现实的,因为它可能是一个巨大的参数,或者可能不能正确地呈现为String(例如浮点值)。
我还尝试使用null而不是?,它没有崩溃,但它返回一个错误的执行计划;它似乎在某种程度上短路了逻辑,返回了一些完全脱离现实的东西。
我做错了什么?

qnakjoqk

qnakjoqk1#

你不需要用 bind variable value 替换$1,但是显然你不能在explain execute语句中使用bind变量-这里必须声明。
所以这个序列工作得很好(伪代码)

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中所述,您将在前几次执行中获得一个 * 自定义计划 *(即你可以看到条件中的变量,这里是id = 42),最后是一个 generic plan(即使用 predicate id = $1
使用null的 * 技巧 * 失败了,因为PostgreSQL知道id = null没有返回任何东西(我使用模糊的公式来避免既不是 * 真 * 也不是 * 假 *),并制定了一个 * 哑 * 计划,例如:One-Time Filter: false
您可以使用plan_cache_mode参数强制生成一个 generic plan

con.createStatement().execute("set plan_cache_mode = force_generic_plan")

在这种情况下,第一个解释的计划是通用的。看起来,通过这种设置,null参数不会导致虚拟计划(但我不确定是否有其他警告)。
因此,我认为你应该始终定义变量prepare x(integer) ...的数据类型,但我不确定,也没有经验,如果你可以通过force_generic_plan和传递null的组合(类似于Oracle功能)获得有意义的执行计划,或者如果你真的必须传递表示样本值或参数。

最后一点这里的所有讨论都集中在 * 精心设计的准备声明 * 上,即传递的任何可能的值将导致单个相同的执行计划。对于 * 其他 * 语句,它没有意义,因为 * 没有单一的执行计划 *。

相关问题