如何提高大型表上基于日期的查询性能?

Posted

技术标签:

【中文标题】如何提高大型表上基于日期的查询性能?【英文标题】:How do I improve date-based query performance on a large table? 【发布时间】:2019-04-05 17:26:40 【问题描述】:

这与我发布的其他 2 个问题有关(听起来我应该将其作为一个新问题发布) - 反馈有所帮助,但我认为下次我需要插入数据时同样的问题会再次出现。事情进展缓慢,这迫使我暂时删除了一些较旧的数据,因此我正在查询的表中只剩下 2 个月的价值。

Indexing strategy for different combinations of WHERE clauses incl. text patterns

How to get date_part query to hit index?

这次提供更多细节 - 希望这将有助于查明问题:

PG 版本 10.7(在 heroku 上运行 总数据库大小:18.4GB(其中包含 2 个月的数据,并且每个月都会以大致相同的速度增长) 15GB 内存 总可用存储空间:512G​​B 最大的表(执行最慢查询的表)为 9.6GB(它是整个数据库中最大的一块)- 大约 1000 万条记录

最大表的架构:

-- 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 计划,我正在尝试投入资源,所以我认为问题仍然只是我的索引或查询效率不高。

【问题讨论】:

就像我说的,不是主要问题。还是各方面都好。这里的主要问题是rows=2801158。聚合接近 3M 行不会很快。但它可能会更快。为了优化索引策略,我们需要知道可能查询的范围,而不仅仅是一个可能会误导的示例查询。你总是按小时汇总吗?总是求和views?总是按性别划分? 对于这个查询,它需要按小时。例如,我在报告中需要的结果集应该吐出 24 条记录(每小时一条),每个小时内每种性别的总浏览量 SUM。 所以您想针对此查询优化您的数据库?我做对了吗?那么您最好的行动方案是 Laurenz 回答中的要点 1。物化视图应该完美地涵盖这一点。您可能仍想优化底层查询,但这并不重要,为此目的量身定制的索引可能不会支付。 【参考方案1】:

materialized view 是实现您所概述的内容的方法。查询过去几个月的只读数据无需刷新即可工作。如果您也需要涵盖当前月份,则可能需要对当前月份进行特殊处理。

基础查询仍然可以从索引中受益,您可以采取两个方向:

首先,partial indexes 就像你现在在你的场景中不会买太多,不值得。如果您收集更多月的数据并且主要按月查询(并按月添加/删除行)table partitioning 可能是一个想法,那么您的索引也会自动分区。不过,我会考虑使用 Postgres 11 甚至即将推出的 Postgres 12。)

如果您的行很宽,请创建一个允许 index-only scans 的索引。喜欢:

CREATE INDEX reportimpression_covering_idx ON reportimpression(datelocal, views, gender);

相关:

How does PostgreSQL perform ORDER BY if a b-tree index is built on that field?

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);

您可能对CLUSTERpg_repack 对表格行进行物理排序感兴趣。 pg_repack 可以在没有对表的排他锁甚至没有 btree 索引(CLUSTER 要求)的情况下做到这一点。但它是 Postgres 标准发行版未附带的附加模块。

相关:

Optimize Postgres deletion of orphaned records How to reclaim disk space after delete without rebuilding table?

【讨论】:

“物理排序” - 这意味着只要我按日期顺序插入所有记录,那么 BRIN 索引的性能应该更高吗?另外 - 在我进行任何这些更改之后,有没有办法确保索引是最新的,以便当我再次运行分析时,我看到了真实的结果?创建这些索引后是否需要运行 Vacuum 分析? @dgwebb:是的,BRIN(块范围)索引对整个块范围(数据页)而不是单个行进行操作。只要对行进行物理排序,就应该为您的设置提供出色的工作。如果您插入按日期排序,然后不更新,那应该可以完美地工作。否则,在打乱实物订单后考虑CLUSTER(其次是VACUUM ANALYZE)。 谢谢!将尝试上述所有方法,看看哪种效果最好。干杯。 我根据您的反馈做了一些优化,现在正在测试 datelocal 列的索引性能。使用 CLUSTER 后,即使使用普通的 btree,我也得到了非常好的结果,但看起来 BRIN 会更合适。我刚刚注意到 CLUSTER 不能在 BRIN 上使用(不知道为什么会这样),但是如果你不能使用 CLUSTER,那么使用桌子的最佳解决方案是什么? CLUSTER 是工厂 Postgres 中的最佳选择。 pg_repack 可能是更好的选择,但必须作为附加模块安装,而不是随标准发行版一起提供。它可以按指定的列而不是索引进行排序。否则,请考虑:dba.stackexchange.com/a/62970/3684。请提出新问题作为新的问题。评论不是地方。【参考方案2】:

您的执行计划似乎做对了。

您可以采取哪些措施来改进,按效果降序排列:

使用预先聚合数据的物化视图

不要使用托管数据库,使用具有良好本地存储和大量 RAM 的自己的 Iron。

只使用一个索引而不是多个分区索引。这主要不是性能建议(除非您有很多索引,否则查询可能不会明显变慢),但它会减轻管理负担。

【讨论】:

我对数据库不是很了解,但据我了解,视图仍然执行底层查询(因此不会提供性能提升)?这是不正确的吗?此外,在我的情况下,托管我自己并不是一个真正的选择(我没有专业知识或时间去做)。对于索引 - 我最初在 datelocal 列上的这个表上只有一个索引。我应该切换回那个吗? @dgwebb: materialized view 确实......很好...... 物化 结果 - 与普通视图相反。 有趣!这对我来说是全新的。会试一试。谢谢你们。 如果我正确阅读了文档 - 似乎在执行 INSERT 或 UPDATE 之后需要运行 REFRESH MATERIALIZED VIEW? @dgwebb:仅当您想更新它时。查询过去几个月的只读数据无需更新即可工作。如果您也需要涵盖当前月份,则可能需要对当前月份进行特殊处理。

以上是关于如何提高大型表上基于日期的查询性能?的主要内容,如果未能解决你的问题,请参考以下文章

基于日期时间值的相同表上的高效sql子查询

使用实体框架提高大型查询的性能 [重复]

如何提高基于视图的查询性能?

基于函数的索引没有提高查询性能

提高计算 MS-Access 中大型数据集 7 天滚动平均值的查询的性能

基于同一表上的另一个查询过滤记录的 SQL