为什么添加ORDER BY可以大大加快查询速度?

时间:2018-12-12 18:37:43

标签: sql postgresql sql-order-by query-performance sql-limit

我在PostgreSQL中发现了一些非常奇怪和违反直觉的行为。

我具有如下查询结构。我正在从子查询中选择ID和计数。子查询执行过滤,连接,计数,但仅按ID排序,因为我使用DISTINCT ON()仅获得唯一的ID。

然后,外部查询将进行正确的排序,并根据需要进行任何限制和偏移。这是查询结构的示例:

SELECT s.id, s.item_count
FROM (
    SELECT DISTINCT ON (work_items.id) work_items.id
        , work_item_states.disposition AS disposition
        , COUNT(work_items.id) OVER () AS item_count
    FROM work_items
    JOIN work_item_states ON work_item_states.work_item_refer = work_items.id
    WHERE work_item_states.disposition = 'cancelled'
    ORDER BY work_items.id
) AS s
ORDER BY s.disposition
LIMIT 50
OFFSET 0

但是我发现了一些奇怪的东西。我的数据库有数百万个条目,因此总体查询并不是最快的。但是,当我在 OUTER 查询中删除ORDER BY子句时,它会大大降低查询时间。

但是,如果我也删除了LIMIT子句,尽管该示例查询返回的结果超过800 000,但它又变得很快。

总而言之,对于外部查询:

订购 LIMIT -快速

...
) AS s
ORDER BY s.disposition
LIMIT 50
OFFSET 0

LIMIT -非常慢

...
) AS s
LIMIT 50
OFFSET 0

订购-快速,尽管有80万个结果

...
) AS s
ORDER BY s.disposition
OFFSET 0

NEITHER-快速,尽管有80万个结果

...
) AS s
OFFSET 0

要想知道仅使用LIMIT子句的速度要慢得多,无论是两者都不是,或者只有ORDER BY,查询所花费的时间不会超过10秒。

但是,仅使用LIMIT子句,查询大约需要15分钟,是查询时间的7倍!

您可能会认为ORDER BY会减慢速度,因为它必须对子查询的结果进行排序,但事实并非如此。这很违反直觉。

如果有人知道这里幕后发生的事情,我将不胜感激他们对此有所了解。

谢谢

编辑-添加了语句的执行计划:

ORDER BY和LIMIT执行计划

Limit  (cost=518486.52..518486.65 rows=50 width=53)
  ->  Sort  (cost=518486.52..520495.59 rows=803628 width=53)
        Sort Key: s.disposition
        ->  Subquery Scan on s  (cost=479736.16..491790.58 rows=803628 width=53)
              ->  Unique  (cost=479736.16..483754.30 rows=803628 width=53)
                    ->  Sort  (cost=479736.16..481745.23 rows=803628 width=53)
                          Sort Key: work_items.id
                          ->  WindowAgg  (cost=136262.98..345979.65 rows=803628 width=53)
                                ->  Hash Join  (cost=136262.98..335934.30 rows=803628 width=45)
                                      Hash Cond: (work_items.id = work_item_states.work_item_refer)
                                      ->  Seq Scan on work_items  (cost=0.00..106679.48 rows=4020148 width=37)
                                      ->  Hash  (cost=119152.97..119152.97 rows=803681 width=45)
                                            ->  Bitmap Heap Scan on work_item_states  (cost=18968.96..119152.97 rows=803681 width=45)
                                                  Recheck Cond: (disposition = 'cancelled'::text)
                                                  ->  Bitmap Index Scan on idx_work_item_states_disposition  (cost=0.00..18768.04 rows=803681 width=0)
                                                        Index Cond: (disposition = 'cancelled'::text)

仅LIMIT执行计划

Limit  (cost=1.11..69.52 rows=50 width=45)
  ->  Subquery Scan on s  (cost=1.11..1099599.17 rows=803628 width=45)
        ->  Unique  (cost=1.11..1091562.89 rows=803628 width=77)
              ->  WindowAgg  (cost=1.11..1089553.82 rows=803628 width=77)
                    ->  Merge Join  (cost=1.11..1079508.47 rows=803628 width=37)
                          Merge Cond: (work_items.id = work_item_states.work_item_refer)
                          ->  Index Only Scan using idx_work_items_id on work_items  (cost=0.56..477365.14 rows=4020148 width=37)
                          ->  Index Scan using idx_work_item_states_work_item_refer on work_item_states  (cost=0.56..582047.48 rows=803681 width=37)
                                Filter: (disposition = 'cancelled'::text)

仅ORDER BY执行计划

Sort  (cost=625547.09..627556.16 rows=803628 width=53)
  Sort Key: s.disposition
  ->  Subquery Scan on s  (cost=479736.16..491790.58 rows=803628 width=53)
        ->  Unique  (cost=479736.16..483754.30 rows=803628 width=53)
              ->  Sort  (cost=479736.16..481745.23 rows=803628 width=53)
                    Sort Key: work_items.id
                    ->  WindowAgg  (cost=136262.98..345979.65 rows=803628 width=53)
                          ->  Hash Join  (cost=136262.98..335934.30 rows=803628 width=45)
                                Hash Cond: (work_items.id = work_item_states.work_item_refer)
                                ->  Seq Scan on work_items  (cost=0.00..106679.48 rows=4020148 width=37)
                                ->  Hash  (cost=119152.97..119152.97 rows=803681 width=45)
                                      ->  Bitmap Heap Scan on work_item_states  (cost=18968.96..119152.97 rows=803681 width=45)
                                            Recheck Cond: (disposition = 'cancelled'::text)
                                            ->  Bitmap Index Scan on idx_work_item_states_disposition  (cost=0.00..18768.04 rows=803681 width=0)
                                                  Index Cond: (disposition = 'cancelled'::text)

1 个答案:

答案 0 :(得分:4)

您没有发布执行计划,但是我已经准备好了水晶球,因此我可以猜测发生了什么。

在第二个非常慢的查询中,优化器有一个聪明的主意如何使其快速运行: 它使用work_items上的索引扫描id,在嵌套循环中从work_item_states获取所有匹配的行,并过滤出不匹配work_item_states.disposition = 'cancelled'的所有内容,直到找到50不同的结果。

这是一个好主意,但是优化器并不知道所有带有work_item_states.disposition = 'cancelled'的行都与具有高work_items的{​​{1}}匹配,因此它必须一直扫描直到找到为止它的50行。

所有其他查询都不允许计划者选择该策略,因为只有按id顺序的几行才能保证这样做。