索引主要被用来提升数据库性能,不当的使用会导致性能变差。 PostgreSQL 提供了多种索引类型: B-tree、Hash、GiST、SP-GiST 、GIN 和 BRIN。每一种索引类型使用了一种不同的算法来适应不同类型的查询。默认情况下,CREATE INDEX 命令创建适合于大部分情况的 B-tree 索引。
多列索引:目前,只有 B-tree、GiST、GIN 和 BRIN 索引类型支持多列索引,最多可以指定32个列(该限制可以在源代码文件 pg_config_manual.h
中修改,但是修改后需要重新编译PostgreSQL)。
CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] name ] ON [ ONLY ] table_name [ USING method ]
( { column_name | ( expression ) } [ COLLATE collation ] [ opclass [ ( opclass_parameter = value [, ... ] ) ] ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] [, ...] )
[ INCLUDE ( column_name [, ...] ) ]
[ WITH ( storage_parameter [= value] [, ... ] ) ]
[ TABLESPACE tablespace_name ]
[ WHERE predicate ]
UNIQUE:唯一索引,在索引被创建时(如果数据已经存在)或者加入数据时检查重复值。
CONCURRENTLY:在构建索引时不会取得任何会阻止该表上并发插入、更新或者删除的锁。而标准的索引构建将会把表锁住以阻止对表的写(但不阻塞读),这种锁定会持续到索引创建完毕。
IF NOT EXISTS:如果一个同名关系已经存在则不要抛出错误。
INCLUDE:指定一个列的列表,其中的列将被包括在索引中作为非键列。不能作为索引扫描的条件,主要作用是相关数据索存储在索引中,访问时无需访问该索引的基表。当前,有B-树和GiST索引访问方法支持这一特性。
name:要创建的索引名称。这里不能包括模式名,因为索引总是被创建在其基表所在的模式中。如果索引名称被省略,PostgreSQL 将基于基表名称和被索引列名称选择一个合适的名称。
ONLY:如果该表是分区表,指示不要在分区上递归创建索引。默认会递归创建索引。
table_name:要被索引的表的名称(可以被模式限定)。
method:要使用的索引方法的名称。可以选择 btree、hash、 gist、spgist、gin以及brin。默认方法是 btree。
column_name:一个表列的名称。
expression:一个基于一个或者更多个表列的表达式。如语法中所示,表达式通常必须被写在圆括号中。不过,如果该表达式是一个函数调用的形式,圆括号可以被省略。
collation:要用于该索引的排序规则的名称。
opclass:一个操作符类的名称。
opclass_parameter:运算符类参数的名称。
ASC:指定上升排序(默认)。
DESC:指定下降排序。
NULLS FIRST:指定把空值排序在非空值前面。在指定DESC时,这是默认行为。
NULLS LAST:指定把空值排序在非空值后面。在没有指定DESC时,这是默认行为。
storage_parameter:索引方法相关的存储参数的名称。可选的WITH子句为索引指定存储参数。每一种 索引方法都有自己的存储参数集合。
B-树、哈希、GiST以及SP-GiST索引方法都接受这个参数:
fillfactor (integer):索引的填充因子是一个百分数,它决定索引方法将尝试填充索引页面的充满程度。对于B-树,在初始的索引构建过程中,叶子页面会被填充至该百分数,当在索引右端扩展索引(增加新的最大键值)时也会这样处理。如果页面后来被完全填满,它们就会被分裂,导致索引的效率逐渐退化。B-树使用了默认的填充因子 90,但是也可以选择为 10 到 100 的任何整数值。如果表是静态的,那么填充因子 100 是最好的,因为它可以让索引的物理尺寸最小化。但是对于更新负荷很重的表,较小的填充因子有利于最小化对页面分裂的需求。其他索引方法以不同但是大致类似的方式使用填充因子,不同方法的默认填充因子也不相同。
deduplicate_items (boolean)
:B 树重复数据删除技术的使用。设置为 ON 或 OFF 以启用或禁用优化。默认值为ON。
vacuum_cleanup_index_scale_factor
:指定在以前的统计信息收集过程中计数到的堆元组总数的一个分数,插入不超过这一数量所代表的元组不会导致VACUUM清理阶段的索引扫描。这个设置当前仅适用于B-树索引。
buffering (enum)
:适用于 GiST 索引,决定是否用缓冲构建技术来构建索引。OFF 会禁用它,ON 则启用该特性,如果设置为 AUTO 则初始会禁用它,但是一旦索引尺寸到达 effective_cache_size 就会随时打开。默认值是 AUTO。
fastupdate (boolean)
:适用于 GIN 索引,这个设置控制快速更新技术的使用。它是一个布尔参数:ON 启用快速更新,OFF 禁用。默认是 ON。
gin_pending_list_limit (integer)
:适用于 GIN 索引,设置 fastupdate 被启用时可以使用的 GIN 索引的待处理列表的最大尺寸。 如果该列表增长到超过这个最大尺寸,会通过批量将其中的项移入索引的主 GIN 数据结构来清理列表。 如果指定值时没有单位,则以千字节为单位。默认值是四兆字节(4MB)。可以通过更改索引的存储参数来为个别 GIN 索引覆盖这个设置。
pages_per_range (integer)
:使用于 BRIN 索引,定义用于每一个BRIN索引项的块范围由多少个表块组成。默认是128。
autosummarize (boolean)
:定义是否只要在下一个页面上检测到插入就为前面的页面范围运行概要操作。
B-tree 索引
meta page和root page是一定有的,meta page需要一个页来存储,表示指向root page的page id。随着记录数的增加,一个root page可能存不下所有的heap item,就会有leaf page,甚至branch page,甚至多层的branch page。一共有几层branch和 leaf,可以用btree page元数据的level来表示。
--使用pageinspect扩展工具查看结构,数据准备
create extension pageinspect;
--主键索引使用的是btree索引,索引名字 tb_order_pkey
create table tb_order(id int primary key, order_no varchar(255));
insert into tb_order select generate_series(1,100), md5(random()::varchar);
--analyze 统计数据库表数据,统计结果存储到pg_statistic系统表中
--vacuum 用于清理死亡元组占用的存储空间
vacuum analyze tb_order;
1、查看meta块
select * from bt_metap('tb_order_pkey');--查看meta块
此时level为0即高度为1,root块为1。
2、根据root 的 page id =1查看 root page的stats
select * from bt_page_stats('tb_order_pkey', 1);--查看page的统计状态信息
图中参数说明如下:
ive_items:存活的索引行
dead_items:死亡的索引行
avg_item_size:平均索引行大小
page_size:块大小,详细看最后说明
free_size:块空余大小
btpo_prev:块左边
btpo_next:块右边
btpo:当前块层次,0代表处于0层
btpo_flags:当前块类型,3代表:他既是leaf又是root,即2+1
meta page
root page:表示为btpo flags=2
branch page :表示为btpo flags=0
leaf page:表示为btpo flags=1
3、查看指定索引块内容
select * from bt_page_items('tb_order_pkey', 1);--查看指定索引块内容
索引:ctid+索引列的值
4、通过索引ctid访问数据
select * from tb_order where ctid='(0,1)'; --通过索引ctid访问数据
包括 meta page, root page, leaf page
--使用pageinspect扩展工具查看结构,数据准备
create extension pageinspect;
--主键索引使用的是btree索引,索引名字 tb_order_pkey
create table tb_order2(id int primary key, order_no varchar(255));
insert into tb_order2 select generate_series(1,10000), md5(random()::varchar);
--analyze 统计数据库表数据,统计结果存储到pg_statistic系统表中
--vacuum 用于清理死亡元组占用的存储空间
vacuum analyze tb_order2;
1、查看meta块
Vacuum用于清理死亡元组占用的存储空间,默认删除或因更新过期(为了MVVC)的元组不会被物理删除。因此需要周期性的进行Vacuum,尤其是频繁更新的表。
Analyze命令用于统计数据库表数据,统计结果存储到pg_statistic系统表中。数据库进行基于成本的优化(CBO)时通过统计数据优化SQL语句的解释计划。
select * from bt_metap('tb_order2_pkey');
此时level为1即高度为2,root块id为3
2、根据root 的 page id =3查看 root page的stats**
select * from bt_page_stats('tb_order2_pkey', 3);--根据root 的 page id =3查看stats
live_items:存活的页块
dead_items:死亡的页块
avg_item_size:平均索引行大小
page_size:块大小,详细看最后说明
free_size:块空余大小
btpo_prev:块左边
btpo_next:块右边
btpo:当前块层次,1代表处于2层,表示下面还有一层
btpo_flags:当前块类型,3代表:他既是leaf又是root,即2+1
- meta page
- root page:表示为btpo flags=2
- branch page :表示为btpo flags=0
- leaf page:表示为btpo flags=1
3、查看指定索引块内容
select * from bt_page_items('tb_order2_pkey', 3);
总共28个页块,
4、查看每个的页块统计
select * from bt_page_stats('tb_order2_pkey', 1);
btpo flags=1表示为leaf page
btpo_prev:0
btpo_next:2
5、查看每个的页块内容
select * from bt_page_items('tb_order2_pkey', 1);
6、通过ctid查看数据
select * from tb_order2 where ctid='(3,1)';
实例
postgres=# create index idx_test_id on test(id);
CREATE INDEX
postgres=# \d test
Table "public.test"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | |
name | text | | |
Indexes:
"idx_test_id" btree (id)
postgres=# explain analyze select * from test where id between 100 and 200;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
Index Scan using idx_test_id on test (cost=0.43..10.49 rows=103 width=15) (actual time=0.006..0.058 rows=101 loops=1)
Index Cond: ((id >= 100) AND (id <= 200))
Planning Time: 0.408 ms
Execution Time: 0.072 ms
(4 rows)
哈希索引项只存储每个索引项的哈希代码,而不是实际的数据值
postgres=# create index idx_test_id on test using hash(id);
CREATE INDEX
postgres=# \d test
Table "public.test"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | |
name | text | | |
Indexes:
"idx_test_id" hash (id)
postgres=# explain analyze select * from test where id = 100;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------
Index Scan using idx_test_id on test (cost=0.00..8.02 rows=1 width=15) (actual time=0.014..0.015 rows=1 loops=1)
Index Cond: (id = 100)
Planning Time: 0.142 ms
Execution Time: 0.029 ms
(4 rows)
GiST的意思是通用的搜索树(Generalized Search Tree)。 它是一种平衡树结构的访问方法,在系统中作为一个基本模版,可以使用它实现任意索引模式。B-trees, R-trees和许多其它的索引模式都可以用GiST实现。
Gist索引适用于多维数据类型和集合数据类型,和Btree索引类似,同样适用于其他的数据类型。和Btree索引相比,Gist多字段索引在查询条件中包含索引字段的任何子集都会使用索引扫描,而Btree索引只有查询条件包含第一个索引字段才会使用索引扫描。
Gist索引创建耗时较长,占用空间也比较大。
postgres=# create index idx_t_gist_pos on t_gist using gist(pos);
CREATE INDEX
postgres=# \d t_gist
Table "public.t_gist"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | |
pos | point | | |
Indexes:
"idx_t_gist_pos" gist (pos)
postgres=# explain analyze select * from t_gist where circle '((100,100) 10)' @> pos;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on t_gist (cost=5.06..271.70 rows=100 width=20) (actual time=0.048..0.092 rows=29 loops=1)
Recheck Cond: ('<(100,100),10>'::circle @> pos)
Heap Blocks: exact=29
-> Bitmap Index Scan on idx_t_gist_pos (cost=0.00..5.03 rows=100 width=0) (actual time=0.027..0.027 rows=29 loops=1)
Index Cond: (pos <@ '<(100,100),10>'::circle)
Planning Time: 0.092 ms
Execution Time: 0.136 ms
(7 rows)
SP-GiST 中的GiST说明它跟 GiST 访问方法有一些相似性。两者的相似性在于,都是通用搜索树,都为构建各种访问方法提供了框架。SP代表空间分区(space partitioning),这里的空间,就是我们常说的空间,比如二维平面。我们将会看到,它可以表示任意搜索空间。
GIST索引不是单独一种索引类型,而是一种架构,可以在这种架构上实现很多不同的索引策略。因此,可以使用GIST索引的特定操作符类型高度依赖于索引策略(操作符类)。
GIST是广义搜索树generalized search tree的缩写。这是一个平衡搜索树。
用于解决一些B-tree,GIN难以解决的数据减少问题,例如,范围是否相交,是否包含,地理位置中的点面相交,或者按点搜索附近的点。
Postgresql也实现了以下几种类型的SP-Gist索引的操作类,我们可以在这些类型上直接建立SP-Gist索引。
postgres=# create index idx_t_spgist_p on t_spgist using spgist(p);
CREATE INDEX
postgres=# \d t_spgist
Table "public.t_spgist"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | |
p | point | | |
Indexes:
"idx_t_spgist_p" spgist (p)
postgres=# explain analyze select * from t_spgist where p >^ point '(2,7)';
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on t_spgist (cost=305.78..1067.78 rows=10000 width=20) (actual time=7.387..15.011 rows=99245 loops=1)
Recheck Cond: (p >^ '(2,7)'::point)
Heap Blocks: exact=637
-> Bitmap Index Scan on idx_t_spgist_p (cost=0.00..303.28 rows=10000 width=0) (actual time=7.302..7.303 rows=99245 loops=1)
Index Cond: (p >^ '(2,7)'::point)
Planning Time: 0.124 ms
Execution Time: 17.611 ms
(7 rows)
GIN 索引
GIN是Generalized lnverted Index的缩写。就是所谓的倒排索引,它处理的数据类型的值不是原来的,而是由元素构成。我们称之为复合类型。
存储被索引字段的VALUE或VALUE的元素,以及行号的list或tree。
col_val:(tid_list or tid_tree),col_val_elements:(tid_list or tid_tree)
比如('hank','15:3 21:4')中,表示hank在15:3和21:4这两个位置出现过
postgres=# create index idx_ts_doc_tsv on ts using gin(doc_tsv);
CREATE INDEX
postgres=# \d ts
Table "public.ts"
Column | Type | Collation | Nullable | Default
---------+----------+-----------+----------+---------
doc | text | | |
doc_tsv | tsvector | | |
Indexes:
"idx_ts_doc_tsv" gin (doc_tsv)
postgres=# set enable_seqscan = off;
SET
postgres=# explain analyze select * from ts where doc_tsv @@ to_tsquery('many & slitter');
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on ts (cost=12.25..16.51 rows=1 width=64) (actual time=0.046..0.047 rows=1 loops=1)
Recheck Cond: (doc_tsv @@ to_tsquery('many & slitter'::text))
Heap Blocks: exact=1
-> Bitmap Index Scan on idx_ts_doc_tsv (cost=0.00..12.25 rows=1 width=0) (actual time=0.042..0.042 rows=1 loops=1)
Index Cond: (doc_tsv @@ to_tsquery('many & slitter'::text))
Planning Time: 0.176 ms
Execution Time: 0.071 ms
(7 rows)
BRIN 索引是块级索引,有别于B-TREE等索引,BRIN记录并不是以行号为单位记录索引明细,而是记录每个数据块或者每段连续的数据块的统计信息。因此BRIN索引空间占用特别的小,对数据写入、更新、删除的影响也很小。
BRIN属于LOSSLY索引,当被索引列的值与物理存储相关性很强时,BRIN索引的效果非常的好。例如时序数据,在时间或序列字段创建BRIN索引,进行等值、范围查询时效果很好。与我们已经熟悉的索引不同,BRIN避免查找绝对不合适的行,而不是快速找到匹配的行。BRIN是一个不准确的索引:不包含表行的tid。
表被分割成ranges(好多个pages的大小):因此被称作block range index(BRIN)。在每个range中存储数据的摘要信息。作为规则,这里是最小值和最大值,但有时也并非如此。假设执行了一个查询,该查询包含某列的条件;如果所查找的值没有进入区间,则可以跳过整个range;但如果它们确实在,所有块中的所有行都必须被查看以从中选择匹配的行。在元数据页和摘要数据之间,是reverse range map页(revmap)。是一个指向相应索引行的指针(TIDs)数组。
在BRIN索引中,PostgreSQL会为每个8k大小的存储数据页面读取所选列的最大值和最小值,然后将该信息(页码以及列的最小值和最大值)存储到BRIN索引中。一般可以不把BRIN看作索引,而是看作顺序扫描的加速器。 如果我们把每个range都看作是一个虚拟分区,那么我们可以把BRIN看作分区的替代方案。BRIN适合单值类型,当被索引列存储相关性越接近1或-1时,数据存储越有序,块的边界越明显,BRIN索引的效果就越好。
postgres=# create index idx_test_id on test using brin(id);
CREATE INDEX
postgres=# \d test
Table "public.test"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | |
name | text | | |
Indexes:
"idx_test_id" brin (id)
postgres=# explain analyze select * from test where id between 100 and 200;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on test (cost=12.03..27651.36 rows=1 width=15) (actual time=0.079..4.006 rows=101 loops=1)
Recheck Cond: ((id >= 100) AND (id <= 200))
Rows Removed by Index Recheck: 23579
Heap Blocks: lossy=128
-> Bitmap Index Scan on idx_test_id (cost=0.00..12.03 rows=23585 width=0) (actual time=0.067..0.067 rows=1280 loops=1)
Index Cond: ((id >= 100) AND (id <= 200))
Planning Time: 0.240 ms
Execution Time: 4.045 ms
(8 rows)
其他用法
postgres=# create index idx_test_dl on test(id,name);
CREATE INDEX
postgres=# \d test
Table "public.test"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | |
name | text | | |
Indexes:
"idx_test_dl" btree (id, name)
postgres=# explain analyze select * from test where id = 100 and name = 'val:100';
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
Index Only Scan using idx_test_dl on test (cost=0.43..8.45 rows=1 width=15) (actual time=0.086..0.087 rows=1 loops=1)
Index Cond: ((id = 100) AND (name = 'val:100'::text))
Heap Fetches: 1
Planning Time: 0.224 ms
Execution Time: 0.104 ms
(5 rows)
postgres=# create unique index idx_test_wy on test(id);
CREATE INDEX
postgres=# \d test
Table "public.test"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | |
name | text | | |
Indexes:
"idx_test_wy" UNIQUE, btree (id)
postgres=# explain analyze select * from test where id = 100;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------
Index Scan using idx_test_wy on test (cost=0.43..8.45 rows=1 width=15) (actual time=0.099..0.101 rows=1 loops=1)
Index Cond: (id = 100)
Planning Time: 0.350 ms
Execution Time: 0.119 ms
(4 rows)
postgres=# create index idx_test_bds on test((id+1000));
CREATE INDEX
postgres=# \d test
Table "public.test"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | |
name | text | | |
Indexes:
"idx_test_bds" btree ((id + 1000))
postgres=# explain analyze select * from test where id+1000 > 10000;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on test (cost=31205.10..83233.11 rows=1666667 width=15) (actual time=578.175..1501.361 rows=4991000 loops=1)
Recheck Cond: ((id + 1000) > 10000)
Heap Blocks: exact=26980
-> Bitmap Index Scan on idx_test_bds (cost=0.00..30788.43 rows=1666667 width=0) (actual time=574.862..574.863 rows=4991000 loops=1)
Index Cond: ((id + 1000) > 10000)
Planning Time: 0.110 ms
Execution Time: 1633.012 ms
(7 rows)
postgres=# create index idx_test_bf on test(id) where (id between 1000 and 2000);
CREATE INDEX
postgres=# \d test
Table "public.test"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | |
name | text | | |
Indexes:
"idx_test_bf" btree (id) WHERE id >= 1000 AND id <= 2000
postgres=# explain analyze select * from test where id between 1500 and 2000;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
Index Scan using idx_test_bf on test (cost=0.28..27.29 rows=515 width=15) (actual time=0.035..0.090 rows=501 loops=1)
Index Cond: (id >= 1500)
Planning Time: 0.265 ms
Execution Time: 0.110 ms
(4 rows)
postgres=# explain analyze select * from test where id between 500 and 1000;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------
Seq Scan on test (cost=10000000000.00..10000102028.00 rows=272 width=15) (actual time=0.035..427.097 rows=501 loops=1)
Filter: ((id >= 500) AND (id <= 1000))
Rows Removed by Filter: 4999499
Planning Time: 0.107 ms
Execution Time: 427.120 ms
(5 rows)
postgres=# create index idx_test_fg on test(id) include(name);
CREATE INDEX
postgres=# \d test
Table "public.test"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | |
name | text | | |
Indexes:
"idx_test_fg" btree (id) INCLUDE (name)
postgres=# explain analyze select * from test where id = 100;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
Index Only Scan using idx_test_fg on test (cost=0.43..8.45 rows=1 width=15) (actual time=0.031..0.032 rows=1 loops=1)
Index Cond: (id = 100)
Heap Fetches: 1
Planning Time: 0.264 ms
Execution Time: 0.047 ms
(5 rows)
--查询
postgres=# select * from pg_indexes where tablename = 'test';
schemaname | tablename | indexname | tablespace | indexdef
------------+-----------+-------------+------------+-------------------------------------------------------------------------
public | test | idx_test_fg | | CREATE INDEX idx_test_fg ON public.test USING btree (id) INCLUDE (name)
(1 row)
--重建
postgres=# reindex index idx_test_fg;
REINDEX
--重命名
postgres=# alter index idx_test_fg rename to idx_test_id;
ALTER INDEX
--修改表空间
postgres=# alter index idx_test_id set tablespace tab1;
ALTER INDEX
--删除
postgres=# drop index idx_test_id;
DROP INDEX
参考文章:https://blog.csdn.net/songyundong1993/article/ details/128938714 https://blog.csdn.net/nusliang/article/ details/126734502 zhuanlan.zhihu.com/p/412628264