PostgreSQL可以对数组列创建索引吗?

223

在文档中,我找不到对这个问题的明确答案。如果一个列是数组类型,那么所有输入的值都会被单独索引吗?

我创建了一张简单的表格,其中只有一个int[]列,并在其上放置了一个唯一索引。我注意到我不能添加相同的int数组,这让我认为索引是数组项的组合,而不是每个项的索引。

INSERT INTO "Test"."Test" VALUES ('{10, 15, 20}');
INSERT INTO "Test"."Test" VALUES ('{10, 20, 30}');

SELECT * FROM "Test"."Test" WHERE 20 = ANY ("Column1");

这个索引有助于这个查询吗?


能否使用数据类型 jsonb 并使用索引?请参考 https://www.postgresql.org/docs/9.5/static/functions-json.html 和 https://www.postgresql.org/docs/9.5/static/datatype-json.html#JSON-INDEXING。 - user3791372
3个回答

265

是的,你可以对数组进行索引,但是你必须使用数组操作符GIN索引类型

示例:

    CREATE TABLE "Test"("Column1" int[]);
    INSERT INTO "Test" VALUES ('{10, 15, 20}');
    INSERT INTO "Test" VALUES ('{10, 20, 30}');
    
    CREATE INDEX idx_test on "Test" USING GIN ("Column1" gin__int_ops);
            
    EXPLAIN ANALYZE
    SELECT * FROM "Test" WHERE "Column1" @> ARRAY[20];

结果:

Bitmap Heap Scan on "Test"  (cost=4.26..8.27 rows=1 width=32) (actual time=0.014..0.015 rows=2 loops=1)
  Recheck Cond: ("Column1" @> '{20}'::integer[])
  ->  Bitmap Index Scan on idx_test  (cost=0.00..4.26 rows=1 width=0) (actual time=0.009..0.009 rows=2 loops=1)
        Index Cond: ("Column1" @> '{20}'::integer[])
Total runtime: 0.062 ms

注意
在许多情况下,似乎需要使用gin__int_ops选项。
create index <index_name> on <table_name> using GIN (<column> gin__int_ops)

我还没有看到一个情况,在没有使用gin__int_ops选项的情况下,它可以与&&和@>运算符一起工作。

25
正如 OP 所猜测的那样,这实际上并没有索引每个数组值,而是索引整个数组。因此,虽然这将有助于查询(请参见执行计划),但这意味着您无法轻松地在单个数组值上创建唯一约束。也就是说,如果您使用的是整数数组,则可以使用 contrib 模块“intarray”来索引单个数组值,在许多情况下可以提高速度。(如果我没记错的话,对于文本值正在进行一些工作,但贡献者可能会很乐意帮助完成它)。 - xzilla
18
请在代码示例中不要使用PostgreSQL标识符的大写字母,这样会让那些不熟悉引用/大小写折叠规则尤其是新手的人感到困惑。 - intgr
12
重复我的评论:根据我的经验,这些索引提供的速度提升非常少或根本没有,除非对于integer[]列使用gin__int_ops。我花了多年的时间感到沮丧并寻找其他解决方案,直到我发现了这个操作类。它是一个接近奇迹的工具。 - IamIC
5
那是否意味着我不应该对字符串数组进行索引?我只应该对整数数组进行索引吗? - ryan2johnson9
3
如果您安装了"intarray"扩展,那么操作类"gin__int_ops"才是必需的,否则索引将默认工作。关于此问题,我在这里进行了更详细的解释:https://dev59.com/x73pa4cB1Zd3GeqPclkZ#63996455 - Simon Perepelitsa
@xzilla 你确定吗?“广义倒排索引”的定义恰好是它可以索引复合值的事实(不像btree索引整个值)。 - collimarco

143

@Tregoreg在他提供的赏金评论中提出了一个问题:

我没有找到当前的答案有效。在数组类型的列上使用GIN索引不能增加ANY()运算符的性能。真的没有解决方案吗?

@Frank的被接受的答案告诉你要使用数组运算符,这对于Postgres 11仍然是正确的。手册:

... the standard distribution of PostgreSQL includes a GIN operator class for arrays, which supports indexed queries using these operators:

<@
@>
=
&&

标准发行版中GIN索引的内置运算符类完整列表在此处。

在Postgres中,索引绑定到运算符(这些运算符针对特定类型实现),而不是仅针对数据类型、函数或其他任何内容。这是来自原始Berkeley Postgres设计的遗产,现在很难改变。但它通常工作得很好。这里有一个关于pgsql-bugs的线程,Tom Lane在其中发表了评论。

一些PostGis 函数(例如ST_DWithin())似乎违反了这个原则,但事实并非如此。这些函数在内部重写以使用相应的运算符

索引表达式必须在运算符的左侧。对于大多数运算符(包括上述所有运算符),如果您将索引表达式放在右侧,则查询规划程序可以通过翻转操作数来实现此目的 - 假设已定义了 COMMUTATORANY构造可以与各种运算符结合使用,但本身不是运算符。当作为 constant = ANY (array_expression) 使用时,只有支持 = 运算符的数组元素上的索引才能符合要求,并且我们需要一个 = ANY() 的交换律。GIN 索引不适用。
Postgres目前还不足够智能化,无法从中派生GIN可索引表达式。 constant = ANY (array_expression)array_expression @> ARRAY [constant]并非完全等效。如果涉及到任何NULL元素,则数组运算符会返回错误,而ANY结构可以处理两侧的NULL。而且对于数据类型不匹配,结果也会有所不同。
相关答案:

附注

在使用整数数组(int4,而不是int2int8)且没有NULL值(就像您的示例所示)时,请考虑使用附加模块intarray,它提供了专门的、更快速的运算符和索引支持。请参见:

至于您问题中未回答的UNIQUE约束:这是通过对整个数组值建立btree索引来实现的(就像您怀疑的那样),并且根本无法帮助搜索元素。详情请见:


1
啊啊啊,现在感到非常尴尬,但我没有想到即使在理论上可能,Postgres也不会使用索引。也许这也是因为我对Postgres的洞察力不足,例如索引绑定运算符。谢谢你抽出时间来回答我提出不恰当的问题并分享你的知识! - Tregoreg
7
@Tregoreg: 不要太尴尬,这并不是太明显。我记得第一次遇到这个问题时也感到困惑。增加的问题和澄清应该对公众非常有用。 - Erwin Brandstetter
1
根据我的经验,这些索引几乎没有任何加速效果,除非对于integer[]列使用gin__int_ops。我花了多年的时间感到沮丧并寻找其他解决方案,直到我发现了这个操作类。它是一个近乎神奇的工具。 - IamIC
2
@IamIC:我在intarray中添加了指针。正如你所指出的那样,这似乎值得注意。 - Erwin Brandstetter
对于形如 ANY (array_expression) = constant 的表达式,GIN 索引是否有效? - user10375

37

现在可以对单个数组元素进行索引。例如:

CREATE TABLE test (foo int[]);
INSERT INTO test VALUES ('{1,2,3}');
INSERT INTO test VALUES ('{4,5,6}');
CREATE INDEX test_index on test ((foo[1]));
SET enable_seqscan TO off;

EXPLAIN ANALYZE SELECT * from test WHERE foo[1]=1;
                                                QUERY PLAN                                                    
------------------------------------------------------------------------------------------------------------------
 Index Scan using test_index on test  (cost=0.00..8.27 rows=1 width=32) (actual   time=0.070..0.071 rows=1 loops=1)
   Index Cond: (foo[1] = 1)
 Total runtime: 0.112 ms
(3 rows)

这适用于至少Postgres 9.2.1版本。需要注意的是,您需要为每个数组索引构建单独的索引,在我的示例中,我仅对第一个元素进行了索引。


39
不要忽视这一点——对于想要使用ANY()运算符的可变长度数组来说,这种方法是无望的。 - Καrτhικ
39
这并不是非常有用。如果你有一个固定数量的数组元素,你最好为每个元素使用单独的列(和普通的btree索引),而不是为每个数组项构建更昂贵的表达式索引。单独列的存储成本要低得多,也没有数组开销。 - Erwin Brandstetter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接