为什么添加 ORDER BY 会大大加快查询速度?
Why does adding ORDER BY drastically speed up query?
我在 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 多个结果。
总而言之,对于外部查询:
排序方式 AND LIMIT - 快速
...
) AS s
ORDER BY s.disposition
LIMIT 50
OFFSET 0
仅 限制 - 非常慢
...
) AS s
LIMIT 50
OFFSET 0
仅 ORDER BY - 速度很快,尽管有 800 000 个结果
...
) AS s
ORDER BY s.disposition
OFFSET 0
都不是 - 速度很快,尽管有 800 000 个结果
...
) AS s
OFFSET 0
为了了解仅使用 LIMIT 子句会慢多少,同时使用、两者都不使用或仅使用 ORDER BY,查询不超过 10 秒。
然而,仅使用 LIMIT 子句,查询大约需要 15 分钟,是原来的 7 倍多!
您可能认为 ORDER BY 反而会减慢速度,因为它必须对子查询的结果进行排序,但事实似乎并非如此。这非常违反直觉。
如果有人知道这里的幕后发生了什么,我将非常感谢他们对此有所了解。
谢谢
EDIT - 添加了语句的执行计划:
ORDER BY and 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)
你没有 post 你的执行计划,但我已经 crystal 准备好了,所以我会猜猜发生了什么。
在你的第二个非常慢的查询中,优化器有一个聪明的主意如何让它变快:
它使用 id
上的索引扫描 work_items
,在嵌套循环中从 work_item_states
中获取所有匹配行,并过滤掉所有不匹配 work_item_states.disposition = 'cancelled'
的行,直到找到 50 个不同的结果。
这是个好主意,但优化器不知道所有带有 work_item_states.disposition = 'cancelled'
的行都与 work_items
匹配并具有高 id
,因此它必须一直扫描直到它已找到它的 50 行。
所有其他查询都不允许计划者选择该策略,因为只有 work_items.id
顺序中的几行才可以。
我在 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 多个结果。
总而言之,对于外部查询:
排序方式 AND LIMIT - 快速
...
) AS s
ORDER BY s.disposition
LIMIT 50
OFFSET 0
仅 限制 - 非常慢
...
) AS s
LIMIT 50
OFFSET 0
仅 ORDER BY - 速度很快,尽管有 800 000 个结果
...
) AS s
ORDER BY s.disposition
OFFSET 0
都不是 - 速度很快,尽管有 800 000 个结果
...
) AS s
OFFSET 0
为了了解仅使用 LIMIT 子句会慢多少,同时使用、两者都不使用或仅使用 ORDER BY,查询不超过 10 秒。
然而,仅使用 LIMIT 子句,查询大约需要 15 分钟,是原来的 7 倍多!
您可能认为 ORDER BY 反而会减慢速度,因为它必须对子查询的结果进行排序,但事实似乎并非如此。这非常违反直觉。
如果有人知道这里的幕后发生了什么,我将非常感谢他们对此有所了解。
谢谢
EDIT - 添加了语句的执行计划:
ORDER BY and 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)
你没有 post 你的执行计划,但我已经 crystal 准备好了,所以我会猜猜发生了什么。
在你的第二个非常慢的查询中,优化器有一个聪明的主意如何让它变快:
它使用 id
上的索引扫描 work_items
,在嵌套循环中从 work_item_states
中获取所有匹配行,并过滤掉所有不匹配 work_item_states.disposition = 'cancelled'
的行,直到找到 50 个不同的结果。
这是个好主意,但优化器不知道所有带有 work_item_states.disposition = 'cancelled'
的行都与 work_items
匹配并具有高 id
,因此它必须一直扫描直到它已找到它的 50 行。
所有其他查询都不允许计划者选择该策略,因为只有 work_items.id
顺序中的几行才可以。