Answers:
这是查询多列索引第二列上的表的结果。
效果很容易复制给任何人。在家尝试一下。
我在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(0) NOT NULL DEFAULT (now())::timestamp(0)
, CONSTRAINT adratt_uni UNIQUE (adr_id, att_id)
);
该UNIQUE
约束有效地实现了唯一索引。我用一个简单的索引重复了测试,以确保并得到与预期相同的结果。
CREATE INDEX adratt_idx ON adratt(adr_id, att_id)
该表聚集在adratt_uni
索引上,并且在我运行测试之前:
CLUSTER adratt;
ANALYZE adratt;
对查询的顺序扫描(adr_id, att_id)
尽可能快。仅在第二个索引列上,多列索引仍将用于查询条件。
我运行了几次查询以填充缓存,然后从十次运行中挑选出最好的一次以获得可比较的结果。
SELECT *
FROM adratt
WHERE att_id = 90
AND adr_id = 10;
adratt_id | adr_id | att_id | log_up
-----------+--------+--------+---------------------
123 | 10 | 90 | 2008-07-29 09:35:54
(1 row)
输出EXPLAIN ANALYZE
:
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
SELECT * FROM adratt WHERE adr_id = 10
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)
输出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
SELECT * FROM adratt WHERE att_id = 90
adratt_id | adr_id | att_id | log_up
-----------+--------+--------+---------------------
123 | 10 | 90 | 2008-07-29 09:35:54
180 | 39 | 90 | 2008-08-29 15:46:07
...
(83 rows)
输出EXPLAIN ANALYZE
:
Index Scan using adratt_uni on adratt (cost=0.00..818.51 rows=83 width=20) (actual time=0.014..0.694 rows=83 loops=1) Index Cond: (att_id = 90) Total runtime: 0.849 ms
SET enable_indexscan = off;
SELECT * FROM adratt WHERE att_id = 90
EXPLAIN ANALYZE的输出:
Bitmap Heap Scan on adratt (cost=779.94..854.74 rows=83 width=20) (actual time=0.558..0.743 rows=83 loops=1) Recheck Cond: (att_id = 90) -> Bitmap Index Scan on adratt_uni (cost=0.00..779.86 rows=83 width=0) (actual time=0.544..0.544 rows=83 loops=1) Index Cond: (att_id = 90) Total runtime: 0.894 ms
SET enable_bitmapscan = off
SELECT * FROM adratt WHERE att_id = 90
输出EXPLAIN ANALYZE
:
Seq Scan on adratt (cost=0.00..1323.10 rows=83 width=20) (actual time=0.009..2.429 rows=83 loops=1) Filter: (att_id = 90) Total runtime: 2.680 ms
不出所料,多列索引仅用于第二列的查询。
不出所料,它的效率较低,但是查询仍然比没有索引的查询快3倍。
禁用索引扫描后,查询计划器将选择一个位图堆扫描,其执行速度几乎相同。也只有在禁用该选项后,它才会退回到顺序扫描。
vacuum full
和a reindex
。除此之外,它将有助于对第一列或前两个列进行索引扫描,但会损害第二列上的查询。在一个新的聚集表中,第二列中具有相同值的行被分散开,因此必须读取最多的块。
关于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友好地向我们展示了一个可行的设置之后),这似乎很大程度上取决于第二列的数据分布,尽管我尚未发现哪种情况可以使优化器使用尾随列在哪里条件。
Oracle 11也可以(有时)使用不在索引定义开头的列。
关于2)是的,它将创建一个索引
添加主键将自动在主键中使用的列或一组列上创建唯一的btree索引。
re 2a)Primary Key (user_id1,user_id2)
将在(user_id1,user_id2)上创建一个索引(您只需创建这样的主键就可以很容易地自己找到索引)
我强烈建议您阅读手册中有关索引的章节,它基本上可以回答以上所有问题。
另外,要创建什么索引?depesz的文章做得很好,解释了索引列和其他与索引相关的主题的顺序。
广告1)
PostgreSQL中有一些限制,例如@a_horse_with_no_name describes。直到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
-但必须扫描整个索引,因此在大多数情况下,计划者宁愿使用顺序表扫描,也不愿使用索引。
这是对杰克回答的答复,不会发表评论。
9.2版之前的PostgreSQL中没有覆盖索引。由于MVCC模型,必须访问结果集中的每个元组以检查可见性。您可能正在考虑Oracle。
PostgreSQL开发人员谈论“仅索引扫描”。实际上,该功能已随Postgres 9.2一起发布。阅读提交消息。
Depesz写了一篇非常有用的博客文章。
INCLUDE
带有Postgres 11 的子句引入了真正的覆盖索引(更新)。
这也有些偏离:
它依赖于以下事实:对索引的“完全扫描”通常比对索引表的“完全扫描”更快,这是因为表中没有出现在索引中的多余列。
正如我对其他答案的评论中所报告的那样,我还使用两个整数表运行了测试,而没有其他任何东西。索引与表具有相同的列。btree索引的大小约为表大小的2/3。不足以说明加速因子3。根据您的设置,我进行了更多测试,简化为两列并具有100000行。在我的PostgreSQL 9.0安装中,结果是一致的。
如果表中有其他列,则使用index的加速会变得更重要,但这当然不是唯一的因素。
多列索引可用于对非前导列的查询,但是对于选择条件(结果中行的百分比很小),提速仅是系数3左右。对于较大的元组,较高,对于结果集中的表的较大部分,较低。
如果性能很重要,请在这些列上创建一个附加索引。
如果所有涉及的列都包含在索引(覆盖索引)中,并且所有涉及的行(每个块)对所有事务可见,则可以在pg 9.2或更高版本中获得“仅索引扫描”。
这些等效吗?如果没有,那为什么呢?
索引(user_id1,user_id2)和索引(user_id2,user_id1)
这些不是等效的,通常来说index(bar,baz)对于查询以下形式将无效 select * from foo where baz=?
Erwin 已经证明了这样的索引确实可以加快查询速度,但是这种效果是有限的,并且与您通常期望索引可以改善查询的顺序不同-它依赖于这样的事实,即对索引的“全图扫描”通常是由于表中没有出现在索引中的多余列,因此比索引表的“完全扫描”要快。
简介:索引甚至可以帮助对非前导列进行查询,但是索引可以通过二级和相对较小的一种方式进行,而不是通常因为索引的btree结构而以戏剧性的方式获得期望
注意,索引的两种扫描方式是:对索引的完全扫描比对表的完全扫描便宜得多,或者: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;
查询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
查询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
查询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
因此,在这种情况下,通过索引的访问速度达到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