【问题标题】:Postgres partial vs regular / full index on nullable column可空列上的 Postgres 部分与常规/完整索引
【发布时间】:2022-08-16 16:13:52
【问题描述】:

我有一个包含 1m 条记录的表,其中 100k 条记录在 colA 上为空。其余记录具有非常不同的值,在此列上创建常规索引与使用where colA is not null 创建部分索引有区别吗?

既然普通的 Postgres 索引不存储 NULL 值,那不就和用where colA is not null 创建部分索引一样吗?
任何一个索引都有什么优点或缺点?

  • 好问题。我不确定 PostgreSQL 是否排除空值。我知道 Oracle 没有,但我的印象是 PostgreSQL 包含它们。
  • 任一索引的有用性在很大程度上取决于查询的细节、它们的频率和优先级、确切的表定义、写入频率和表的VACUUM、数据类型和平均值。 colA 的大小(与平均行大小相比)和(附加)基数。

标签: postgresql indexing null partial-index


【解决方案1】:

如果您创建没有空值的部分索引,它将不会使用它来查找空值。

这是一个关于 13.5 的完整索引的测试。

# create index idx_test_num on test(num);
CREATE INDEX

# explain select count(*) from test where num is null;
                                     QUERY PLAN                                      
-------------------------------------------------------------------------------------
 Aggregate  (cost=5135.00..5135.01 rows=1 width=8)
   ->  Bitmap Heap Scan on test  (cost=63.05..5121.25 rows=5500 width=0)
         Recheck Cond: (num IS NULL)
         ->  Bitmap Index Scan on idx_test_num  (cost=0.00..61.68 rows=5500 width=0)
               Index Cond: (num IS NULL)
(5 rows)

并带有部分索引。

# create index idx_test_num on test(num) where num is not null;
CREATE INDEX

# explain select count(*) from test where num is null;
                                      QUERY PLAN                                      
--------------------------------------------------------------------------------------
 Finalize Aggregate  (cost=10458.12..10458.13 rows=1 width=8)
   ->  Gather  (cost=10457.90..10458.11 rows=2 width=8)
         Workers Planned: 2
         ->  Partial Aggregate  (cost=9457.90..9457.91 rows=1 width=8)
               ->  Parallel Seq Scan on test  (cost=0.00..9352.33 rows=42228 width=0)
                     Filter: (num IS NULL)
(6 rows)

由于常规 postgres 索引不存储 NULL 值...

自 16 年前的 8.2 版 [检查笔记] 以来,情况并非如此。 8.2 docs 说...

默认情况下,索引不用于 IS NULL 子句。在这种情况下使用索引的最佳方法是使用 IS NULL 谓词创建部分索引。

8.3 introduced nulls firstnulls last 以及围绕空值的许多其他改进,包括...

允许 col IS NULL 使用索引 (Teodor)

【讨论】:

  • 通常,当所有行中有 10% 符合条件时,Postgres 不会进行位图索引扫描。
【解决方案2】:

这一切都取决于。

NULL 值自 Postgres 8.3 版起包含在(默认)B-tree 索引中,就像 Schwern 提供的一样。但是,您提到的谓词(where colA is not null)仅得到适当支持,因为Postgres 9.0. The release notes:

允许IS NOT NULL 限制使用索引 (Tom Lane)

这对于查找MAX()/MIN() 包含许多空值的索引。

GIN indexes 紧随其后:

从 PostgreSQL 9.1 开始,空键值可以包含在索引中。

通常,如果部分索引从索引中排除了表的主要部分,则它会变得更小并节省对索引的写入。由于 B-tree 索引非常浅,因此裸寻道性能可以惊人地扩展(一旦索引被缓存)。减少 10 % 的索引条目在该领域几乎无关紧要。

您的案例将仅排除所有行的 10% 左右,而且很少有回报。部分索引为查询计划器增加了一些开销,并排除了与索引条件不匹配的查询。 (如果匹配不是很明显,Postgres 查询规划器不会努力。)

OTOH,Postgres 很少使用索引来检索表的 10% 的谓词 - 顺序扫描通常会更快。再次,这取决于。

如果(几乎)所有查询都排除 NULL 无论如何(以 Postgres 规划器理解的方式),那么仅排除所有行的 10% 的部分索引仍然是一个明智的选择。但如果查询模式发生变化,它可能会适得其反。增加的复杂性可能不值得。

另外值得注意的是,Postgres 索引中仍然存在 NULL 值的极端情况。我最近遇到了这种情况,当使用IS NULL 过滤第一个索引表达式时,Postgres 证明不愿意从多列索引中读取已排序的行(使部分索引更适合这种情况):

db<>小提琴here

因此,这取决于完整的图片。

【讨论】:

    猜你喜欢
    • 2012-09-06
    • 1970-01-01
    • 2019-10-02
    • 1970-01-01
    • 2020-12-24
    • 1970-01-01
    • 2013-02-02
    • 1970-01-01
    • 2023-03-06
    相关资源
    最近更新 更多