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

问题描述

我使用Postgresql,并且有一个名为table的表。 该表包含名为created_atdata_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扫描

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. 表结构(如果有)应该改进的地方
  3. 使用的索引类型(BTREE)是否适合此类查询

解决方法

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

Seq扫描仅占用查询时间的一半,另一半用于GROUP BY(或对其进行排序)。如果您增加work_mem,至少排序/分组应该更快(work_mem更多),则排序很可能会被散列聚合代替。

假设id被定义为not null,那么使用count(*)代替count(id)也会使查询速度更快。对于一个,因为在计数功能中不再需要“空检查”。但更重要的是,因为Postgres可能只进行索引扫描,因为只需要在索引中直接使用的created_at列。如果此操作没有切换为“仅索引扫描”,则可能需要运行vacuum analyze the_table;来更新可见性图。