【问题标题】:Is there a way to improve PostgreSQL estimates when joining skewed relations?加入倾斜关系时,有没有办法改进 PostgreSQL 估计?
【发布时间】:2021-01-24 01:49:12
【问题描述】:

我目前正在使用存储移动设备的 PostgreSQL 12 数据库。其中,它有两个表device_typesdevice_groups。数据分布是这样的,大多数device_groups 中只有很少的device_types,例如“2020 年发布的 iOS 设备”大约有 10 个device_types,而少数device_groups 有非常多的device_types,例如“现有的所有 Android 模型”,大约有 80'000 device_types

这很麻烦,因为 PostgreSQL 查询规划器会做出错误的估计,因此在使用 device_group“所有 Android 模型存在”时,稍后会在查询中选择次优的连接类型。根据其统计数据,它预计平均只有极少数device_types,但实际上在某些情况下必须处理包含 80,000 台设备的中间结果集。

我在下面创建了一个最小的复制案例。

这是数据模型:

CREATE TABLE device_groups (
    id              integer CONSTRAINT device_groups_pk PRIMARY KEY
);

CREATE TABLE device_types (
    id              integer CONSTRAINT device_types_pk PRIMARY KEY,
    device_group_id integer CONSTRAINT device_types_fk_device_groups_id REFERENCES device_groups(id)
);

现在我们插入一些倾斜的数据。大多数设备组有 ~10 个device_types,一个有 80k 个device_types

/* create 100 device groups */
INSERT INTO device_groups(id)
SELECT i
FROM generate_series(1, 100) as t(i);

/* 99 device groups have only a few entries, 10 on average: */
INSERT INTO device_types(id, device_group_id)
SELECT i, trunc(random() * 98 + 1)
FROM generate_series(1, 1000) as t(i);

/* device group 100 has 80k devices: */
INSERT INTO device_types(id, device_group_id)
SELECT i, 100
FROM generate_series(1001, 81001) as t(i);

ANALYZE device_groups;
ANALYZE device_types;

毫不奇怪,这个查询运行良好,因为 Postgres 拥有估计结果基数所需的所有统计信息:

EXPLAIN ANALYZE
SELECT *
FROM device_types
WHERE device_types.device_group_id = 100;
/* => correctly estimates ~80k rows: */
/* Seq Scan on device_types  (cost=0.00..1371.51 rows=79932 width=8) (actual time=0.104..665.699 rows=80001 loops=1) */

这个查询(我有点惊讶)也正确估计了它将返回多少行:

EXPLAIN ANALYZE
SELECT *
FROM device_types
JOIN device_groups ON device_types.device_group_id = device_groups.id
WHERE device_groups.id = 100;
/* => also correctly estimates ~80k rows: */
/* Nested Loop  (cost=0.00..2173.08 rows=79932 width=12) (actual time=0.165..1930.591 rows=80001 loops=1) */

现在我们添加一个额外的间接层,通过添加第三个表 device_group_collections 来加入我们的连接,这就是问题所在:

CREATE TABLE device_group_collections (
    device_group_id integer CONSTRAINT device_group_collections_fk_device_groups_id REFERENCES device_groups(id),
    collection_id   integer
);

INSERT INTO device_group_collections(device_group_id, collection_id) VALUES (100, 25);

ANALYZE device_group_collections;

EXPLAIN ANALYZE
SELECT *
FROM device_types
JOIN device_groups ON device_types.device_group_id = device_groups.id
JOIN device_group_collections ON device_group_collections.device_group_id = device_groups.id
WHERE device_group_collections.collection_id = 25;
/* => estimates only 810 rows, but in reality there will be 80'001 rows: */
/* Hash Join  (cost=3.42..1484.29 rows=810 width=20) (actual time=19.817..1949.068 rows=80001 loops=1) */

这是 Postgres 查询计划器的一般限制,还是有什么方法可以帮助它理解数据集的偏差?

【问题讨论】:

  • 我可能遗漏了一些东西,但 device_group_collections 中只有一条记录,请参阅 INSERT 语句。也没有主键,collection_id 上也没有索引。您还可以更改统计信息的设置,整体或仅此表:ALTER TABLE SET STATISTICS postgresql.org/docs/current/planner-stats.html

标签: sql postgresql


【解决方案1】:

这是规划器的限制。

您的中间查询被准确估计只是因为规划器意识到ON device_types.device_group_id = device_groups.id WHERE device_groups.id = 100 意味着 device_types.device_group_id 也必须等于 100,并且它可以在 device_types.device_group_id 上查找该值的统计信息。

如果您的 device_groups 表也有一个“名称”列并且查询指定了ON device_types.device_group_id = device_groups.id WHERE device_groups.name = 'name for 100',那么这将准确估计。

我不知道有任何通用解决方案可以解决这个问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-03
    • 1970-01-01
    • 2020-02-17
    • 2020-03-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多