是吗:
>在声明准备时间,或
>在处理SELECT的开始时,或
>别的
我问的原因是存在Stackoverflow问题:same query,two different ways,vastly different performance
很多人似乎认为查询的计划方式不同,因为在一种情况下,查询包含字符串文字(‘foo’),在另一种情况下,它是一个占位符(?).
现在我的想法是,这是一个红色的鲱鱼,因为查询不是在语句准备时计划的,而是实际计划在SELECT时间.
因此,比如说,我可以使用占位符准备一个语句,然后使用不同的绑定值多次运行查询,并为每个不同的绑定值运行查询计划程序.
我怀疑question linked above归结为该值的Postgresql数据类型,在’foo’文字的情况下,已知它是一个字符串,但在占位符的情况下,该类型不能被判断,所以正在查询计划程序作为一些奇怪的类型,它无法创建一个有效的计划.在这种情况下,问题不在于查询的计划方式不同,因为该值本身就是占位符(在语句准备时),但该值是以不同的Postgresql类型传递给查询的,这就是影响查询计划器.要解决这个问题,只需将占位符与适当的显式类型声明绑定即可.
解决方法
Postgresql准备了陈述和毫无准备的陈述.毫无准备的语句会立即被解析,计划和执行.它们也不支持参数替换.在普通的psql shell上,您可以像这样显示他们的查询计划:
tmpdb> explain select * from soMetable where flag = true;
另一方面,有准备好的陈述:它们通常(参见下面的“例外”)在一个步骤中解析和计划,并在第二步中执行.它们可以使用不同的参数重新执行几次,因为它们确实支持参数替换. psql中的等价物如下:
tmpdb> prepare foo as select * from soMetable where flag = $1; tmpdb> explain execute foo(true);
您可能会看到,该计划与未准备好的声明中的计划不同,因为计划确实已经在准备阶段进行,如PREPARE的文档中所述:
When the PREPARE statement is executed,the specified statement is parsed,rewritten,and planned. When an EXECUTE command is subsequently issued,the prepared statement need only be executed. Thus,the parsing,rewriting,and planning stages are only performed once,instead of every time the statement is executed.
这也意味着,该计划未针对替换参数进行优化:在第一个示例中可能使用标记索引,因为Postgresql知道在一百万个条目中只有十个具有值true.当Postgresql使用预准备语句时,这种推理是不可能的.在这种情况下,会创建一个计划,该计划将尽可能好地适用于所有可能的参数值.这可能会排除提到的索引,因为通过随机访问(由于索引)获取整个表的更好部分比普通顺序扫描慢. PREPARE doc证实了这一点:
In some situations,the query plan produced for a prepared statement will be inferior to the query plan that would have been chosen if the statement had been submitted and executed normally. This is because when the statement is planned and the planner attempts to determine the optimal query plan,the actual values of any parameters specified in the statement are unavailable. Postgresql collects statistics on the distribution of data in the table,and can use constant values in a statement to make guesses about the likely result of executing the statement. Since this data is unavailable when planning prepared statements with parameters,the chosen plan might be suboptimal.
BTW – 关于计划缓存,PREPARE doc也有话要说:
Prepared statements only last for the duration of the current database session. When the session ends,the prepared statement is forgotten,so it must be recreated before being used again.
此外,没有自动计划缓存,也没有多个连接的缓存/重用.
例外:我已经提到“通常”.显示的psql示例不是Perl DBI真正使用的客户端适配器.它使用某个protocol.这里术语“简单查询”对应于psql中的“未准备查询”,术语“extended query”对应于“准备好的查询”,但有一个例外:(一个)“未命名语句”之间存在区别和(可能是多个)“命名陈述”.关于命名陈述,doc说:
Named prepared statements can also be created and accessed at the sql command level,using PREPARE and EXECUTE.
并且:
Query planning for named prepared-statement objects occurs when the Parse message is processed.
所以在这种情况下,计划是在没有参数的情况下完成的,如上所述的PREPARE – 没有什
提到的例外是“未命名的声明”.医生说:
The unnamed prepared statement is likewise planned during Parse processing if the Parse message defines no parameters. But if there are parameters,query planning occurs every time Bind parameters are supplied. This allows the planner to make use of the actual values of the parameters provided by each Bind message,rather than use generic estimates.
这是好处:虽然未命名的语句是“准备好的”(即可以有参数替换),但它也可以使查询计划适应实际参数.
BTW:在过去的Postgresql服务器版本中,未命名语句的确切处理已经多次改变.如果您真的想要,可以查找旧文档以获取详细信息.
理由 – Perl /任何客户:
像Perl这样的客户端如何使用协议是一个完全不同的问题.一些客户端像Java的JDBC驱动程序基本上说:即使程序员使用预准备语句,前五个(或左右)执行内部映射到“简单查询”(即实际上没有准备),之后驱动程序切换到“命名陈述“.
所以客户有这些选择:
>每次使用“简单查询”协议强制(重新)规划.
>计划一次,使用“扩展查询”协议和“命名语句”执行多次(计划可能不好,因为计划是在没有参数的情况下完成的).
>解析一次,使用“扩展查询”协议和“未命名语句”计划每次执行(使用当前Postgresql版本)并遵守更多内容(在“解析”消息期间提供一些参数)
>玩完全不同的技巧,如JDBC驱动程序.
Perl目前做了什么:我不知道.但提到的“红鲱鱼”并非不太可能.