【INDEX】Postgresql索引介绍

索引访问方法介绍

支持的索引

mydb=# select * from pg_am;
 oid  | amname |      amhandler       | amtype 
------+--------+----------------------+--------
    2 | heap   | heap_tableam_handler | t
  403 | btree  | bthandler            | i
  405 | hash   | hashhandler          | i
  783 | gist   | gisthandler          | i
 2742 | gin    | ginhandler           | i
 4000 | spgist | spghandler           | i
 3580 | brin   | brinhandler          | i
(7 rows)

索引属性

  • 访问方法属性—pg_indexam_has_property
  • 特定索引的属性—pg_index_has_property
  • 索引中各列的属性—pg_index_column_has_property
访问方法属性中的四种类型
  • can_order: 访问方法能够在创建索引时指定值的排序顺序(仅适用于“ btree”)
  • can_unique:支持唯一约束和主键,适用于btree
  • can_multi_col:可以在多列创建索引
  • can_exclude:支持排它约束
 select a.amname, p.name, pg_indexam_has_property(a.oid,p.name)
from pg_am a,
     unnest(array['can_order','can_unique','can_multi_col','can_exclude']) p(name)
where a.amname = 'btree'
order by a.amname;
 amname |     name      | pg_indexam_has_property 
--------+---------------+-------------------------
 btree  | can_order     | t
 btree  | can_unique    | t
 btree  | can_multi_col | t
 btree  | can_exclude   | t
(4 rows)
特定索引属性
  • clusterable:可以根据索引对行进行重新排序(对其进行cluster操作)
  • index_scan:支持索引扫描。尽管此属性可能看起来很奇怪,但并非所有索引都能一次返回TID-有些返回结果一次全部返回,并且仅支持位图扫描
  • bitmap_scan: 支持位图扫描
  • backward_scan:可以按建立索引时指定的相反顺序返回结果

clusterable:可以根据索引对表的行重新排序,直接使用cluster命令,语法如下:

mydb=# \h cluster 
Command:     CLUSTER
Description: cluster a table according to an index
Syntax:
CLUSTER [VERBOSE] table_name [ USING index_name ]
CLUSTER [VERBOSE]
URL: https://www.postgresql.org/docs/13/sql-cluster.html
列属性
  • asc,desc,nulls_first,nulls_last,orderable:这些属性与值的排序有关,只有btree索引支持排序
  • distance_orderable:可以按照操作确定的排序顺序返回结果(到目前为止仅适用于GiST和RUM索引)
  • returnable:在不访问表的情况下使用索引的可能性,即仅索引扫描的支持
  • search_array:支持使用表达式搜索多个值。
  • search_nulls:通过IS NULL和IS NOT NULL条件进行搜索的可能性。
select p.name,
     pg_index_column_has_property('tbl_a_pkey'::regclass,1,p.name)
from unnest(array[
       'asc','desc','nulls_first','nulls_last','orderable','distance_orderable','returnable','search_array','search_nulls']) p(name);
        name        | pg_index_column_has_property 
--------------------+------------------------------
 asc                | t
 desc               | f
 nulls_first        | f
 nulls_last         | t
 orderable          | t
 distance_orderable | f
 returnable         | t
 search_array       | t
 search_nulls       | t
(9 rows)

操作符类和操作符族

除了需要知道各种操作方法相关的属性之外,我们还需要知道各种不同的操作方法分别支持哪些数据类型和哪些运算符。pg中提供了操作符类和操作符族的概念。

操作符类包含用于索引的最小运算符集(可能还有辅助函数),以操作某种数据类型。

某些操作符族中包括一个操作符类别。此外,如果一个通用的运算符族具有相同的语义,则它们可以包含多个运算符类。例如,integer_ops系列包括类型bigint,integer和smallint的int8_ops,int4_ops和int2_ops类,它们的大小不同但含义相同

可操作的类型
--如整型和日期为例
select opfname, opcname, opcintype::regtype
from pg_opclass opc, pg_opfamily opf
where opf.opfname = 'integer_ops'
and opc.opcfamily = opf.oid
and opf.opfmethod = (
   select oid from pg_am where amname = 'btree'
 );
   opfname   | opcname  | opcintype 
-------------+----------+-----------
 integer_ops | int2_ops | smallint
 integer_ops | int4_ops | integer
 integer_ops | int8_ops | bigint
(3 rows)
--datetime_ops系列包含用于操作日期(有时间和无时间)的运算符类:
select opfname, opcname, opcintype::regtype
from pg_opclass opc, pg_opfamily opf
where opf.opfname = 'datetime_ops'
and opc.opcfamily = opf.oid
and opf.opfmethod = (
   select oid from pg_am where amname = 'btree'
 );
   opfname    |     opcname     |          opcintype          
--------------+-----------------+-----------------------------
 datetime_ops | date_ops        | date
 datetime_ops | timestamptz_ops | timestamp with time zone
 datetime_ops | timestamp_ops   | timestamp without time zone
(3 rows)

运算符族还可以包括其他运算符,以比较不同类型的值。分组到族中使计划者可以将具有不同类型值的谓词使用索引。一个族还可以包含其他辅助功能。
在大多数情况下,我们不需要了解操作员的族和类。通常,我们只创建索引,默认情况下使用某个运算符类。

但是,我们可以显式指定运算符类。这是当需要显式规范时的一个简单示例:==在排序规则不同于C的数据库中,常规索引不支持LIKE操作:==

explain select * from t where b like 'A%';
--这种情况我们就可以通过使用操作符类 text_pattern_ops创建索引来克服此限制:
create index on t(b text_pattern_ops);

我们可以通过查询这些系统目录解决很多问题,例如:某种访问方法可以操纵哪些数据类型:

btree方法可以操作以下数据类型

select opcname, opcintype::regtype
from pg_opclass
where opcmethod = (select oid from pg_am where amname = 'btree')
order by opcintype::regtype::text;
       opcname       |          opcintype          
---------------------+-----------------------------
 array_ops           | anyarray
 enum_ops            | anyenum
 range_ops           | anyrange
 int8_ops            | bigint
 bit_ops             | bit
 varbit_ops          | bit varying
 bool_ops            | boolean
 bytea_ops           | bytea
 char_ops            | "char"
 bpchar_pattern_ops  | character
 bpchar_ops          | character
 date_ops            | date
 float8_ops          | double precision
 cidr_ops            | inet
 inet_ops            | inet
 int4_ops            | integer
 interval_ops        | interval
 jsonb_ops           | jsonb
 macaddr_ops         | macaddr
 macaddr8_ops        | macaddr8
 money_ops           | money
 name_ops            | name
 numeric_ops         | numeric
 oid_ops             | oid
 oidvector_ops       | oidvector
 pg_lsn_ops          | pg_lsn
 float4_ops          | real
 record_image_ops    | record
 record_ops          | record
 int2_ops            | smallint
 varchar_pattern_ops | text
 text_ops            | text
 varchar_ops         | text
 text_pattern_ops    | text
 tid_ops             | tid
 timestamp_ops       | timestamp without time zone
 timestamptz_ops     | timestamp with time zone
 time_ops            | time without time zone
 timetz_ops          | time with time zone
 tsquery_ops         | tsquery
 tsvector_ops        | tsvector
 uuid_ops            | uuid
 xid8_ops            | xid8
(43 rows)

运算符类包含哪些运算符

select amop.amopopr::regoperator
from pg_opclass opc, pg_opfamily opf, pg_am am, pg_amop amop
where opc.opcname = 'array_ops'
and opf.oid = opc.opcfamily
and am.oid = opf.opfmethod
and amop.amopfamily = opc.opcfamily
and am.amname = 'btree'
and amop.amoplefttype = opc.opcintype;
        amopopr        
-----------------------
 <(anyarray,anyarray)
 <=(anyarray,anyarray)
 =(anyarray,anyarray)
 >=(anyarray,anyarray)
 >(anyarray,anyarray)
(5 rows)

相关数据字典

数据字典 描述
pg_proc procedure
pg_operator operator
pg_amop access method’s operator
pg_opfamily operator family
pg_amproc access method’s support procedure
pg_type datatype
pg_opclass operator class
pg_am access method

索引支持的新数据类型

--创建一个新的组合类型
create type complex as (re float, im float);
--创建表
create table t1(x complex);
insert into t1 values ((0.0, 10.0)), ((1.0, 3.0)), ((1.0, 1.0));
insert into t1 values((1.0,2.0)),((2.0,2.0));

默认情况,对于组合类型排序是分开的,先比较第一个字段再比较第二个,也可以通过其他方式排序。

--创建相关函数
create function modulus(a complex) returns float as $$
select sqrt(a.re*a.re + a.im*a.im);
$$ immutable language sql;
--定义5种操作符函数:
create function complex_lt(a complex, b complex) returns boolean as $$
select modulus(a) < modulus(b);
$$ immutable language sql;
create function complex_le(a complex, b complex) returns boolean as $$
select modulus(a) <= modulus(b);
$$ immutable language sql;
create function complex_eq(a complex, b complex) returns boolean as $$
select modulus(a) = modulus(b);
$$ immutable language sql;
create function complex_ge(a complex, b complex) returns boolean as $$
select modulus(a) >= modulus(b);
$$ immutable language sql;
create function complex_gt(a complex, b complex) returns boolean as $$
select modulus(a) > modulus(b);
$$ immutable language sql;

创建对应的操作符

create operator #<#(leftarg=complex, rightarg=complex, procedure=complex_lt);
create operator #<=#(leftarg=complex, rightarg=complex, procedure=complex_le);
create operator #=#(leftarg=complex, rightarg=complex, procedure=complex_eq);
create operator #>=#(leftarg=complex, rightarg=complex, procedure=complex_ge);
create operator #>#(leftarg=complex, rightarg=complex, procedure=complex_gt);
--比较数字
select (1.0,1.0)::complex #<# (1.0,3.0)::complex;

除了整个5个操作符,还需要定义函数:小于返回-1;等于返回0;大于返回1。其他访问方法可能需要定义其他函数

create function complex_cmp(a complex, b complex) returns integer as $$
select case when modulus(a) < modulus(b) then -1
when modulus(a) > modulus(b) then 1
else 0
end; $$ language sql;

创建一个操作符类

create operator class complex_ops
default for type complex
using btree as
operator 1 #<#,
operator 2 #<=#,
operator 3 #=#,
operator 4 #>=#,
operator 5 #>#,
function 1 complex_cmp(complex,complex);
--查看排序结构
mydb=# select * from t1 order by x;
   x    
--------
 (1,1)
 (1,3)
 (0,10)
(3 rows)
--查看可以使用此查询获取支持的函数:
select amp.amprocnum,
amp.amproc,
amp.amproclefttype::regtype,
amp.amprocrighttype::regtype
from pg_opfamily opf,
pg_am am,
pg_amproc amp
where opf.opfname = 'complex_ops'
and opf.opfmethod = am.oid
and am.amname = 'btree'
and amp.amprocfamily = opf.oid;
 amprocnum |   amproc    | amproclefttype | amprocrighttype 
-----------+-------------+----------------+-----------------
         1 | complex_cmp | complex        | complex
(1 row)

管理操作符

--查看 操作符 和操作符类
select * from pg_operator;
select * from pg_opclass;
--查看操作符类型
select o.oid,o.oprname,
    (select t.typname from pg_type t 
     where o.oprleft=t.oid) as oprleft,
    (select t.typname  from pg_type t
     where o.oprright=t.oid)  as oprright
from pg_operator o  where  o.oprname like '#%#';
--删除操作符
drop operator #<# (complex,complex);
--删除操作类
DROP OPERATOR CLASS complex_ops using btree;
--删除函数
select * from pg_proc where proname like 'com%';
drop function function_name;

参考资料

BTREE索引

重要特征

  • B树是平衡的
  • B树是多分支的,即每个页面(通常为8 KB)包含许多(数百个)ctid。因此,B树的深度很小,对于非常大的表,实际上可以达到4–5的深度。
  • 索引中的数据按非递减顺序排序,(在页面之间和每个页面内部),并且同一级别的页面通过双向列表相互连接。因此,我们可以仅通过列表的一个方向或另一个方向获得有序数据集,而不必每次都返回到根。

无论哪种扫描类型(index scan、index only scan、bitmap scan),使用btree索引都会返回有序的数据。所以,如果在排序列上存在索引,优化器会首先考虑是索引扫描,否则就是先顺序扫描然后排序。

create index idx_t1 on t1(c1 desc);
--尤其组合索引 ,如果常用排序,可使用如下方法
create index idx_t1 on t1(c1 desc,c2 asc);

空值

pg索引可以存储空值,并支持按条件IS NULL和IS NOT NULL进行搜索。

explain select * from t1 where c1 is null;

在索引中null值存储在索引的一端,取决于创建索引时指定nulls first还是nulls last。

如果查询包括排序,则这一点很重要:如果SELECT命令在其ORDER BY子句中指定的NULL顺序与构建索引指定的顺序相同(NULLS FIRST或NULLS LAST),则可以使用索引,否则将无法使用索引。

在数据库中null是无法和其它值进行比较的

内部结构

PostgreSQL的btree索引的算法可以参考:src/backend/access/nbtree/README

其中PostgreSQL 的B-Tree索引页分为几种类别,我们可以通过btpo_flags的值去区分.

meta page    
root page         #  btpo_flags=2    
branch page       #  btpo_flags=0    
leaf page         #  btpo_flags=1        
leaf&root page    #  btpo_flags=3
--详情见:src/include/access/nbtree.h
/* Bits defined in btpo_flags */
#define BTP_LEAF                (1 << 0)        /* leaf page, i.e. not internal page */
#define BTP_ROOT                (1 << 1)        /* root page (has no parent) */
#define BTP_DELETED             (1 << 2)        /* page has been deleted from tree */
#define BTP_META                (1 << 3)        /* meta-page */
#define BTP_HALF_DEAD   (1 << 4)        /* empty, but still in tree */
#define BTP_SPLIT_END   (1 << 5)        /* rightmost page of split group */
#define BTP_HAS_GARBAGE (1 << 6)        /* page has LP_DEAD tuples */
#define BTP_INCOMPLETE_SPLIT (1 << 7)   /* right sibling's downlink is missing */

其中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插件研究所以你内部结构

  • bt_metap(relname text):返回record,bt_metap用来返回关于一个B树索引元页的信息。
  • bt_page_stats(relname text, blkno int):返回record,bt_page_stats返回有关
    B-树索引单一页面的总计信息
  • bt_page_items(relname text, blkno int):返回record,bt_page_items返回一个
    B-树索引页面上项的所有细节信息

参考文档

HASH索引

哈希表是根据键(Key)而直接访问在内存储存位置的数据结构。它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做哈希表

索引结构

当插入索引时,让我们计算键的哈希函数。PostgreSQL中的哈希函数总是返回integer类型,其范围为2的32次方≈40亿个值。存储桶的数量最初等于2,然后根据数据大小动态增加。bucket编号可以使用位算法从哈希码中计算出来。这是我们将放置ctid的bucket。

当搜索索引时,我们计算键的哈希函数并获取bucket编号。现在,仍然需要遍历bucket的内容,并仅返回具有适当哈希码的匹配ctid。由于存储的“hash code - ctid”对是有序的,因此可以高效地完成此操作。

哈希索引包含4种页:meta page, primary bucket page, overflow page, bitmap page

  • metapage(0号页),包含了HASH索引的控制信息,指导如何找到其他页面(每个bucket的primary page),以及当前存储概貌。其他索引的0号页基本都是这一个套路。
  • primary bucket page,hash index将存储划分为多个bucket(逻辑概念),每个bucket中包含若干page(每个bucket的page数量不需要一致),当插入数据时,根据计算得到的哈希,通过映射算法,映射到某个bucket,也就是说数据首先知道应该插入哪个bucket中,然后插入bucket中的primary
    page,如果primary page空间不足时,会扩展overflow page,数据写入overflow page。在page中,数据是有序存储(TREE),page内支持二分查找(binary search),而page与page之间是不保证顺序的,所以hash index不支持order by。
  • overflow page,是bucket里面的页,当primary page没有足够空间时,扩展的块称为overflow page
  • bimap page,记录primary , overflow page是否为空可以被重用

注意bucket, page都没有提供收缩功能,即无法从OS中收缩空间,但是提供了reuse(通过bitmap page跟踪),如果想要减小索引大小的唯一办法就是使用REINDEX或VACUUM FULL命令从头开始重建索引

总结

hash索引介绍:
src/backend/access/hash/README

使用场景:

  • 1、等值查询;
  • 2、btree索引不支持的大字段类型。

使用限制:

  • 1、不支持对null的处理;
  • 2、不能使用index only scan;
  • 3、不支持多列索引。

参考文档

请使用浏览器的分享功能分享到微信等