如何在大型 table 上提高基于日期的查询性能?
How do I improve date-based query performance on a large table?
这与我 post 编辑的其他 2 个问题有关(听起来我应该 post 这是一个新问题)- 反馈有帮助,但我认为同样的问题会再次出现下次我需要插入数据。事情 运行 缓慢地停止,这迫使我暂时删除一些旧数据,以便我正在查询的 table 中只剩下 2 个月的价值。
这次提供更多细节 - 希望它能帮助查明问题:
- PG 版本 10.7(运行在 heroku 上可用
- 数据库总大小:18.4GB(包含 2 个月的数据量,每个月的增长速度大致相同)
- 15GB 内存
- 总可用存储空间:512GB
- 最大的 table(执行最慢查询的那个)是 9.6GB(它是整个数据库中最大的块)- 大约 1000 万条记录
最大的架构table:
-- Table Definition ----------------------------------------------
CREATE TABLE reportimpression (
datelocal timestamp without time zone,
devicename text,
network text,
sitecode text,
advertisername text,
mediafilename text,
gender text,
agegroup text,
views integer,
impressions integer,
dwelltime numeric
);
-- Indices -------------------------------------------------------
CREATE INDEX reportimpression_feb2019_index ON reportimpression(datelocal timestamp_ops) WHERE datelocal >= '2019-02-01 00:00:00'::timestamp without time zone AND datelocal < '2019-03-01 00:00:00'::timestamp without time zone;
CREATE INDEX reportimpression_mar2019_index ON reportimpression(datelocal timestamp_ops) WHERE datelocal >= '2019-03-01 00:00:00'::timestamp without time zone AND datelocal < '2019-04-01 00:00:00'::timestamp without time zone;
CREATE INDEX reportimpression_jan2019_index ON reportimpression(datelocal timestamp_ops) WHERE datelocal >= '2019-01-01 00:00:00'::timestamp without time zone AND datelocal < '2019-02-01 00:00:00'::timestamp without time zone;
慢查询:
SELECT
date_part('hour', datelocal) AS hour,
SUM(CASE WHEN gender = 'male' THEN views ELSE 0 END) AS male,
SUM(CASE WHEN gender = 'female' THEN views ELSE 0 END) AS female
FROM reportimpression
WHERE
datelocal >= '3-1-2019' AND
datelocal < '4-1-2019'
GROUP BY date_part('hour', datelocal)
ORDER BY date_part('hour', datelocal)
此查询中的日期范围通常是整个月(它接受来自基于 Web 的报告的用户输入)- 如您所见,我尝试为每个月的数据创建索引。这有帮助,但据我所知,除非查询最近是 运行(将结果放入缓存),它仍然可能需要一分钟才能到达 运行.
解释分析结果:
Finalize GroupAggregate (cost=1035890.38..1035897.86 rows=1361 width=24) (actual time=3536.089..3536.108 rows=24 loops=1)
Group Key: (date_part('hour'::text, datelocal))
-> Sort (cost=1035890.38..1035891.06 rows=1361 width=24) (actual time=3536.083..3536.087 rows=48 loops=1)
Sort Key: (date_part('hour'::text, datelocal))
Sort Method: quicksort Memory: 28kB
-> Gather (cost=1035735.34..1035876.21 rows=1361 width=24) (actual time=3535.926..3579.818 rows=48 loops=1)
Workers Planned: 1
Workers Launched: 1
-> Partial HashAggregate (cost=1034735.34..1034740.11 rows=1361 width=24) (actual time=3532.917..3532.933 rows=24 loops=2)
Group Key: date_part('hour'::text, datelocal)
-> Parallel Index Scan using reportimpression_mar2019_index on reportimpression (cost=0.09..1026482.42 rows=3301168 width=17) (actual time=0.045..2132.174 rows=2801158 loops=2)
Planning time: 0.517 ms
Execution time: 3579.965 ms
我不认为处理 1000 万条记录会太多,特别是考虑到我最近提高了我正在尝试投入资源的 PG 计划,所以我认为问题是仍然只是我的索引或我的查询效率不高。
您的执行计划似乎在做正确的事情。
您可以做些什么来改进,按有效性降序排列:
使用预先聚合数据的物化视图
不要使用托管数据库,使用具有良好本地存储和大量 RAM 的自己的熨斗。
只使用一个索引而不是多个分区索引。这主要不是性能建议(除非您有很多索引,否则查询可能不会明显变慢),但它会减轻管理负担。
A materialized view 是实现您概述的方法。查询过去几个月的只读数据无需刷新即可。如果您也需要涵盖当月,您可能希望对当前月份进行特殊处理。
底层查询仍然可以从索引中获益,您可以选择两个方向:
首先,partial indexes like you have now won't buy much in your scenario, not worth it. If you collect many more months of data and mostly query by month (and add / drop rows by month) table partitioning 可能是一个想法,然后您也可以自动对索引进行分区。不过,我会考虑使用 Postgres 11 甚至即将推出的 Postgres 12。)
如果您的行很宽,请创建一个允许index-only scans的索引。喜欢:
CREATE INDEX reportimpression_covering_idx ON reportimpression(datelocal, views, gender);
相关:
或 INCLUDE
Postgres 11 或更高版本中的附加列:
CREATE INDEX reportimpression_covering_idx ON reportimpression(datelocal) INCLUDE (views, gender);
Else,如果您的行按 datelocal
物理排序,请考虑 BRIN index。它非常小,可能与您的案例的 B 树索引一样快。 (但它是如此之小,它会更容易地保持缓存,并且不会将其他数据推出那么多。)
CREATE INDEX reportimpression_brin_idx ON reportimpression USING BRIN (datelocal);
您可能对 CLUSTER
or pg_repack
物理排序 table 行感兴趣。 pg_repack
可以在 table 上没有独占锁甚至没有 btree 索引(CLUSTER
需要)的情况下做到这一点。但它是一个附加模块,没有随 Postgres 的标准发行版一起提供。
相关:
- Optimize Postgres deletion of orphaned records
- How to reclaim disk space after delete without rebuilding table?
这与我 post 编辑的其他 2 个问题有关(听起来我应该 post 这是一个新问题)- 反馈有帮助,但我认为同样的问题会再次出现下次我需要插入数据。事情 运行 缓慢地停止,这迫使我暂时删除一些旧数据,以便我正在查询的 table 中只剩下 2 个月的价值。
这次提供更多细节 - 希望它能帮助查明问题:
- PG 版本 10.7(运行在 heroku 上可用
- 数据库总大小:18.4GB(包含 2 个月的数据量,每个月的增长速度大致相同)
- 15GB 内存
- 总可用存储空间:512GB
- 最大的 table(执行最慢查询的那个)是 9.6GB(它是整个数据库中最大的块)- 大约 1000 万条记录
最大的架构table:
-- Table Definition ----------------------------------------------
CREATE TABLE reportimpression (
datelocal timestamp without time zone,
devicename text,
network text,
sitecode text,
advertisername text,
mediafilename text,
gender text,
agegroup text,
views integer,
impressions integer,
dwelltime numeric
);
-- Indices -------------------------------------------------------
CREATE INDEX reportimpression_feb2019_index ON reportimpression(datelocal timestamp_ops) WHERE datelocal >= '2019-02-01 00:00:00'::timestamp without time zone AND datelocal < '2019-03-01 00:00:00'::timestamp without time zone;
CREATE INDEX reportimpression_mar2019_index ON reportimpression(datelocal timestamp_ops) WHERE datelocal >= '2019-03-01 00:00:00'::timestamp without time zone AND datelocal < '2019-04-01 00:00:00'::timestamp without time zone;
CREATE INDEX reportimpression_jan2019_index ON reportimpression(datelocal timestamp_ops) WHERE datelocal >= '2019-01-01 00:00:00'::timestamp without time zone AND datelocal < '2019-02-01 00:00:00'::timestamp without time zone;
慢查询:
SELECT
date_part('hour', datelocal) AS hour,
SUM(CASE WHEN gender = 'male' THEN views ELSE 0 END) AS male,
SUM(CASE WHEN gender = 'female' THEN views ELSE 0 END) AS female
FROM reportimpression
WHERE
datelocal >= '3-1-2019' AND
datelocal < '4-1-2019'
GROUP BY date_part('hour', datelocal)
ORDER BY date_part('hour', datelocal)
此查询中的日期范围通常是整个月(它接受来自基于 Web 的报告的用户输入)- 如您所见,我尝试为每个月的数据创建索引。这有帮助,但据我所知,除非查询最近是 运行(将结果放入缓存),它仍然可能需要一分钟才能到达 运行.
解释分析结果:
Finalize GroupAggregate (cost=1035890.38..1035897.86 rows=1361 width=24) (actual time=3536.089..3536.108 rows=24 loops=1)
Group Key: (date_part('hour'::text, datelocal))
-> Sort (cost=1035890.38..1035891.06 rows=1361 width=24) (actual time=3536.083..3536.087 rows=48 loops=1)
Sort Key: (date_part('hour'::text, datelocal))
Sort Method: quicksort Memory: 28kB
-> Gather (cost=1035735.34..1035876.21 rows=1361 width=24) (actual time=3535.926..3579.818 rows=48 loops=1)
Workers Planned: 1
Workers Launched: 1
-> Partial HashAggregate (cost=1034735.34..1034740.11 rows=1361 width=24) (actual time=3532.917..3532.933 rows=24 loops=2)
Group Key: date_part('hour'::text, datelocal)
-> Parallel Index Scan using reportimpression_mar2019_index on reportimpression (cost=0.09..1026482.42 rows=3301168 width=17) (actual time=0.045..2132.174 rows=2801158 loops=2)
Planning time: 0.517 ms
Execution time: 3579.965 ms
我不认为处理 1000 万条记录会太多,特别是考虑到我最近提高了我正在尝试投入资源的 PG 计划,所以我认为问题是仍然只是我的索引或我的查询效率不高。
您的执行计划似乎在做正确的事情。
您可以做些什么来改进,按有效性降序排列:
使用预先聚合数据的物化视图
不要使用托管数据库,使用具有良好本地存储和大量 RAM 的自己的熨斗。
只使用一个索引而不是多个分区索引。这主要不是性能建议(除非您有很多索引,否则查询可能不会明显变慢),但它会减轻管理负担。
A materialized view 是实现您概述的方法。查询过去几个月的只读数据无需刷新即可。如果您也需要涵盖当月,您可能希望对当前月份进行特殊处理。
底层查询仍然可以从索引中获益,您可以选择两个方向:
首先,partial indexes like you have now won't buy much in your scenario, not worth it. If you collect many more months of data and mostly query by month (and add / drop rows by month) table partitioning 可能是一个想法,然后您也可以自动对索引进行分区。不过,我会考虑使用 Postgres 11 甚至即将推出的 Postgres 12。)
如果您的行很宽,请创建一个允许index-only scans的索引。喜欢:
CREATE INDEX reportimpression_covering_idx ON reportimpression(datelocal, views, gender);
相关:
或 INCLUDE
Postgres 11 或更高版本中的附加列:
CREATE INDEX reportimpression_covering_idx ON reportimpression(datelocal) INCLUDE (views, gender);
Else,如果您的行按 datelocal
物理排序,请考虑 BRIN index。它非常小,可能与您的案例的 B 树索引一样快。 (但它是如此之小,它会更容易地保持缓存,并且不会将其他数据推出那么多。)
CREATE INDEX reportimpression_brin_idx ON reportimpression USING BRIN (datelocal);
您可能对 CLUSTER
or pg_repack
物理排序 table 行感兴趣。 pg_repack
可以在 table 上没有独占锁甚至没有 btree 索引(CLUSTER
需要)的情况下做到这一点。但它是一个附加模块,没有随 Postgres 的标准发行版一起提供。
相关:
- Optimize Postgres deletion of orphaned records
- How to reclaim disk space after delete without rebuilding table?