PostgreSQL 13 - 从具有日期范围的 table 获取不同数据的性能改进

PostgreSQL 13 - Performance Improvement to get distinct data from table with date range

我正在使用 PostgreSQL 13 并且具有 PostgreSQL 的中级经验。

我有一个名为 audit_log 的 table。每当用户登录系统时,我都会将 activity 存储在此 table.

下面是我的table结构,后面是数据类型和索引访问方法

  Column     |            Data Type        |     Index name            | Idx Access Type
-------------+-----------------------------+---------------------------+---------------------------
 id          | bigint                      |                           |
 log_time    | timestamp with time zone    |  idx_log_time             | btree
 user_id     | text                        |  idx_user_id              | btree
 customer_id | bigint                      |  idx_customer_id          | btree
 is_active   | boolean                     |  idx_is_active            | btree
 is_delete   | boolean                     |  idx_is_delete            | btree

我想获取在过去 30 天内为给定客户登录系统的唯一用户。

有两个客户cust1 & cust2 audit_log table 中的总记录是 2,24,11,490

对于 cust1 ,当我执行以下查询时,我得到 4,374 过去 30 天内登录系统的唯一用户,执行时间为 792 毫秒.

SELECT COUNT(DISTINCT user_id) FROM audio_log WHERE is_active=TRUE AND is_delete=FALSE AND customer_id=1 AND log_time BETWEEN '2022-01-01 00:00:00' AND '2022-01-31 00:00:00'

请注意,过去 30 天 cust1 的登录用户总数为 9,747

问题区域

对于 cust2 ,当我执行以下查询时,我得到 88,568 过去 30 天内登录系统的唯一用户,执行时间为 3秒.

SELECT COUNT(DISTINCT user_id) FROM audio_log WHERE is_active=TRUE AND is_delete=FALSE AND customer_id=2 AND log_time BETWEEN '2022-01-01 00:00:00' AND '2022-01-31 00:00:00'

请注意,过去 30 天内 cust2 的登录用户总数为 4,86,519

问题

我担心的是 cust2 此查询的时间应该少于 1 秒。那么有没有什么办法可以优化和减少执行时间呢?

下面是解释查询计划 下面是我的 pg_settings

尝试过的解决方案

  1. BRIN 指数 我为 log_time 列应用了 BRIN 索引,但执行查询花费了更多时间

  2. 分组依据选项 我按照少数解决方案中的建议使用了 GROUP BY 子句,但这也没有提高性能。

请指导。谢谢






更新

我必须添加新的条件,它不会考虑给定的 user_id 列表的非重复计数。

以下查询执行需要 1.2 秒。

SELECT COUNT(DISTINCT user_id) FROM audio_log WHERE is_active=TRUE AND is_delete=FALSE AND customer_id=2 AND log_time BETWEEN '2022-01-01 00:00:00' AND '2022-01-31 00:00:00'

当我在上面的查询中使用 not 时,它需要更多的时间来执行。下面是我更新的查询。

SELECT COUNT(DISTINCT user_id) FROM audio_log WHERE is_active=TRUE AND is_delete=FALSE AND customer_id=2 AND log_time BETWEEN '2022-01-01 00:00:00' AND '2022-01-31 00:00:00' AND user_id not in ('sndka__sjkd_jask_ldkl','eydu_iehc_jksd_hcjk_hsdk')

已添加索引

使用 BTREE 在 audio_log 上创建索引 cust_time_user (customer_id, is_active, is_delete, log_time, user_id);

有什么办法可以优化吗??

这是一个针对多列 BTREE 索引的作业。您的查询,已注释,是这样的。

SELECT COUNT(DISTINCT user_id)      -- item to deduplicate
  FROM audio_log
 WHERE is_active=TRUE               -- equality filter
   AND is_delete=FALSE              -- equality filter
   AND customer_id=1                -- most selective equality filter
   AND log_time BETWEEN             -- range filter
            '2022-01-01 00:00:00' AND '2022-01-31 00:00:00' 

在 BTREE 索引中,首先是相等过滤器,然后是范围过滤器,最后是要删除重复项的项目。所以,使用这个索引。我将其命名为 cust_time_user,但您可以随意命名。

CREATE INDEX cust_time_user ON audio_log USING BTREE
      (customer_id, is_active, is_delete, log_time, user_id);

Postgres 可以通过此 BTREE 索引满足您的查询,方法是随机访问第一个符合条件的行,然后按顺序扫描它以找到最后一个符合条件的行。

您的范围过滤器可能有误:BETWEEN 通常不适合 time-range 过滤器。为获得最佳结果,请使用 >= 作为范围的开头,使用 < 作为范围的结尾。你想要,我相信,

   AND log_time >= '2022-01-01 00:00:00'  -- earliest day
   AND log_time <  '2022-02-01 00:00:00'  -- day after the latest day

专业提示 通常向表中添加大量 single-column 索引是没有意义的。索引在设计用于帮助满足特定形状的查询时最有用。阅读本文以获得更多精彩信息 https://use-the-index-luke.com/