什么时候(SELECT)查询计划?

zgp*_*max 6 postgresql perl

在PostgreSQL中,什么时候(SELECT)查询计划?

是吗:

  1. 在声明准备时间,或
  2. 在处理SELECT的开始,或
  3. 别的

我问的原因是存在Stackoverflow问题:相同的查询,两种不同的方式,性能差异很大

很多人似乎认为查询的计划方式不同,因为在一种情况下查询包含字符串文字('foo'),在另一种情况下,它是一个占位符(?).

现在我的想法是,这是一个红色的鲱鱼,因为查询不是在语句准备时计划的,而是实际计划在SELECT时间.

因此,比如说,我可以使用占位符准备一个语句,然后使用不同的绑定值多次运行查询,并为每个不同的绑定值运行查询计划程序.

我怀疑上面链接问题归结为值的PostgreSQL数据类型,在'foo'文字的情况下,已知它是一个字符串,但在占位符的情况下,类型不能被判断,所以是作为一些奇怪的类型进入查询规划器,它无法创建一个有效的计划.在这种情况下,问题不在于查询的计划方式不同,因为该值本身就是占位符(在语句准备时),但该值是以不同的PostgreSQL类型传递给查询的,这就是影响查询计划器.要解决这个问题,只需将占位符与适当的显式类型声明绑定即可.

A.H*_*.H. 10

我不能谈论客户端的Perl接口本身,但我可以对PostgreSQL服务器端有所了解.

PostgreSQL准备了陈述和毫无准备的陈述.毫无准备的语句会立即被解析,计划和执行.它们也不支持参数替换.在普通psqlshell上,您可以像这样显示他们的查询计划:

tmpdb> explain select * from sometable where flag = true;
Run Code Online (Sandbox Code Playgroud)

另一方面,有准备好的陈述:它们通常(参见下面的"例外")在一个步骤中解析和计划,并在第二步中执行.它们可以使用不同的参数重新执行几次,因为它们确实支持参数替换.相当于psql:

tmpdb> prepare foo as select * from sometable where flag = $1;
tmpdb> explain execute foo(true);
Run Code Online (Sandbox Code Playgroud)

您可能会看到,该计划与未准备好的声明中的计划不同,因为计划确实已经在PREPAREprepare文档中描述的阶段中进行:

执行PREPARE语句时,将解析,重写和计划指定的语句.随后发出EXECUTE命令时,只需执行准备好的语句.因此,解析,重写和计划阶段仅执行一次,而不是每次执行语句.

这也意味着,该计划不是用于取代参数优化:在第一个例子可以用一个指标flag,因为PostgreSQL的人都知道一万个条目中仅十分具有价值true.当PostgreSQL使用预准备语句时,这种推理是不可能的.在这种情况下,会创建一个计划,该计划将尽可能好地适用于所有可能的参数值.这可能会排除提到的索引,因为通过随机访问(由于索引)获取整个表的更好部分比普通顺序扫描慢.该PREPARE文档证实了这一点:

在某些情况下,为预准备语句生成的查询计划将不如在语句已正确提交和执行时选择的查询计划.这是因为在计划语句并且计划程序尝试确定最佳查询计划时,语句中指定的任何参数的实际值都不可用.PostgreSQL收集有关表中数据分布的统计信息,并且可以在语句中使用常量值来猜测执行语句的可能结果.由于在计划带参数的预准备语句时此数据不可用,因此所选计划可能不是最理想的.

顺便说一句 - 关于计划缓存,PREPARE文档也有话要说:

准备语句仅持续当前数据库会话的持续时间.会话结束时,忘记了准备好的语句,因此必须重新创建它才能再次使用.

此外,没有自动计划缓存,也没有多个连接的缓存/重用.

例外:我已经提到"通常".显示的psql示例不是Perl DBI真正使用的客户端适配器.它使用某种协议.这里术语"简单查询"对应于"未准备的查询" psql,术语" 扩展查询 "对应于"准备好的查询",但有一个例外:(一个)"未命名语句"和(可能是多个)之间存在区别"命名陈述".关于命名陈述,该文件说:

也可以使用PREPARE和EXECUTE在SQL命令级别创建和访问命名的预准备语句.

并且:

处理Parse消息时,会发生对命名的准备语句对象的查询计划.

因此,在这种情况下,计划是在没有如上所述的参数的情况下完成的PREPARE- 没有什

提到的例外是"未命名的声明".医生说:

如果Parse消息未定义参数,则在Parse处理期间同样计划未命名的预准备语句.但是如果有参数,则每次提供Bind参数时都会进行查询计划.这允许规划器利用每个Bind消息提供的参数的实际值,而不是使用通用估计.

这里有好处:虽然未命名的语句是"准备好的"(即可以有参数替换),但它也可以使查询计划适应实际参数.

BTW:在过去的PostgreSQL服务器版本中,未命名语句的确切处理已经多次改变.如果您真的想要,可以查找旧文档以获取详细信息.

理由 - Perl /任何客户:

像Perl这样的客户端如何使用协议是一个完全不同的问题.一些客户端像Java的JDBC驱动程序基本上说:即使程序员使用预准备语句,前五个(或左右)执行内部映射到"简单查询"(即实际上没有准备),之后驱动程序切换到"命名陈述".

所以客户有这些选择:

  • 每次使用"简单查询"协议强制(重新)规划.
  • 计划一次,使用"扩展查询"协议和"命名语句"执行多次(计划可能不好,因为计划是在没有参数的情况下完成的).
  • 解析一次,计划每次执行(使用当前的PostgreSQL版本),使用"扩展查询"协议和"未命名语句"并遵守更多内容(在"解析"消息期间提供一些参数)
  • 玩完全不同的技巧,如JDBC驱动程序.

Perl目前做了什么:我不知道.但提到的"红鲱鱼"并非不太可能.