我的应用程序有无数的查询,我想得到其中一些查询的执行计划。大多数(如果不是所有的话)查询都有多个参数,我无法找到如何在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
而不是?
,它没有崩溃,但它返回一个错误的执行计划;它似乎在某种程度上短路了逻辑,返回了一些完全脱离现实的东西。
我做错了什么?
1条答案
按热度按时间qnakjoqk1#
你不需要用 bind variable value 替换
$1
,但是显然你不能在explain execute
语句中使用bind变量-这里必须声明。所以这个序列工作得很好(伪代码)
如前面标记为重复的question中所述,您将在前几次执行中获得一个 * 自定义计划 *(即你可以看到条件中的变量,这里是
id = 42
),最后是一个 generic plan(即使用 predicateid = $1
)使用
null
的 * 技巧 * 失败了,因为PostgreSQL知道id = null
没有返回任何东西(我使用模糊的公式来避免既不是 * 真 * 也不是 * 假 *),并制定了一个 * 哑 * 计划,例如:One-Time Filter: false
您可以使用
plan_cache_mode
参数强制生成一个 generic plan在这种情况下,第一个解释的计划是通用的。看起来,通过这种设置,
null
参数不会导致虚拟计划(但我不确定是否有其他警告)。因此,我认为你应该始终定义变量
prepare x(integer) ...
的数据类型,但我不确定,也没有经验,如果你可以通过force_generic_plan
和传递null
的组合(类似于Oracle功能)获得有意义的执行计划,或者如果你真的必须传递表示样本值或参数。最后一点这里的所有讨论都集中在 * 精心设计的准备声明 * 上,即传递的任何可能的值将导致单个相同的执行计划。对于 * 其他 * 语句,它没有意义,因为 * 没有单一的执行计划 *。