为什么子查询中的distinct on会影响PostgreSQL的性能?

为什么子查询中的distinct on会影响PostgreSQL的性能?,postgresql,performance,greatest-n-per-group,postgresql-performance,distinct-on,Postgresql,Performance,Greatest N Per Group,Postgresql Performance,Distinct On,我有一个带有id和email字段的用户表。id是主键,电子邮件也会编制索引 database> \d users +-----------------------------+-----------------------------+-----------------------------------------------------+ | Column | Type | Modifiers

我有一个带有id和email字段的用户表。id是主键,电子邮件也会编制索引

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子句的表,我会得到显著的性能损失

数据库>解释、分析、缓冲区 选择 身份证件 从…起 在电子邮件上选择distinct 身份证件 从…起 使用者 as t 其中id=123 +---------------------------------------------------------------+ |查询计划| |---------------------------------------------------------------| |t上的子查询扫描成本=8898.69..10077.84行=337宽度=4实际时间=221.133..250.782行=1循环=1| |过滤器:t.id=123| |被筛选器删除的行:67379| |缓冲区:共享命中=2824,临时读取=288写入=289| |->唯一成本=8898.69..9235.59行=67380宽度=24实际时间=221.121..247.582行=67380循环=1| |缓冲区:共享命中=2824,临时读取=288写入=289| |->排序成本=8898.69..9067.14行=67380宽度=24实际时间=221.120..239.573行=67380循环=1| |排序键:users.email| |排序方法:外部合并磁盘:2304kB| |缓冲区:共享命中=2824,临时读取=288写入=289| |->按用户顺序扫描成本=0.00..3494.80行=67380宽度=24实际时间=0.009..9.714行=67380循环=1| |缓冲区:共享命中=2821| |计划时间:0.243毫秒| |执行时间:251.258毫秒| +---------------------------------------------------------------+ 将其与成本小于前一个查询的千分之一的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                                                                                                    |
+-----------------------------------------------------------------------------------------------------------------------------+
为什么会这样

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

逻辑差异 id列和电子邮件列都是唯一的。但只有id不为空。主键列始终为。空值不相等,在具有唯一约束或索引的列中允许多个空值。这是根据标准SQL实现的。见:

但在考虑空值相等时是不同的或不同的。 显然,如果两行至少在以下方面不同,则认为它们是不同的 一列值。在此比较中,空值被视为相等

我的。进一步阅读:

在第二个查询中,distinct on id是一个逻辑no op:结果保证与未使用distinct on时的结果相同。由于id=123上的外部选择过滤器,Postgres可以去除噪声,并完成非常便宜的仅索引扫描

另一方面,在您的第一个查询中,如果电子邮件中有多行为空,那么distinct on email实际上可能会执行某些操作。然后Postgres必须根据给定的排序顺序选择第一个id。由于没有ORDER BY,因此会导致任意拾取。但是id=123的谓词的外部SELECT可能取决于结果。整个查询在性质上与第一个查询完全不同,并且在设计上有所不同

偶然性 除此之外,还有两个幸运的发现:

提到磁盘就表示不够用。见:

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

有用的比较? 这种比较毫无意义。我们可以从中学到一些东西 将第一个查询与此查询进行比较-切换PK和UNIQUE列的角色后:

选择电子邮件 从用户发送的电子邮件中选择不同的id 电子邮件地址在哪里user123@foo.com'; 或者通过将第二个查询与此查询进行比较—尝试使用唯一列而不是PK列执行相同的操作:

选择电子邮件 从用户发送的电子邮件中选择distinct 电子邮件地址在哪里user123@foo.com'; 我们了解到PK和UNIQUE约束对查询计划没有不同的影响。Postgres不使用元信息来偷工减料。PK实际上会对分组产生影响。见:

如此 s工程:

选择电子邮件 从…起 选择电子邮件-无需聚合,因为id=PK 来自用户 按id分组-! T 电子邮件地址在哪里user123@foo.com'; 但在切换id和电子邮件后,同样的方法不起作用。我在小提琴上添加了一些演示:

小提琴

所以 出于不同的原因,这两个问题都是无稽之谈。我看不出他们能帮你解决真正的问题:

我遇到的真正问题是,我试图创建一个视图,该视图在一个索引列上执行distinct操作,但该列不是唯一的,而且性能非常差

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

逻辑差异 id列和电子邮件列都是唯一的。但只有id不为空。主键列始终为。空值不相等,在具有唯一约束或索引的列中允许多个空值。这是根据标准SQL实现的。见:

但在考虑空值相等时是不同的或不同的。 显然,如果两行至少在以下方面不同,则认为它们是不同的 一列值。在此比较中,空值被视为相等

我的。进一步阅读:

在第二个查询中,distinct on id是一个逻辑no op:结果保证与未使用distinct on时的结果相同。由于id=123上的外部选择过滤器,Postgres可以去除噪声,并完成非常便宜的仅索引扫描

另一方面,在您的第一个查询中,如果电子邮件中有多行为空,那么distinct on email实际上可能会执行某些操作。然后Postgres必须根据给定的排序顺序选择第一个id。由于没有ORDER BY,因此会导致任意拾取。但是id=123的谓词的外部SELECT可能取决于结果。整个查询在性质上与第一个查询完全不同,并且在设计上有所不同

偶然性 除此之外,还有两个幸运的发现:

提到磁盘就表示不够用。见:

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

有用的比较? 这种比较毫无意义。我们可以从中学到一些东西 将第一个查询与此查询进行比较-切换PK和UNIQUE列的角色后:

选择电子邮件 从用户发送的电子邮件中选择不同的id 电子邮件地址在哪里user123@foo.com'; 或者通过将第二个查询与此查询进行比较—尝试使用唯一列而不是PK列执行相同的操作:

选择电子邮件 从用户发送的电子邮件中选择distinct 电子邮件地址在哪里user123@foo.com'; 我们了解到PK和UNIQUE约束对查询计划没有不同的影响。Postgres不使用元信息来偷工减料。PK实际上会对分组产生影响。见:

所以这是可行的:

选择电子邮件 从…起 选择电子邮件-无需聚合,因为id=PK 来自用户 按id分组-! T 电子邮件地址在哪里user123@foo.com'; 但在切换id和电子邮件后,同样的方法不起作用。我在小提琴上添加了一些演示:

小提琴

所以 出于不同的原因,这两个问题都是无稽之谈。我看不出他们能帮你解决真正的问题:

我遇到的真正问题是,我试图创建一个视图,该视图在一个索引列上执行distinct操作,但该列不是唯一的,而且性能非常差

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


由于要放弃结果,因此您正在强制服务器执行伪distinct。在最后一种情况下,服务器能够检测到这一点,因为只涉及唯一id列,只需放弃该不同的操作即可。在前一种情况下,它必须计算distinct on以找到返回的ID值,然后才能对其进行筛选。这两个查询返回不同的结果。如果一封电子邮件的id小于123,则第一封邮件可能不会返回任何结果。在第二种情况下,总是会有一个结果作为唯一索引上的一个DISTINCT,它只返回索引值。我已经使用explain analyze、buffers更新了问题。您正在强制服务器执行一个伪DISTINCT,因为您要放弃结果。在最后一种情况下,服务器能够检测到这一点,因为只涉及唯一id列,只需放弃该不同的操作即可。在前一种情况下,它必须计算distinct on以找到返回的ID值,然后才能对其进行筛选。这两个查询返回不同的结果。如果一封电子邮件的id小于123,则第一封邮件可能不会返回任何结果。在第二种情况下,总是有一个结果作为唯一索引上的一个DISTINCT,它只返回索引
值我已使用解释、分析、缓冲区更新了问题
Sort Method: external merge  Disk: 2304kB
          ->  Seq Scan on users  (cost=0.00..3494.80 rows=67380