问题描述
在postgres 10上,我有一个类似的查询,对于具有数百万行的表,以获取属于教室的最新帖子:
SELECT "posts".*
FROM "posts"
WHERE "posts"."school_id" = 1
AND "posts"."classroom_id" IN (10,11,12,13,14,15,16,17,18,19)
ORDER BY date desc,created_at desc
LIMIT 30 OFFSET 30;
假设教室仅属于一所学校。
我有这样的索引:
t.index ["date","created_at","school_id","classroom_id"],name: "optimize_post_pagination"
运行查询时,它会像我希望的那样向后扫描索引,并在0.7毫秒内返回。
Limit (cost=127336.95..254673.34 rows=30 width=494) (actual time=0.189..0.242 rows=30 loops=1)
-> Index Scan Backward using optimize_post_pagination on posts (cost=0.56..1018691.68 rows=240 width=494) (actual time=0.103..0.236 rows=60 loops=1)
Index Cond: (school_id = 1)
" Filter: (classroom_id = ANY ('{10,...}'::integer[]))"
Planning time: 0.112 ms
Execution time: 0.260 ms
SELECT "posts".*
FROM "posts"
WHERE "posts"."school_id" = 1
AND "posts"."classroom_id" IN (10,11)
ORDER BY date desc,created_at desc
LIMIT 30 OFFSET 30;
它吓跑了,做了很多的额外工作,耗时近4秒钟:
-> Sort (cost=933989.58..933989.68 rows=40 width=494) (actual time=3857.216..3857.219 rows=60 loops=1)
" Sort Key: date DESC,created_at DESC"
Sort Method: top-N heapsort Memory: 61kB
-> Bitmap Heap Scan on posts (cost=615054.27..933988.51 rows=40 width=494) (actual time=2700.871..3851.518 rows=18826 loops=1)
Recheck Cond: (school_id = 1)
" Filter: (classroom_id = ANY ('{10,11}'::integer[]))"
Rows Removed by Filter: 86099
Heap Blocks: exact=29256
-> Bitmap Index Scan on optimize_post_pagination (cost=0.00..615054.26 rows=105020 width=0) (actual time=2696.385..2696.385 rows=104925 loops=1)
Index Cond: (school_id = 485)
甚至更奇怪的是,如果我删除WHERE
的{{1}}子句,则教室(少数几个或很多)的两种情况都可以通过向后索引扫描快速运行。
This index cookbook建议将ORDER BY索引列放在最后,就像这样:
school_id
但这使我的查询速度变慢,即使费用低得多。
t.index ["school_id","classroom_id","date","created_at"],name: "activity_page_index"
有趣的是,使用Limit (cost=993.93..994.00 rows=30 width=494) (actual time=208.443..208.452 rows=30 loops=1)
-> Sort (cost=993.85..994.45 rows=240 width=494) (actual time=208.436..208.443 rows=60 loops=1)
" Sort Key: date DESC,created_at DESC"
Sort Method: top-N heapsort Memory: 118kB
-> Index Scan using activity_page_index on posts (cost=0.56..985.56 rows=240 width=494) (actual time=0.032..178.147 rows=102403 loops=1)
" Index Cond: ((school_id = 1) AND (classroom_id = ANY ('{10,...}'::integer[])))"
Planning time: 0.132 ms
Execution time: 208.482 ms
查询时,在教室较少的情况下查询时,它不会改变其行为。
所以,有几个问题:
- 使用原始查询,为什么教室数量如此巨大?
- 为什么删除
activity_page_index
WHERE子句会使两种情况都快速运行? - 即使索引仍然包含
school_id
,为什么删除school_id
WHERE子句也能使两种情况都快速运行? - 高成本查询如何快速完成(65883-> 0.7ms),而低成本查询完成较慢(994-> 208ms)?
其他说明
- 即使看起来似乎多余,也必须同时按
school_id
和date
进行排序。
解决方法
对于所示查询,您所示的第一个计划似乎是不可能的。 school_id = 1条件应该以索引条件或过滤条件的形式出现,但您都不会在其中之一中显示它。
使用原始查询,为什么教室数量如此巨大?
在原始计划中,它通过遍历索引以所需顺序获得行。然后,一旦它累积了60个满足非索引标准的行,它便会提前停止。因此,其他条件更具选择性,则在获取足够多的行以尽早停止之前,它需要遍历的大部分索引。从列表中删除教室会使其更具选择性,因此使该计划看起来不那么吸引人。在某个时候,它越过一条线,看上去比其他东西吸引力弱。
为什么删除school_id WHERE子句会使两种情况都快速运行?
您说每个教室仅属于一所学校。但是PostgreSQL不知道,它认为这两个条件是独立的,因此通过将两个单独的估计值相乘来获得总体估计的选择性。这给了整体选择性一个非常误导的估计,这使得已经排序的索引扫描看起来比实际情况差。不指定多余的school_id可以防止它对标准的独立性做出错误的假设。您可以创建多列统计信息来尝试解决此问题,但是在我看来,直到v13为止,这实际上对您的查询没有帮助(出于我不了解的原因)。
这是关于估算过程,而不是执行过程。因此,school_id是否在索引中并不重要。
高成本查询如何快速完成(65883-> 0.7ms)而低成本查询完成较慢(994-> 208ms)?
"It is difficult to make predictions,especially about the future."成本估算是预测。有时候他们做得不太好。