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

时间:2012-02-14 12:37:58

标签: perl postgresql

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

是吗:

  1. 在声明准备时间,或
  2. 在处理SELECT开始时,或
  3. 别的东西
  4. 我问的原因是存在Stackoverflow问题:same query, two different ways, vastly different performance

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

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

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

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

1 个答案:

答案 0 :(得分:10)

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

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

tmpdb> explain select * from sometable where flag = true;

另一方面,有准备好的陈述:它们通常(参见&#34;例外&#34;下面)一步解析和计划,然后在第二步执行。它们可以使用不同的参数重新执行几次,因为它们执行支持参数替换。 psql中的等价物是:

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

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

  

执行PREPARE语句时,指定的语句已解析,已重写,且已计划。当EXECUTE命令后续发出时,只需要执行准备好的语句。因此,解析,重写和计划阶段只执行一次,而不是每次执行语句。

这也意味着该计划 NOT 针对替代参数进行了优化:在第一个示例中可能使用true的索引,因为PostgreSQL知道在一百万个条目中只有十个有值psql。当PostgreSQL使用预准备语句时,这种推理是不可能的。在这种情况下,会创建一个计划,该计划将尽可能好地适用于所有可能的参数值。此可能排除提到的索引,因为通过随机访问(由于索引)获取整个表的更好部分比普通顺序扫描慢。 PREPARE doc确认了这一点:

  

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

BTW - 关于计划缓存,PREPARE doc也有话要说:

  

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

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

EXCEPTION :我已提及&#34;通常&#34;。显示的psql示例不是Perl DBI真正使用的客户端适配器。它使用某个PREPARE。这里的术语&#34;简单查询&#34;对应于未准备好的查询&#34;在PREPARE中,术语&#34; protocol&#34;对应于准备好的查询&#34;除了一个例外:(一)&#34;未命名陈述&#34;和(可能是多个)&#34;命名陈述&#34;。关于命名陈述,extended query说:

  

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

还有:

  

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

因此,在这种情况下,计划是在没有参数的情况下完成的,如{{1}}所述 - 没什么新的。

提到的例外是&#34;未命名的声明&#34;。医生说:

  

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

这就是好处:虽然未命名的陈述是&#34;准备好的&#34; (即可以进行参数替换),它也可以使查询计划适应实际参数。

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

理由 - Perl /任何客户

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

所以客户有这些选择:

  • 每次使用&#34;简单查询&#34;强制(重新)计划。协议
  • 计划一次,使用&#34;扩展查询执行多次&#34;协议和&#34;命名声明&#34; (计划可能不好,因为计划是在没有参数的情况下完成的。)
  • 解析一次,使用&#34;扩展查询&#34;计划每次执行(使用当前PostgreSQL版本)协议和&#34;未命名的声明&#34;并且服从更多的东西(在#34;解析&#34;消息期间提供一些参数)
  • 玩完全不同的技巧,比如JDBC驱动程序。

Perl目前做了什么:我不知道。但提到的红鲱鱼&#34;并非不太可能。