WHERE子句是否按其编写顺序应用?


36

我正在尝试优化查询到一个大表(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')
    )

WHERE日期范围的子句是否在子查询之前执行?为了使执行更快,将限制性最强的子句放在首位以避免其他子句出现大循环的好方法吗?

现在,查询需要执行大量时间。

Answers:


68

要详细说明@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
);

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

仅发现查询计划者已经弄清楚您正在尝试的技巧并自动应用它们,尝试优化查询的情况并不少见,因此手动优化的版本并不比原始版本更好。

局限性

规划器/优化器远非全能,并且受到以下限制:必须绝对确定它不能更改查询的效果,用于决策的可用数据,已实现的规则以及CPU时间它可以花钱考虑优化。例如:

  • 计划者依赖于保存的统计数据ANALYZE(通常通过自动清理)。如果这些都已过时,则计划选择可能会很糟糕。

  • 统计信息仅是样本,因此由于抽样效应,它们可能会产生误导,尤其是在样本量太小的情况下。可能会选择错误的计划。

  • 统计信息无法跟踪有关表的某些数据,例如列之间的相关性。当假定事物不是独立的时,这可能会导致计划者做出错误的决定。

  • 计划者依赖于成本参数,例如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')
    )

没有什么能阻止它被平整为带有一组额外联接的简单查询,而且很有可能会。

结果可能是这样的(显然,未经测试):

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';

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

如何查看优化器的工作

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

无法将查询计划或内部计划树“解析”回SQL。

http://explain.depesz.com/具有不错的查询计划助手。如果您是查询计划等的新手(在这种情况下,我很惊讶您通过本文完成了这么远),则PgAdmin拥有一个图形化查询计划查看器,它提供的信息少得多,但更简单。

相关阅读:

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


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


哇...相当完整的答案:-)我对Postgresql(以及其他著名的DB引擎,如Oracle)的计划很慢的情况之一是列之间的关联或多个关联的联接。考虑到计划的这一步只有几行,而实际上却有成千上万行,它通常会导致嵌套循环。优化这类查询的一种方法是“设置enable_nestloop = off;”。在查询期间。
alci

我遇到了一种情况,在一个简单的7 where子句查询中,v9.5.5试图在检查是否可以应用TO_DATE之前应用TO_DATE。顺序很重要。
user1133275'2

@ user1133275在那种情况下,它只是偶然地为您工作,因为计算成本估算值是相同的。PostgreSQL可能仍会决定to_date在检入某些更高版本之前或由于某些优化统计信息的更改而运行。要可靠地在仅应在检查之后运行的功能之前运行检查,请使用一条CASE语句。
Craig Ringer '02

我在SO上见过的最棒的答案之一!竖起大拇指,伙计!
62mkv

我确实遇到过这样的情况:对于简单查询,添加order by查询比没有查询要快得多order by。这就是我用联接来编写查询的原因之一,就像我想执行它们一样-拥有出色的优化器很高兴,但是我完全不相信自己的命运,不考虑如何写查询就不明智它may be执行了...很好的答案!
Greg0ry

17

SQL是一种声明性语言:您说的是您要的内容,而不是方法。RDBMS将选择执行查询的方式,称为执行计划。

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

在大多数情况下,这确实是最好的选择,但是有时数据库引擎会做出错误的选择,从而导致查询速度非常慢。


应当指出,在某些RDBMS上,查询顺序仍然很重要,但是对于更高级的查询,您所说的一切在实践和理论上都是正确的。当查询计划者选择错误的执行顺序选择时,通常可以使用查询提示将其推向更有效的方向(例如WITH(INDEX(<index>))在MSSQL中强制为特定联接选择索引)。
David Spillett

问题是date_day实际上是否存在某个索引。如果没有,则优化器没有太多可比较的计划。
jkavalik 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.