为什么子查询中的 unique on 会损害 PostgreSQL 的性能?

The*_*off 1 postgresql performance greatest-n-per-group distinct-on postgresql-performance

我有一个users包含字段id和 的表emailid是主键并且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)

Run Code Online (Sandbox Code Playgroud)

如果我在子查询中使用子句查询表,distinct on (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)

Run Code Online (Sandbox Code Playgroud)

将此与其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                                                                                                    |
+-----------------------------------------------------------------------------------------------------------------------------+
Run Code Online (Sandbox Code Playgroud)

为什么是这样?

我遇到的真正问题是我正在尝试创建一个视图来执行distinct on不唯一的索引列,并且性能非常糟糕。

Erw*_*ter 5

逻辑差异

两列idemail都是UNIQUE。但仅idNOT NULL。(PRIMARY KEY列总是如此。)NULL值不被视为相等,具有约束(或索引)NULL的列中允许有多个值。UNIQUE这是根据标准 SQL 的。看:

DISTINCT还是DISTINCT ON认为 NULL 值相等。手册:

显然,如果两行至少有一列值不同,则它们被视为不同。在此比较中,空值被视为相等。

大胆强调我的。进一步阅读:

在您的第二个查询中,distinct on (id)是一个逻辑无操作:保证结果与 without 相同DISTINCT ON。由于SELECT上的外部过滤器id = 123,Postgres 可以去除噪音并完成非常便宜的仅索引扫描。

另一方面,在您的第一个查询中,distinct on (email)如果有多个行带有email IS NULL. 然后 Postgres 必须id根据给定的排序顺序选择第一个。由于没有ORDER BY,因此会导致任意选择。SELECT但带有谓词的外部where id = 123可能取决于结果。整个查询在原则上与第一个查询不同 - 并且在设计上被破坏。

机缘巧合

除此之外,还有两个“幸运”的发现:

Sort Method: external merge  Disk: 2304kB
Run Code Online (Sandbox Code Playgroud)

提及“磁盘”表示work_mem不足。看:

          ->  Seq Scan on users  (cost=0.00..3494.80 rows=67380 
Run Code Online (Sandbox Code Playgroud)

在我的测试中,我总是在这里进行索引扫描。表示索引臃肿或您的设置存在其他问题。

比较有用吗?

比较没有任何结果。在切换 PK 和 UNIQUE 列的角色之后,我们可以通过比较第一个查询与此查询来学到一些东西:

Sort Method: external merge  Disk: 2304kB
Run Code Online (Sandbox Code Playgroud)

或者将第二个查询与此查询进行比较 - 尝试使用 UNIQUE 列而不是 PK 列进行相同的操作:

          ->  Seq Scan on users  (cost=0.00..3494.80 rows=67380 
Run Code Online (Sandbox Code Playgroud)

我们了解到 PK 和 UNIQUE 约束对查询计划没有不同的影响。Postgres 不会使用元信息来偷工减料。PK实际上会产生影响GROUP BY。看:

所以这有效:

select email
from  (select distinct on (id) email from users) t
where email = 'user123@foo.com';
Run Code Online (Sandbox Code Playgroud)

id但切换和后同样不起作用email。我在小提琴中添加了一些演示:

db<>在这里摆弄

所以?

由于不同的原因,这两个查询都是无稽之谈。我不知道他们如何帮助您解决实际问题:

我遇到的真正问题是我正在尝试创建一个在索引列上不同的视图,该索引列不是唯一的,并且性能非常糟糕。

我们需要查看您的真实查询以及您的设置的所有其他相关详细信息。可能有解决方案,但这可能远远超出了 SO 问题的范围。考虑聘请一名顾问。或者考虑以下方法之一来优化性能: