要详细说明@alci的答案:
PostgreSQL不在乎您以什么顺序写东西
PostgreSQL根本不在乎WHERE
子句中条目的顺序,而是仅根据成本和选择性估计来选择索引和执行顺序。
在配置之前,连接的写入顺序也将被忽略join_collapse_limit
;如果连接多于此,它将按照写入顺序执行它们。
子查询可以在包含子查询的查询之前或之后执行,这取决于最快的执行方式,只要在外部查询实际需要信息之前执行子查询即可。实际上,子查询通常在中间执行,或者与外部查询交错执行。
不能保证PostgreSQL实际上会执行部分查询。它们可以被完全优化掉。如果您调用具有副作用的函数,则这一点很重要。
PostgreSQL将改变您的查询
PostgreSQL将在保持完全相同的效果的同时对查询进行大量转换,以使查询运行得更快而又不改变结果。
子查询外部的术语可以下推到子查询中,因此它们作为子查询的一部分执行,而不是您在外部查询中写的术语所在的位置
子查询中的术语可以上拉到外部查询,因此它们的执行是外部查询的一部分,而不是您在子查询中写入它们的位置
子查询可以并且经常被扁平化为外部表上的联接。EXISTS
和NOT 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 0
hack。如果发现这种情况,请报告查询计划程序错误。
如果您真的很热衷,您还可以使用该debug_print_plans
选项来查看原始查询计划,但是我保证您不想阅读它。真。