按日期和created_at排序的多列索引在不同查询中表现出奇怪的行为

问题描述

在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 查询时,在教室较少的情况下查询时,它不会改变其行为。

所以,有几个问题

  1. 使用原始查询,为什么教室数量如此巨大?
  2. 为什么删除activity_page_index WHERE子句会使两种情况都快速运行?
  3. 即使索引仍然包含school_id,为什么删除school_id WHERE子句也能使两种情况都快速运行?
  4. 高成本查询如何快速完成(65883-> 0.7ms),而低成本查询完成较慢(994-> 208ms)?

其他说明

  • 即使看起来似乎多余,也必须同时按school_iddate进行排序。

解决方法

对于所示查询,您所示的第一个计划似乎是不可能的。 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."成本估算是预测。有时候他们做得不太好。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...