使用 JSONB 聚合和 CTE 优化查询

问题描述 投票:0回答:0

我有一个 SQL 查询,我试图通过添加索引或更改查询本身来加快速度。

但是,执行计划说我的 HashAggregate 和 Sort 操作很慢。

这是可视化的执行计划:https://explain.dalibo.com/plan/09c7d4a2dgcc29ec

这是我的桌子 DDL:

CREATE TABLE notification
(
    id        bigint                   NOT NULL GENERATED BY DEFAULT AS IDENTITY,
    body      jsonb                    NOT NULL,
    "type"    text                     NOT NULL,
    userid    int                      NOT NULL,
    isread    boolean                  NOT NULL DEFAULT FALSE,
    platform  text                     NOT NULL,
    "state"   text                     NOT NULL,
    createdat timestamp WITH TIME ZONE NOT NULL DEFAULT NOW(),
    updatedat timestamp WITH TIME ZONE NOT NULL DEFAULT NOW(),
    CONSTRAINT notification_pkey PRIMARY KEY (id, platform),
    CONSTRAINT fk_notification_userid FOREIGN KEY (userid) REFERENCES "user" (id)
) PARTITION BY LIST (platform);

CREATE TABLE notification_a PARTITION OF notification FOR VALUES IN ('a');
CREATE TABLE notification_b PARTITION OF notification FOR VALUES IN ('b');
CREATE TABLE notification_default PARTITION OF notification DEFAULT;

这是我的 sql 查询:

--EXPLAIN (ANALYZE, COSTS, BUFFERS, VERBOSE, FORMAT TEXT)
WITH grouped AS (
    SELECT CASE

               WHEN type = 'newContent' AND COUNT(*) FILTER (WHERE type = 'newContent') OVER () >= 3
                   THEN
                           JSONB_AGG(
                           JSONB_BUILD_OBJECT(
                                   'id', id,
                                   'body', body,
                                   'createdAt', createdat,
                                   'type', type
                               )
                       ) FILTER (WHERE type = 'newContent') OVER ()

               ELSE
                   JSONB_BUILD_OBJECT(
                           'id', id,
                           'body', body,
                           'createdAt', createdat,
                           'type', type
                       )
               END "notification"
    FROM notification_a
    WHERE userid = 23053
      AND NOT isread
      AND state = 'sent'
    ORDER BY createdat DESC
)
SELECT CASE

           WHEN JSONB_TYPEOF(notification) = 'array' AND notification #>> '{0,type}' = 'newContent' THEN
               JSONB_BUILD_OBJECT(
                       'body', JSONB_BUILD_OBJECT(
                       'contentCount', JSONB_ARRAY_LENGTH(notification),
                       'department', notification #>> '{0,body,department}'
                   ),
                       'createdAt', NOW(),
                       'type', 'newContentCompact',
                       'group', notification
                   )

           ELSE notification
           END,
       COUNT(*) OVER () total_count
FROM grouped
GROUP BY notification
ORDER BY (notification ->> 'createdAt')::timestamptz DESC
OFFSET 0 LIMIT 100;

首先,我按

platform
键对表进行分区,因为平台
a
的通知数量比平台
b
多得多。

然后,我尝试了不同的索引配置,并停在了下面给出最佳性能增益的配置上:

CREATE INDEX IF NOT EXISTS notification_search_index ON notification (userid, isread, state, createdat DESC) WHERE (state = 'sent' and not isread);

然而,尽管在索引中有它,但我无法摆脱对

notification_a.createdat
键的排序操作。


该表目前约有 1000 万行,未来将有数亿行。

我意识到可能是查询本身不是很“可优化”,但我找不到更好的方法来实现我需要的:

  • 获取特定的分页通知
    platform
  • 如果同一个
    type
    有超过2个通知(并且如果这种类型是
    newContent
    ),它们应该在最终结果中组合在一起(因此使用
    JSONB_AGG
    )。该组唯一真正需要的值是
    type
    body ->> 'department'
    和组的大小。

例如,如果我有行

id: 1, type: 'a'
id: 2, type: 'a'
id: 3, type: 'a'
id: 4, type: 'b'
id: 5, type: 'c'
id: 6, type: 'd'

如果做一个

SELECT ... OFFSET 0 LIMIT 3
我应该收到3行:

count: 3, type: 'a'
id: 4, type: 'b'
id: 5, type: 'c'

你能帮我优化我的查询,并可能找到一种更好(性能更高)的方法来实现我的需要吗?结果

jsonb
并不那么重要,我可以在应用程序级别构建 json,这是我发现让它进行分组的唯一方法。


更新 1

运行

ALTER ROLE current_user SET work_mem TO '128MB';
给~x2加速。

这里是新的执行计划:https://explain.dalibo.com/plan/e231h440b2943ga2

sql postgresql indexing jsonb postgresql-14
© www.soinside.com 2019 - 2024. All rights reserved.