我在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)
答案 0 :(得分:4)
您没有发布执行计划,但是我已经准备好了水晶球,因此我可以猜测发生了什么。
在第二个非常慢的查询中,优化器有一个聪明的主意如何使其快速运行:
它使用work_items
上的索引扫描id
,在嵌套循环中从work_item_states
获取所有匹配的行,并过滤出不匹配work_item_states.disposition = 'cancelled'
的所有内容,直到找到50不同的结果。
这是一个好主意,但是优化器并不知道所有带有work_item_states.disposition = 'cancelled'
的行都与具有高work_items
的{{1}}匹配,因此它必须一直扫描直到找到为止它的50行。
所有其他查询都不允许计划者选择该策略,因为只有按id
顺序的几行才能保证这样做。