PostgreSQL:创建一个索引以快速区分NULL和非NULL值

Ada*_*tan 29 sql postgresql indexing null

考虑具有以下WHERE谓词的SQL查询:

...
WHERE name IS NOT NULL
...
Run Code Online (Sandbox Code Playgroud)

namePostgreSQL中的文本字段在哪里.

没有其他查询检查此值的任何文本属性,只是它是否是NULL.因此,完整的btree索引似乎是一种矫枉过正,即使它支持这种区别:

此外,索引列上的IS NULL或IS NOT NULL条件可以与B树索引一起使用.

什么是正确的PostgreSQL索引来快速区分NULLs和非NULLs?

jpm*_*c26 26

我在解释你声称它在两个方面"过度杀伤":在复杂性方面(使用B-Tree而不仅仅是列表)和空间/性能.

对于复杂性而言,它并不过分.B树索引是优选的,因为从中删除将比某种"无序"索引更快(因为缺少更好的术语).(无序索引需要完全索引扫描才能删除.)鉴于这一事实,无序索引的任何收益通常都会被损害所抵消,因此开发工作是不合理的.

但是,对于空间和性能,如果您想要一个高度选择性的效率索引,您可以WHERE在索引中包含一个子句,如精细手册中所述:

CREATE INDEX ON my_table (name) WHERE name IS NOT NULL;
Run Code Online (Sandbox Code Playgroud)

请注意,如果它允许PostgreSQL 在执行查询时忽略大量行,您将只看到此索引的好处.例如,如果99%的行都有name IS NOT NULL,那么只要让全表扫描发生,索引就不会为你买任何东西; 实际上,效率会降低(如@CraigRinger所说),因为它需要额外的磁盘读取.但是,如果只有1%的行有name IS NOT NULL,那么这代表了巨大的节省,因为PostgreSQL可以忽略查询的大部分表.如果你的表非常大,即使消除了50%的行也许是值得的.这是一个调优问题,索引是否有价值将在很大程度上取决于数据的大小和分布.

此外,如果您仍需要name IS NULL行的另一个索引,则空间方面的收益很少.有关详细信息,请参阅Craig Ringer的答案.

  • 很好解释。事实上,将索引用于匹配 99% 的行的内容将非常低效,比 seqscan 慢得多。 (3认同)

Cra*_*ger 14

您可以使用表达式索引,但不应该.保持简单,并使用普通的b树.


可以在以下位置创建表达式索引colname IS NOT NULL:

test=> CREATE TABLE blah(name text);
CREATE TABLE
test=> CREATE INDEX name_notnull ON blah((name IS NOT NULL));
CREATE INDEX
test=> INSERT INTO blah(name) VALUES ('a'),('b'),(NULL);
INSERT 0 3
test=> SET enable_seqscan = off;
SET
craig=> SELECT * FROM blah WHERE name IS NOT NULL;
 name 
------
 a
 b
(2 rows)

test=> EXPLAIN SELECT * FROM blah WHERE name IS NOT NULL;
                                 QUERY PLAN                                  
-----------------------------------------------------------------------------
 Bitmap Heap Scan on blah  (cost=9.39..25.94 rows=1303 width=32)
   Filter: (name IS NOT NULL)
   ->  Bitmap Index Scan on name_notnull  (cost=0.00..9.06 rows=655 width=0)
         Index Cond: ((name IS NOT NULL) = true)
(4 rows)

test=> SET enable_bitmapscan = off;
SET
test=> EXPLAIN SELECT * FROM blah WHERE name IS NOT NULL;
                                  QUERY PLAN                                  
------------------------------------------------------------------------------
 Index Scan using name_notnull on blah  (cost=0.15..55.62 rows=1303 width=32)
   Index Cond: ((name IS NOT NULL) = true)
   Filter: (name IS NOT NULL)
(3 rows)
Run Code Online (Sandbox Code Playgroud)

......但Pg没有意识到它也适用于IS NULL:

test=> EXPLAIN SELECT * FROM blah WHERE name IS NULL;
                               QUERY PLAN                                
-------------------------------------------------------------------------
 Seq Scan on blah  (cost=10000000000.00..10000000023.10 rows=7 width=32)
   Filter: (name IS NULL)
(2 rows)
Run Code Online (Sandbox Code Playgroud)

甚至转变NOT (name IS NOT NULL)name IS NULL,这通常是你想要的.

test=> EXPLAIN SELECT * FROM blah WHERE NOT (name IS NOT NULL);
                               QUERY PLAN                                
-------------------------------------------------------------------------
 Seq Scan on blah  (cost=10000000000.00..10000000023.10 rows=7 width=32)
   Filter: (name IS NULL)
(2 rows)
Run Code Online (Sandbox Code Playgroud)

所以你最好使用两个不相交的表达式索引,一个在null上,另一个在非null集上.

test=> DROP INDEX name_notnull ;
DROP INDEX
test=> CREATE INDEX name_notnull ON blah((name IS NOT NULL)) WHERE (name IS NOT NULL);
CREATE INDEX
test=> EXPLAIN SELECT * FROM blah WHERE name IS NOT NULL;
                                QUERY PLAN                                
--------------------------------------------------------------------------
 Index Scan using name_notnull on blah  (cost=0.13..8.14 rows=3 width=32)
   Index Cond: ((name IS NOT NULL) = true)
(2 rows)

test=> CREATE INDEX name_null ON blah((name IS NULL)) WHERE (name IS NULL);
CREATE INDEX
craig=> EXPLAIN SELECT * FROM blah WHERE name IS NULL;
                              QUERY PLAN                               
-----------------------------------------------------------------------
 Index Scan using name_null on blah  (cost=0.12..8.14 rows=1 width=32)
   Index Cond: ((name IS NULL) = true)
(2 rows)
Run Code Online (Sandbox Code Playgroud)

这非常令人毛骨悚然.对于最明智的用途,我只使用普通的b树索引.索引大小的改进并不太令人兴奋,至少对于小小的输入,就像我用一堆md5值创建的虚拟:

test=> SELECT pg_size_pretty(pg_relation_size('blah'));
 pg_size_pretty 
----------------
 9416 kB
(1 row)

test=> SELECT pg_size_pretty(pg_relation_size('blah_name'));
 pg_size_pretty 
----------------
 7984 kB
(1 row)

test=> SELECT pg_size_pretty(pg_relation_size('name_notnull'));
 pg_size_pretty 
----------------
 2208 kB
(1 row)

test=> SELECT pg_size_pretty(pg_relation_size('name_null'));
 pg_size_pretty 
----------------
 2208 kB
(1 row)
Run Code Online (Sandbox Code Playgroud)


fxt*_*cle 5

您可以使用像 (title IS NULL) 这样的表达式作为索引列。所以这按预期工作:

CREATE INDEX index_articles_on_title_null ON articles ( (title IS NULL) );
SELECT * FROM articles WHERE (title IS NULL)='t';
Run Code Online (Sandbox Code Playgroud)

这比使用谓词有很大的优势,在这种情况下,存储在索引中的值只是一个是/否布尔值,而不是完整的列值。因此,特别是如果您的 NULL 检查列往往包含大值(如此处的标题文本字段),那么这种索引方式比使用谓词索引更节省空间。

  • 你不需要`= 't'`。使用`WHERE title IS NULL` 就可以了 (6认同)