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

huangapple go评论67阅读模式
英文:

How do I get a PostgreSQL execution plan with parameters?

问题

My application has a myriad of queries and I wanted to get the execution plan of some of them. Most if not all of the queries have multiple parameters and I can't find how to get the execution plan for any non-trivial query in PostgreSQL.

The real cases are much more involved but here's a simple, representative case of a query (just one parameter for simplicity):

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));
}

When I run this, I get the error:

ERROR: there is no parameter $1
Position: 34

If I hardcode the parameter (e.g. replacing $1 with 'a') everything works well and I get a plan. However, if I try to use a JDBC parameter it doesn't work. Hardcoding the parameter is not realistic for my use case since it can be a massive parameter or may not be rendered correctly as String (e.g. float value).

I also tried using null instead of ? and it doesn't crash, but it returns an execution plan that is wrong; it seems it somehow short-circuits the logic and returns something totally off reality.

What am I doing wrong?

英文:

My application has a myriad of queries and I wanted to get the execution plan of some of them. Most if not
all of the queries have multiple parameters and I can't find how to get the execution plan for any non-trivial query in PostgreSQL.

The real cases are much more involved but here's a simple, representative case of a query (just one parameter for simplicity):

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));
}

When I run this, I get the error:

> ERROR: there is no parameter $1
> Position: 34

If I hardcode the parameter (e.g. replacing $1 with 'a') everything works well and I get a plan. However, if I try to use a JDBC parameter it doesn't work. Hardcoding the parameter is not realistic for my use case since it can be a massive parameter or may not be rendered correctly as String (e.g. float value).

I also tried using null instead of ? and it doesn't crash, but
it returns an execution plan that is wrong; it seems it somehow short-circuits the logic and returns something totally off reality.

What am I doing wrong?

答案1

得分: 2

Here is the Chinese translation of the provided text:

不需要用绑定变量值替换$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()

如在以前标记为重复的问题中描述的,首次执行时会得到自定义计划(即在条件中看到变量,这里是id = 42),最后是通用计划(即带有谓词id = $1

您用null技巧失败了,因为PostgreSql知道id = null不返回任何内容(我使用模糊表述以避免既不是也不是),并生成了一个虚拟计划,例如One-Time Filter: false

您可以使用plan_cache_mode参数强制生成通用计划

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

在这种情况下,第一个解释的计划是通用的。似乎在这种设置下,null参数不会导致虚拟计划(但我不确定是否存在其他注意事项)。

所以我会建议您始终定义变量的数据类型 prepare x(integer) ...,但我不确定并且没有经验,是否可以通过force_generic_plan的组合和传递null(类似于Oracle功能)来获取有意义的执行计划,或者是否确实必须传递表示示例值或参数。

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

英文:

You don't need to replace the $1 with the bind variable value, but you can't apparently use the bind variable in the explain execute statement - here it must be stated.

So this sequence works fine (pseudocode)

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()

As described in the previously marked as duplicated question you get for first few execution a custom plan (i.e. you see the variable in the condition, here id = 42) and finaly a generic plan (i.e. with a predicate id = $1)

Your trick with null fails as PostgreSql knows that id = null returns nothing (I'm using vague formulation to avoid to be neither true nor false) and makes a dummy plan e.g. One-Time Filter: false

You may force to produce a generic plan with the plan_cache_mode parameter

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

in which case the the first explained plan is generic. It seem that with this setting the null parameter does not lead to a dummy plan (but I'm not sure if there are not other caveats).

So I'd say you should always define the data type of the variable prepare x(integer) ... but I'm not sure and have no experience if you can get a meaningfull execution plan with the combination of force_generic_plan and passing null (similar to Oracle functionality) or if you realy must pass representation sample value(s) or the parameter(s).

Final Note all the discussion here is focused on well designed prepared statements, i.e. any possible value passed will lead to a single same excution plan. For other statements it is not meaningfull, as there is no single execution plan.

答案2

得分: 0

你可以尝试这一行

PreparedStatement ps1 = conn.prepareStatement(
  "prepare x as select * from documents where content = '" + 1 + "'");
ps1.execute();

而不是

PreparedStatement ps1 = conn.prepareStatement(
  "prepare x as select * from documents where content = $1");
ps1.execute();
英文:

you can try this line

PreparedStatement ps1 = conn.prepareStatement(
  "prepare x as select * from documents where content = '"+ 1 +"'");
ps1.execute();

instead of

   PreparedStatement ps1 = conn.prepareStatement(
      "prepare x as select * from documents where content = $1");
    ps1.execute();

huangapple
  • 本文由 发表于 2023年5月10日 21:34:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/76219095.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定