我正在尝试优化查询到一个大表(3,700万行)的查询,并对在查询中执行操作的顺序有疑问。

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子句吗?
是否为了避免执行其他子句大循环,将限制性最强的子句放在首位是一个好方法,以便更快地执行?

现在查询要花费很多时间。

#1 楼

要详细说明@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之类的成本参数来告诉它,其所安装的特定系统上各种操作的相对速度。这些只是指南。如果错误很严重,则可能会导致错误的计划选择。
任何带有LIMITOFFSET的子查询都无法展平或受到上拉/下推操作。但是,这并不意味着它会在外部查询的所有部分之前执行,甚至根本不会执行。
如果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的图形查询计划查看器提供的信息要少得多,但更简单。

相关阅读:


Optimizer
查询计划参数
EXPLAIN
CTE(WITH查询)
我的博客关于CTE作为优化围栏

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


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

#2 楼

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

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

在大多数情况下,它实际上是最佳策略,但有时数据库引擎将做出错误的选择,导致查询速度非常慢。

评论


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

– David Spillett
15年9月22日在8:03

问题是,date_day上是否存在某些索引。如果没有,则优化器没有很多可比较的计划。

– jkavalik
15年9月22日在10:01