我目前正在使用存储移动设备的 PostgreSQL 12 数据库。其中,它有两个表 device_types
和 device_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 查询规划器的一般限制,还是有什么方法可以帮助它理解数据集的偏斜?
最佳答案
这是计划者的限制。
准确估计您的中间查询只是因为规划器意识到 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'
,那么就是 不是 被准确估计。
我不知道这个问题的任何通用解决方案。
关于sql - 加入倾斜关系时,有没有办法改进 PostgreSQL 估计?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65861780/