对截断的 DATE 字段使用的索引扫描的顺序扫描

Sequential scan over index scan used on truncated DATE field

我使用 PostgreSQL,我有一个名为 table 的 table。 此 table 包含名为 created_at 的列(data_typetimestamptz),该列使用 BTREE 进行索引。

我想计算一段时间内按 created_at::date 分组的行数(按 from_dateend_date 过滤)。

我运行下面的查询(结果符合预期):

SELECT ("table"."created_at" AT TIME ZONE 'UTC')::date AS "date",
       COUNT("table"."id") AS "count"
FROM "table"
WHERE ("table"."created_at" >= '2018-08-05T00:00:00+00:00'::timestamptz AND "table"."created_at" <= '2020-09-05T00:00:00+00:00'::timestamptz)
GROUP BY ("table"."created_at" AT TIME ZONE 'UTC')::date
ORDER BY "date" ASC

这个查询需要很长时间才能 运行(超过 200 万行),在查看查询计划时,我注意到有一个很重的 Seq Scan :

GroupAggregate  (cost=538741.06..605206.42 rows=2954016 width=12) (actual time=3866.460..5077.054 rows=559 loops=1)
  Group Key: ((timezone('UTC'::text, created_at))::date)
  ->  Sort  (cost=538741.06..546126.10 rows=2954016 width=8) (actual time=3866.414..4413.922 rows=2954016 loops=1)
        Sort Key: ((timezone('UTC'::text, created_at))::date)
        Sort Method: external merge  Disk: 52056kB
        ->  Seq Scan on table  (cost=0.00..140489.32 rows=2954016 width=8) (actual time=0.070..2194.108 rows=2954016 loops=1)
              Filter: ((created_at >= '2018-08-05 00:00:00+00'::timestamp with time zone) AND (created_at <= '2020-09-05 00:00:00+00'::timestamp with time zone))
Planning time: 1.018 ms
Execution time: 5094.280 ms

我想了解以下内容:

  1. 查询中需要改进的地方(如果有)
  2. table结构需要改进的地方(如果有的话)
  3. 使用的索引类型 (BTREE) 是否适合此类查询?

您的 WHERE 条件似乎并未实际过滤掉任何行,因此 table 中的所有行都已处理。在这种情况下,使用 Seq Scan 是检索数据的最有效方法。如果您使时间范围更小,以便只检索 table 行的一小部分,优化器应该使用索引。

Seq Scan只占用查询时间的一半,另一半花在了GROUP BY(或排序)上。如果你增加 work_mem 至少 sorting/grouping 应该更快(更多 work_mem)排序很可能被哈希聚合取代。

假设 id 定义为 not null,那么使用 count(*) 而不是 count(id) 也会使查询更快。其一是因为计数函数中不再需要“空检查”。但更重要的是,因为 Postgres 很可能只进行索引扫描,因为只需要 created_at 列,它可以直接在索引中使用。如果这没有切换到仅索引扫描,您可能想 运行 vacuum analyze the_table; 更新可见性地图。