为啥子查询中的 distinct on 会损害 PostgreSQL 的性能?
Posted
技术标签:
【中文标题】为啥子查询中的 distinct on 会损害 PostgreSQL 的性能?【英文标题】:Why does distinct on in subqueries hurt performance in PostgreSQL?为什么子查询中的 distinct on 会损害 PostgreSQL 的性能? 【发布时间】:2020-11-05 03:57:46 【问题描述】:我有一个表 users
,其中包含字段 id
和 email
。 id
是主键,email
也是索引。
database> \d users
+-----------------------------+-----------------------------+-----------------------------------------------------+
| Column | Type | Modifiers |
|-----------------------------+-----------------------------+-----------------------------------------------------|
| id | integer | not null default nextval('users_id_seq'::regclass) |
| email | character varying | |
+-----------------------------+-----------------------------+-----------------------------------------------------+
Indexes:
"users_pkey" PRIMARY KEY, btree (id)
"index_users_on_email" UNIQUE, btree (email)
如果我在子查询中使用 distinct on (email)
子句查询表,我会受到显着的性能损失。
database> explain (analyze, buffers)
select
id
from (
select distinct on (email)
id
from
users
) as t
where id = 123
+-----------------------------------------------------------------------------------------------------------------------------+
| QUERY PLAN |
|-----------------------------------------------------------------------------------------------------------------------------|
| Subquery Scan on t (cost=8898.69..10077.84 rows=337 width=4) (actual time=221.133..250.782 rows=1 loops=1) |
| Filter: (t.id = 123) |
| Rows Removed by Filter: 67379 |
| Buffers: shared hit=2824, temp read=288 written=289 |
| -> Unique (cost=8898.69..9235.59 rows=67380 width=24) (actual time=221.121..247.582 rows=67380 loops=1) |
| Buffers: shared hit=2824, temp read=288 written=289 |
| -> Sort (cost=8898.69..9067.14 rows=67380 width=24) (actual time=221.120..239.573 rows=67380 loops=1) |
| Sort Key: users.email |
| Sort Method: external merge Disk: 2304kB |
| Buffers: shared hit=2824, temp read=288 written=289 |
| -> Seq Scan on users (cost=0.00..3494.80 rows=67380 width=24) (actual time=0.009..9.714 rows=67380 loops=1) |
| Buffers: shared hit=2821 |
| Planning Time: 0.243 ms |
| Execution Time: 251.258 ms |
+-----------------------------------------------------------------------------------------------------------------------------+
将此与distinct on (id)
进行比较,其成本不到上一个查询的千分之一。
database> explain (analyze, buffers)
select
id
from (
select distinct on (id)
id
from
users
) as t
where id = 123
+-----------------------------------------------------------------------------------------------------------------------------+
| QUERY PLAN |
|-----------------------------------------------------------------------------------------------------------------------------|
| Unique (cost=0.29..8.31 rows=1 width=4) (actual time=0.021..0.022 rows=1 loops=1) |
| Buffers: shared hit=3 |
| -> Index Only Scan using users_pkey on users (cost=0.29..8.31 rows=1 width=4) (actual time=0.020..0.020 rows=1 loops=1) |
| Index Cond: (id = 123) |
| Heap Fetches: 1 |
| Buffers: shared hit=3 |
| Planning Time: 0.090 ms |
| Execution Time: 0.034 ms |
+-----------------------------------------------------------------------------------------------------------------------------+
这是为什么?
我遇到的真正问题是我正在尝试创建一个视图,该视图执行 distinct on
一个不唯一的索引列并且性能非常糟糕。
【问题讨论】:
你强迫服务器做一个虚假的distinct
,因为你要丢弃结果。在最后一种情况下,服务器能够检测到这一点,因为仅涉及唯一的 id
列,并且简单地丢弃了 DISTINCT
操作。在前一种情况下,它必须计算 distinct on
以找出返回了哪些 ID
值,然后才能过滤它们
这两个查询返回不同的结果。如果电子邮件的id
小于123
,则第一个可能会返回no 结果。在第二种情况下,UNIQUE
索引上的DISTINCT
总是会返回索引值
我已经使用explain (analyze, buffers)
更新了问题
【参考方案1】:
逻辑差异
id
和 email
两列都是 UNIQUE
。但只有id
是NOT NULL
。 (PRIMARY KEY
列始终是。)NULL
值不被视为相等,具有UNIQUE
约束(或索引)的列中允许多个NULL
值。那是根据标准SQL。见:
但DISTINCT
或DISTINCT ON
认为NULL 值相等。 The manual:
显然,如果两行至少不同 一列值。 在此比较中,空值被视为相等。
我的大胆强调。延伸阅读:
Select first row in each GROUP BY group?在您的第二个查询中,distinct on (id)
是一个逻辑空操作:保证结果与没有DISTINCT ON
的结果相同。而且由于id = 123
上的外部SELECT
过滤器,Postgres 可以去除噪音并完成非常便宜的仅索引扫描。
另一方面,在您的第一个查询中,如果email IS NULL
有多个行,distinct on (email)
可能实际上会执行某些操作。然后 Postgres 必须根据给定的排序顺序选择第一个 id
。由于没有ORDER BY
,因此会导致任意选择。但是带有谓词where id = 123
的外部SELECT
可能取决于结果。整个查询在性质上与第一个完全不同 - 并且被设计破坏。
巧遇
除此之外,还有两个“幸运”的发现:
Sort Method: external merge Disk: 2304kB
提及“磁盘”表示work_mem 不足。见:
Configuration parameter work_mem in PostgreSQL on Linux-> Seq Scan on users (cost=0.00..3494.80 rows=67380
在我的测试中,我总是在这里进行索引扫描。表示您的设置存在臃肿的索引或其他问题。
有用的比较?
比较无济于事。我们可能会从中学到一些东西 将第一个查询与此查询进行比较 - 在切换 PK 和 UNIQUE 列的角色后:
select email
from (select distinct on (id) email from users) t
where email = 'user123@foo.com';
或者通过比较第二个查询和这个 - 尝试使用 UNIQUE 列而不是 PK 列:
select email
from (select distinct on (email) email from users) t
where email = 'user123@foo.com';
我们了解到 PK 和 UNIQUE 约束对查询计划没有不同的影响。 Postgres 不使用元信息来偷工减料。 PK 实际上会对GROUP BY
产生影响。见:
所以这行得通:
SELECT email
FROM (
SELECT email -- no aggregate required, because id = PK
FROM users
GROUP BY id -- !
) t
WHERE email = 'user123@foo.com';
但是在切换id
和email
之后同样不起作用。我在小提琴中添加了一些演示:
db小提琴here
那么?
这两个查询都是无意义的,原因不同。我看不出他们如何帮助您解决真正的问题:
我遇到的真正问题是,我正在尝试创建一个视图,该视图在不是唯一的的索引列上确实不同,并且性能非常糟糕。
我们需要查看您的真实查询 - 以及您设置的所有其他相关详细信息。可能有解决方案,但这可能远远超出了关于 SO 的问题的范围。考虑聘请顾问。或者考虑使用以下方法之一来优化性能:
Optimize GROUP BY query to retrieve latest row per user【讨论】:
以上是关于为啥子查询中的 distinct on 会损害 PostgreSQL 的性能?的主要内容,如果未能解决你的问题,请参考以下文章
为啥子查询解决方法中的这个 ORDER BY 不能始终如一地工作?