Postgresql 仅索引扫描在 Group By 上无法正常工作

Posted

技术标签:

【中文标题】Postgresql 仅索引扫描在 Group By 上无法正常工作【英文标题】:Postgresql Index Only Scan Doesnt Properly Work On Group By 【发布时间】:2020-03-25 19:38:09 【问题描述】:

我有一张这样的桌子:

CREATE TABLE summary
(
    id serial NOT NULL,
    user_id bigint NOT NULL,
    country character varying(5),
    product_id bigint NOT NULL,
    category_id bigint NOT NULL,
    text_id bigint NOT NULL,
    text character varying(255),
    product_type integer NOT NULL,
    event_name character varying(255),
    report_date date NOT NULL,
    currency character varying(5),
    revenue double precision,
    last_event_time timestamp
);

我的表大小是 1786 MB(索引除外)。在这里,我创建了如下索引:

CREATE INDEX "idx_as_type_usr_productId_eventTime"
    ON summary USING btree
    (product_type, user_id, product_id, last_event_time)
    INCLUDE(event_name);

我的简单查询如下所示:

select 
   event_name, 
   max(last_event_time) 
from summary s 
where s.user_id = ? and s.product_id = ? and s.product_type = ? 
   and s.last_event_time > '2020-03-01' and s.last_event_time < '2020-03-25'
group by event_name;

当我解释它时,它看起来像;

HashAggregate  (cost=93.82..96.41 rows=259 width=25) (actual time=9187.533..9187.536 rows=10 loops=1)
  Group Key: event_name
  Buffers: shared hit=70898 read=10579 dirtied=22650
  I/O Timings: read=3876.367
  ->  Index Only Scan using "idx_as_type_usr_productId_eventTime" on summary s  (cost=0.56..92.36 rows=292 width=25) (actual time=0.485..9153.812 rows=87322 loops=1)
        Index Cond: ((product_type = 2) AND (product_id = ?) AND (product_id = ?) AND (last_event_time > '2020-03-01 00:00:00'::timestamp without time zone) AND (last_event_time < '2020-03-25 00:00:00'::timestamp without time zone))
        Heap Fetches: 35967
        Buffers: shared hit=70898 read=10579 dirtied=22650
        I/O Timings: read=3876.367
Planning Time: 0.452 ms
Execution Time: 9187.583 ms

在这里,一切看起来都很好。但是当我执行它时,它需要超过 10 秒,有时需要超过 30 秒。

    在这里,如果我在没有 Group By 的情况下执行它,它会很快返回,不到 2 秒。 Group By 的作用是什么?选择部分太少了(比如 500 行)。 此表具有每秒 30 次的插入/更新操作。这可能与这个索引问题有关吗?

更新:

查询没有 - GroupBy:

select 
   event_name, 
   last_event_time 
from summary s 
where s.user_id = ? and s.product_id = ? and s.product_type = ? 
   and s.last_event_time > '2020-03-01' and s.last_event_time < '2020-03-25';

不解释 - 分组方式:

Index Only Scan using "idx_as_type_usr_productId_eventTime" on summary s  (cost=0.56..92.36 rows=292 width=25) (actual time=0.023..79.138 rows=87305 loops=1)
  Index Cond: ((product_type = ?) AND (user_id = ?) AND (product_id = ?) AND (last_event_time > '2020-03-01 00:00:00'::timestamp without time zone) AND (last_event_time < '2020-03-25 00:00:00'::timestamp without time zone))
  Heap Fetches: 22949
  Buffers: shared hit=37780 read=12143 dirtied=15156
  I/O Timings: read=4418.930
Planning Time: 0.639 ms
Execution Time: 4625.213 ms

【问题讨论】:

请以文本格式显示 EXPLAIN (ANALYZE, BUFFERS)。 JSON 格式很适合机器阅读,但我们不是机器。更好的是,如果还没有,请先打开 track_io_timing。并且请对没有 GROUP BY 的查询执行相同的操作,因为您也询问了该查询。 查询与计划不符。 (last_event_time &gt; '2020-03-01 00:00:00'::timestamp without time zone) AND (last_event_time &lt; '2020-03-25 00:00:00'::timestamp without time zone))" 不在查询中。请提出一个一致的问题。总是从披露你的 Postgres 版本开始。以及 Jeff 对文本格式的看法。 @jjanes 和欧文;问题已按照您所说的进行编辑。 @a_horse_with_no_name 已修复。 【参考方案1】:

有几个问题:

PostgreSQL 必须设置 提示位,这会弄脏页面并导致写入。

PostgreSQL 必须从磁盘获取表行以获取它们的可见性。

PostgreSQL 必须扫描 80000 个页面才能获得 87000 行,因此索引必须完全膨胀。

前两个可以通过运行来处理

VACUUM summary;

在批量加载后总是一个好主意,并且可以通过以下方式治愈臃肿

REINDEX INDEX "idx_as_type_usr_productId_eventTime";

【讨论】:

劳伦兹,感谢您的回答。我知道这与 DB-cache 有关,因为我按顺序获取它们。因此,我再次更新了问题。此时,我在两次查询之间间隔 15 分钟获取它们。 我的主要问题不是分组和非分组之间的区别。我的主要问题是;只有 500 行要扫描,而且此扫描是 index-only-scan,为什么提取时间需要这么长时间? 我已将答案调整为更改后的问题。 但是这个表每秒至少有 30 次插入/更新操作。那么我应该一直重新索引它吗?是适用的解决方案吗? 这是您问题的根本原因。调整 autovacuum 并使用 HOT 更新。但这是另一个问题的材料。

以上是关于Postgresql 仅索引扫描在 Group By 上无法正常工作的主要内容,如果未能解决你的问题,请参考以下文章

为啥即使使用仅索引扫描 PostgresQL 计数也如此缓慢

小表上的仅索引扫描非常慢

mysql优化 之 group by索引松散扫描和紧凑扫描

PostgreSQL citext 索引与较低的表达式索引性能

PostgreSQL 未对 JSONB 上的 GIN 索引使用索引扫描

为啥 postgresql 不使用我的 group by 聚合索引?