WHERE 子句是否按照它们的编写顺序应用?

Jor*_*hez 47 postgresql performance postgresql-performance

我正在尝试优化一个查询,该查询查看一个大表(3700 万行),并有一个关于在查询中执行操作的顺序的问题。

select 1 
from workdays day
where day.date_day >= '2014-10-01' 
    and day.date_day <= '2015-09-30' 
    and day.offer_id in (
        select offer.offer_day 
        from offer  
        inner join province on offer.id_province = province.id_province  
        inner join center cr on cr.id_cr = province.id_cr 
        where upper(offer.code_status) <> 'A' 
            and province.id_region in ('10' ,'15' ,'21' ,'26' ,'31' , ...,'557') 
            and province.id_cr in ('9' ,'14' ,'20' ,'25' ,'30' ,'35' ,'37')
    )
Run Code Online (Sandbox Code Playgroud)

WHERE日期范围的子句是否在子查询之前执行?将最严格的子句放在首位以避免其他子句的大循环,以便更快地执行是否是一种好方法?

现在查询需要很多时间来执行。

Cra*_*ger 81

详细说明@alci的回答:

PostgreSQL 不在乎你写东西的顺序

  • PostgreSQL 根本不关心WHERE子句中条目的顺序,仅根据成本和选择性估计来选择索引和执行顺序。

  • 写入连接的顺序也被忽略,直到配置的join_collapse_limit; 如果有更多的连接,它会按照它们的写入顺序执行它们。

  • 子查询可以在包含它们的查询之前或之后执行,这取决于什么是最快的,只要在外部查询实际需要信息之前执行子查询即可。实际上,子查询通常在中间执行,或者与外部查询交错执行。

  • 不能保证 PostgreSQL 会实际执行部分查询。它们可以完全优化掉。如果您调用具有副作用的函数,这一点很重要。

PostgreSQL 将转换您的查询

PostgreSQL 将对查询进行大量转换,同时保留完全相同的效果,以便在不改变结果的情况下使它们运行得更快。

  • 子查询之外的术语可以下推到子查询中,因此它们作为子查询的一部分执行,而不是您在外部查询中编写它们的位置

  • 子查询中的术语可以被拉到外部查询中,因此它们的执行作为外部查询的一部分完成,而不是您在子查询中编写它们的位置

  • 子查询可以并且经常被展为外部表上的连接。EXISTSNOT EXISTS查询之类的事情也是如此。

  • 视图被扁平化到使用视图的查询中

  • SQL 函数经常被内联到调用查询中

  • ...还有许多其他对查询进行的转换,例如常量表达式预评估、某些子查询的去相关以及各种其他规划器/优化器技巧。

一般来说,PostgreSQL 可以大规模地转换和重写您的查询,以至于每个查询:

select my_table.*
from my_table
left join other_table on (my_table.id = other_table.my_table_id)
where other_table.id is null;

select *
from my_table
where not exists (
  select 1
  from other_table
  where other_table.my_table_id = my_table.id
);

select *
from my_table
where my_table.id not in (
  select my_table_id
  from other_table
  where my_table_id is not null
);
Run Code Online (Sandbox Code Playgroud)

通常都会产生完全相同的查询计划。(假设我在上面没有犯任何愚蠢的错误)。

尝试优化查询却发现查询规划器已经找出了您正在尝试的技巧并自动应用它们的情况并不少见,因此手动优化的版本并不比原始版本好。

限制

规划器/优化器远非无所不能,并且受限于必须绝对确定它不能改变查询的效果、可用于决策的数据、已实施的规则以及 CPU 时间它可以花时间思考优化。例如:

  • 规划器依赖于ANALYZE(通常通过 autovacuum)保存的统计数据。如果这些已经过时,则计划选择可能很糟糕。

  • 统计数据只是一个样本,因此由于抽样效应,它们可能会产生误导,特别是如果样本太小。可能会导致错误的计划选择。

  • 统计信息不会跟踪有关表的某些类型的数据,例如列之间的相关性。这可能会导致计划者在假设事物独立时做出错误的决定,而实际上它们不是。

  • 规划器依赖于成本参数,例如random_page_cost告诉它安装在特定系统上的各种操作的相对速度。这些只是指南。如果他们大错特错,他们可能会导致糟糕的计划选择。

  • 任何带有LIMIT或 的子查询OFFSET都不能展平或受到上拉/下推。这并不意味着它会在外部查询的所有部分之前执行,虽然,甚至可以说,它会执行在所有

  • CTE 术语(WITH查询中的子句)总是完整地执行,如果它们被执行的话。它们不能被展平,也不能跨 CTE 术语屏障向上或向下推压术语。CTE 术语始终在最终查询之前执行。这是非 SQL 标准行为,但它被记录为 PostgreSQL 的工作方式。

  • PostgreSQL 在对外部表、security_barrier视图和某些其他特殊类型的关系的查询上进行优化的能力有限

  • PostgreSQL 不会内联用普通 SQL 以外的任何东西编写的函数,也不会在它和外部查询之间进行上拉/下推。

  • 规划器/优化器在选择表达式索引以及索引和表达式之间微不足道的数据类型差异方面真的很愚蠢。

还有更多。

您的查询

在您的查询的情况下:

select 1 
from workdays day
where day.date_day >= '2014-10-01' 
    and day.date_day <= '2015-09-30' 
    and day.offer_id in (
        select offer.offer_day 
        from offer  
        inner join province on offer.id_province = province.id_province  
        inner join center cr on cr.id_cr = province.id_cr 
        where upper(offer.code_status) <> 'A' 
            and province.id_region in ('10' ,'15' ,'21' ,'26' ,'31' , ...,'557') 
            and province.id_cr in ('9' ,'14' ,'20' ,'25' ,'30' ,'35' ,'37')
    )
Run Code Online (Sandbox Code Playgroud)

没有什么能阻止它通过一组额外的连接被扁平化为一个更简单的查询,而且很可能会。

它可能会变成这样(显然未经测试):

select 1 
from workdays day
inner join offer on day.offer_id = offer.offer_day
inner join province on offer.id_province = province.id_province  
inner join center cr on cr.id_cr = province.id_cr 
where upper(offer.code_status) <> 'A' 
   and province.id_region in ('10' ,'15' ,'21' ,'26' ,'31' , ...,'557') 
   and province.id_cr in ('9' ,'14' ,'20' ,'25' ,'30' ,'35' ,'37')
   and day.date_day >= '2014-10-01' 
   and day.date_day <= '2015-09-30';
Run Code Online (Sandbox Code Playgroud)

然后 PostgreSQL 将根据其选择性和行数估计以及可用索引优化连接顺序和连接方法。如果这些合理地反映了现实,那么它将进行连接并以任何最佳顺序运行 where 子句条目 - 通常将它们混合在一起,所以它会先做一点,然后再做一点,然后回到第一部分, 等等。

如何查看优化器做了什么

您看不到 PostgreSQL 将查询优化为的 SQL,因为它将 SQL 转换为内部查询树表示,然后对其进行修改。您可以转储查询计划并将其与其他查询进行比较。

没有办法将该查询计划或内部计划树“解析”回 SQL。

http://explain.depesz.com/有一个不错的查询计划助手。如果您对查询计划等完全陌生(在这种情况下,我很惊讶您通过这篇文章做到了这一点),那么 PgAdmin 有一个图形查询计划查看器,它提供的信息要少得多,但更简单。

相关阅读:

下推/上拉和展平功能在每个版本中都在不断改进。PostgreSQL在上拉/下推/展平决策方面通常是正确的,但并非总是如此,因此偶尔您必须(ab)使用 CTE 或OFFSET 0hack。如果发现这种情况,请报告查询计划程序错误。


如果您真的非常热衷,您还可以使用该debug_print_plans选项查看原始查询计划,但我保证您不想阅读它。真的。


小智 19

SQL 是一种声明性语言:你告诉你想要什么,而不是如何去做。RDBMS 将选择执行查询的方式,称为执行计划。

曾几何时(5-10 年前),查询的编写方式对执行计划有直接影响,但如今,大多数 SQL 数据库引擎都使用基于成本的优化器进行计划。也就是说,它将根据其对数据库对象的统计信息评估执行查询的不同策略,并选择最佳策略。

大多数时候,它确实是最好的,但有时数据库引擎会做出错误的选择,导致查询非常缓慢。