对截断的 DATE 字段使用的索引扫描的顺序扫描
Sequential scan over index scan used on truncated DATE field
我使用 PostgreSQL,我有一个名为 table
的 table。
此 table 包含名为 created_at
的列(data_type
是 timestamptz
),该列使用 BTREE 进行索引。
我想计算一段时间内按 created_at::date
分组的行数(按 from_date
和 end_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
我想了解以下内容:
- 查询中需要改进的地方(如果有)
- table结构需要改进的地方(如果有的话)
- 使用的索引类型 (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;
更新可见性地图。
我使用 PostgreSQL,我有一个名为 table
的 table。
此 table 包含名为 created_at
的列(data_type
是 timestamptz
),该列使用 BTREE 进行索引。
我想计算一段时间内按 created_at::date
分组的行数(按 from_date
和 end_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
我想了解以下内容:
- 查询中需要改进的地方(如果有)
- table结构需要改进的地方(如果有的话)
- 使用的索引类型 (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;
更新可见性地图。