在 PostgreSQL 中使用索引

cod*_*ool 85 postgresql index primary-key

我有几个关于在 PostgreSQL 中使用索引的问题。我有一个Friends带有以下索引的表:

   Friends ( user_id1 ,user_id2) 
Run Code Online (Sandbox Code Playgroud)

user_id1并且user_id2user表的外键

  1. 这些是等价的吗?如果不是,那为什么?

    Index(user_id1,user_id2) and Index(user_id2,user_id1)
    
    Run Code Online (Sandbox Code Playgroud)
  2. 如果我创建主键(user_id1,user_id2),它会自动为它创建索引吗?

    如果第一个问题中的索引不相等,那么在上面的主键命令上创建了哪个索引?

Erw*_*ter 89

这个答案是关于(默认)B 树索引的。稍后查看有关 GiST、GIN 等的相关回答:


以下是在多列索引第二列上查询表的结果。
效果很容易为任何人重现。回家试试。

我在 Debian 上使用PostgreSQL 9.0.5进行了测试,使用了一个中等大小的真实数据库表,其中包含 23322 行。它实现了表adr(地址)和att(属性)之间的 n:m 关系,但这与此处无关。简化架构:

CREATE TABLE adratt (
  adratt_id serial PRIMARY KEY
, adr_id    integer NOT NULL
, att_id    integer NOT NULL
, log_up    timestamp NOT NULL DEFAULT (now()::timestamp)
, CONSTRAINT adratt_uni UNIQUE (adr_id, att_id)
);
Run Code Online (Sandbox Code Playgroud)

UNIQUE约束有效地实现了唯一索引。我用一个简单的索引重复了测试,以确保得到与预期相同的结果。

CREATE INDEX adratt_idx ON adratt(adr_id, att_id);
Run Code Online (Sandbox Code Playgroud)

该表聚集在adratt_uni索引上,并且在我运行的测试之前:

CLUSTER adratt;
ANALYZE adratt;
Run Code Online (Sandbox Code Playgroud)

查询的顺序扫描(adr_id, att_id)尽可能快。多列索引仍然可以单独用于第二个索引列的查询条件。

我运行了几次查询来填充缓存,并选择了十次运行中最好的来获得可比较的结果。

1. 使用两列查询

SELECT *
FROM   adratt
WHERE  att_id = 90
AND    adr_id = 10;
Run Code Online (Sandbox Code Playgroud)
CREATE INDEX adratt_idx ON adratt(adr_id, att_id);
Run Code Online (Sandbox Code Playgroud)

的输出EXPLAIN ANALYZE

CLUSTER adratt;
ANALYZE adratt;
Run Code Online (Sandbox Code Playgroud)

2. 使用第一列查询

SELECT * FROM adratt WHERE adr_id = 10;
Run Code Online (Sandbox Code Playgroud)
SELECT *
FROM   adratt
WHERE  att_id = 90
AND    adr_id = 10;
Run Code Online (Sandbox Code Playgroud)

的输出EXPLAIN ANALYZE

 adratt_id | adr_id | att_id |       log_up
-----------+--------+--------+---------------------
       123 |     10 |     90 | 2008-07-29 09:35:54
(1 row)
Run Code Online (Sandbox Code Playgroud)

3. 使用第二列查询

SELECT * FROM adratt WHERE att_id = 90;
Run Code Online (Sandbox Code Playgroud)
Index Scan using adratt_uni on adratt  (cost=0.00..3.48 rows=1 width=20) (actual time=0.022..0.025 rows=1 loops=1)
  Index Cond: ((adr_id = 10) AND (att_id = 90))
Total runtime: 0.067 ms
Run Code Online (Sandbox Code Playgroud)

的输出EXPLAIN ANALYZE

SELECT * FROM adratt WHERE adr_id = 10;
Run Code Online (Sandbox Code Playgroud)

4.禁用索引扫描和位图扫描

SET enable_indexscan = off;
SELECT * FROM adratt WHERE att_id = 90;
Run Code Online (Sandbox Code Playgroud)

解释分析的输出:

 adratt_id | adr_id | att_id |       log_up
-----------+--------+--------+---------------------
       126 |     10 |     10 | 2008-07-29 09:35:54
       125 |     10 |     13 | 2008-07-29 09:35:54
      4711 |     10 |     21 | 2008-07-29 09:35:54
     29322 |     10 |     22 | 2011-06-06 15:50:38
     29321 |     10 |     30 | 2011-06-06 15:47:17
       124 |     10 |     62 | 2008-07-29 09:35:54
     21913 |     10 |     78 | 2008-07-29 09:35:54
       123 |     10 |     90 | 2008-07-29 09:35:54
     28352 |     10 |    106 | 2010-11-22 12:37:50
(9 rows)
Run Code Online (Sandbox Code Playgroud)
SET enable_bitmapscan = off;
SELECT * FROM adratt WHERE att_id = 90;
Run Code Online (Sandbox Code Playgroud)

的输出EXPLAIN ANALYZE

Index Scan using adratt_uni on adratt  (cost=0.00..8.23 rows=9 width=20) (actual time=0.007..0.023 rows=9 loops=1)
  Index Cond: (adr_id = 10)
Total runtime: 0.058 ms
Run Code Online (Sandbox Code Playgroud)

结论

正如预期的那样,多列索引仅用于对第二列的查询。
正如预期的那样,它的效率较低,但查询仍然比没有索引时快 3 倍
禁用索引扫描后,查询计划器选择位图堆扫描,它的执行速度几乎一样快。只有在禁用它之后,它才会回退到顺序扫描。

请参阅手册中原始引用的其他答案

自 Postgres 9.0 以来的更新

在 Postgres 13 中,一切仍然基本正确。最显着的变化:

都支持指数表现。(不过,顺序扫描也变得更快了。)


a_h*_*ame 31

重新 1) 是和否。

对于同时使用两列的查询,例如where (user_id1, user_id2) = (1,2)创建哪个索引并不重要。

对于仅在其中一列上具有条件的查询,例如where user_id1 = 1它确实很重要,因为通常只有“前导”列可以被优化器用于比较。所以where user_id1 = 1将能够使用索引 (user_id1, user_id2) 但它不能在所有情况下都使用索引 (user_id2, user_id1)。

在玩弄这个之后(在 Erwin 向我们展示了它工作的设置之后),这似乎在很大程度上取决于第二列的数据分布,尽管我还没有发现哪种情况使优化器能够使用尾随列对于 WHERE 条件。

Oracle 11 也可以(有时)使用不在索引定义开头的列。

re 2)是的,它会创建一个索引

从手册中引用

添加主键将自动在主键中使用的列或列组上创建唯一的 btree 索引。

re 2a)Primary Key (user_id1,user_id2)将在 (user_id1,user_id2) 上创建一个索引(您可以通过简单地创建这样的主键容易地自己找到)

我强烈建议您阅读手册中有关索引章节,它基本上回答了上述所有问题。

另外,要创建什么索引?by depesz 很好地解释了索引列和其他索引相关主题的顺序。


Erw*_*ter 12

广告 1)
PostgreSQL 中存在一些限制,例如 @a_horse_with_no_name 描述。在8.0 版之前,多列索引只能用于对前导列的查询。这在 8.1 版中得到了改进。Postgres 10(更新)的当前手册解释了:

多列 B 树索引可与涉及索引列的任何子集的查询条件一起使用,但当对前导(最左侧)列有约束时,索引最有效。确切的规则是,前导列上的等式约束,加上没有等式约束的第一列上的任何不等式约束,将用于限制被扫描的索引部分。这些列右侧的列的约束在索引中检查,因此它们可以正确地保存对表的访问,但它们不会减少必须扫描的索引部分。例如,给定一个索引 on(a, b, c)和一个查询条件WHERE a = 5 AND b >= 42 AND c < 77,索引必须从第一个条目开始扫描a= 5 和b= 42 一直最后一个条目a= 5. c>= 77 的索引条目将被跳过,但仍需对其进行扫描。这个索引原则上可以用于有约束b和/或c没有约束的查询a——但是必须扫描整个索引,所以在大多数情况下,计划者更喜欢顺序表扫描而不是使用索引。

强调我的。我可以从经验中证实这一点。
另请参阅此处添加了我后来的答案的测试用例。


Erw*_*ter 12

这是对杰克的回答的回复,评论是行不通的。

在9.2 版本之前的PostgreSQL没有覆盖索引。由于 MVCC 模型,必须访问结果集中的每个元组以检查可见性。您可能会想到 Oracle。

PostgreSQL 开发人员谈论“仅索引扫描”。实际上,该功能已随 Postgres 9.2 发布。阅读提交消息
Depesz 写了一篇内容丰富的博客文章

INCLUDEPostgres 11的子句引入了真正的覆盖索引(更新)。相关:

这也有点不对:

它依赖于这样一个事实:由于表中没有出现在索引中的额外列,索引的“完全扫描”通常比索引表的“完全扫描”更快。

正如对我的其他答案的评论中所报告的那样,我还使用一个包含两个整数的表进行了测试,仅此而已。索引包含与表相同的列。btree 索引的大小大约是表大小的 2/3。不足以解释因素 3 的加速。根据您的设置,我运行了更多测试,简化为两列和 100000 行。在我的 PostgreSQL 9.0 安装中,结果是一致的。

如果表有额外的列,索引的加速会变得更加显着,但这肯定不是这里的唯一因素

总结一下主要的几点:

  • 多列索引可用于非前导列上的查询,但对于选择性条件(结果中的行的百分比很小),加速仅约为 3 倍。较大的元组较高,结果集中表的较大部分较低。

  • 如果性能很重要,则在这些列上创建一个额外的索引。

  • 如果所有涉及的列都包含在索引(覆盖索引)中,并且所有涉及的行(每个块)对所有事务都是可见的,您可以在 pg 9.2 或更高版本中获得“仅索引扫描”


Jac*_*las 8

  1. 这些是等价的吗?如果不是,那为什么?

    索引(user_id1,user_id2) 和索引(user_id2,user_id1)

这些不是等价的,一般来说 index(bar,baz) 对表单的查询效率不高 select * from foo where baz=?

Erwin已经证明,这样的索引确实可以加快查询速度,但这种效果是有限的,并且与您通常期望索引改进查找的顺序不同 - 它依赖于这样一个事实,即索引的“完整扫描”通常是由于表中没有出现在索引中的额外列,因此比索引表的“完全扫描”更快。

总结:索引可以帮助查询即使在非前导列上,但以两种次要且相对次要的方式之一,而不是以您通常期望索引提供帮助的戏剧性方式,因为它是 btree 结构

nb 索引可以提供帮助的两种方式是,如果对索引的完全扫描比对表的完全扫描便宜得多,并且: 1. 表查找很便宜(因为它们很少或者它们是聚集的),或者2. 索引被覆盖,所以根本没有表查找,请参阅此处的Erwins 评论

试验台:

create table foo(bar integer not null, baz integer not null, qux text not null);

insert into foo(bar, baz, qux)
select random()*100, random()*100, 'some random text '||g from generate_series(1,10000) g;
Run Code Online (Sandbox Code Playgroud)

查询 1(无索引,达到74 个缓冲区):

explain (buffers, analyze, verbose) select max(qux) from foo where baz=0;
                                                  QUERY PLAN
--------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=181.41..181.42 rows=1 width=32) (actual time=3.301..3.302 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=74
   ->  Seq Scan on stack.foo  (cost=0.00..181.30 rows=43 width=32) (actual time=0.043..3.228 rows=52 loops=1)
         Output: bar, baz, qux
         Filter: (foo.baz = 0)
         Buffers: shared hit=74
 Total runtime: 3.335 ms
Run Code Online (Sandbox Code Playgroud)

查询 2(使用索引 - 优化器忽略索引 -再次命中74 个缓冲区):

create index bar_baz on foo(bar, baz);

explain (buffers, analyze, verbose) select max(qux) from foo where baz=0;
                                                  QUERY PLAN
--------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=199.12..199.13 rows=1 width=32) (actual time=3.277..3.277 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=74
   ->  Seq Scan on stack.foo  (cost=0.00..199.00 rows=50 width=32) (actual time=0.043..3.210 rows=52 loops=1)
         Output: bar, baz, qux
         Filter: (foo.baz = 0)
         Buffers: shared hit=74
 Total runtime: 3.311 ms
Run Code Online (Sandbox Code Playgroud)

查询 2(使用索引 - 我们欺骗优化器使用它):

explain (buffers, analyze, verbose) select max(qux) from foo where bar>-1000 and baz=0;
                                                       QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=115.56..115.57 rows=1 width=32) (actual time=1.495..1.495 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=36 read=30
   ->  Bitmap Heap Scan on stack.foo  (cost=73.59..115.52 rows=17 width=32) (actual time=1.370..1.428 rows=52 loops=1)
         Output: bar, baz, qux
         Recheck Cond: ((foo.bar > (-1000)) AND (foo.baz = 0))
         Buffers: shared hit=36 read=30
         ->  Bitmap Index Scan on bar_baz  (cost=0.00..73.58 rows=17 width=0) (actual time=1.356..1.356 rows=52 loops=1)
               Index Cond: ((foo.bar > (-1000)) AND (foo.baz = 0))
               Buffers: shared read=30
 Total runtime: 1.535 ms
Run Code Online (Sandbox Code Playgroud)

因此,在这种情况下,通过索引的访问速度是30 个缓冲区的两倍——就索引而言,“稍微快一点”!,YMMV 取决于表和索引的相对大小,以及过滤的行数和集群特征表中的数据

相比之下,前导列上的查询使用索引的 btree 结构 - 在这种情况下会命中2 个缓冲区

explain (buffers, analyze, verbose) select max(qux) from foo where bar=0;
                                                       QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=75.70..75.71 rows=1 width=32) (actual time=0.172..0.173 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=38
   ->  Bitmap Heap Scan on stack.foo  (cost=4.64..75.57 rows=50 width=32) (actual time=0.036..0.097 rows=59 loops=1)
         Output: bar, baz, qux
         Recheck Cond: (foo.bar = 0)
         Buffers: shared hit=38
         ->  Bitmap Index Scan on bar_baz  (cost=0.00..4.63 rows=50 width=0) (actual time=0.024..0.024 rows=59 loops=1)
               Index Cond: (foo.bar = 0)
               Buffers: shared hit=2
 Total runtime: 0.209 ms
Run Code Online (Sandbox Code Playgroud)