PostgreSQL中与索引相关的系统表
本文主要介绍下图中这些系统表的关系和作用,并且以Btree索引、int类型(int2、int4、int8)为例进行一些直观的展示。
(图片摘自Egor Rogov的英文博客)
上图这些系统表的关系并不满足第三范式,即每张表中可能冗余了其它表上的某些列,比如pg_am.oid、pg_opfamily.oid、左右操作符的类型等。这可以避免对多张表进行连接,加速查找速度。
pg_type中保存了PostgreSQL支持的所有数据类型,pg_proc中包含了一系列函数的定义。它们与本文的关系不大。
pg_am
PostgreSQL支持多种索引类型,每种索引类型都有一个oid,并且在pg_am中有一条记录与它对应,如下图所示,btree的oid是403。AM的全称是Access Method(访问方法),虽然heap不是索引类型,它也出现其中。
pg_opclass和pg_opfamily
对每种索引所支持的每种数据类型,PostgreSQL都会定义一个opclass。它的主要作用是指定这个opclass所对应的opfamily。opfamily就是把一些相关的opclass组织在一起,同一个opfamily内opclass对应的数据类型,可以直接相互比较。用什么操作符对这些不同的数据类型进行比较呢?在pg_amop中查找。
对Btree来说,共有44种opclass。其中,int2_ops、int4_ops、int8_ops三个opclass属于同一个opfamily,oid为1976。
创建索引时,PG会找出每个索引列对应的opclass,并且把它们存在pg_index系统表的indclass列中。查找opclass的函数为ResolveOpClass。请看下图,b列的类型是int(int4),对b列创建索引时,PG使用索引类型的oid 403(btree)和数据类型23(int4)查出了对应的opclass 1978。最终1978被保存到pg_index.indclass中。
为什么上图中的查询条件是where indexrelid=16398?简要来说,索引也是一种关系,索引的定义会被保存到pg_class中,从pg_class可以查到索引对应的oid。再结合pg_index的定义,可知查询条件为where indexrelid=16398。
pg_amproc
pg_amproc中保存的是每种索引类型的support function。对btree来说,主要与排序相关。2018年2月之前,btree只有两种support function,都与比较或排序有关。amprocnum为2的函数在创建索引时被用来对索引元组进行排序。amprocnum为1的函数在查找索引时,可以用来比较索引的值。
请看示例:
create table t(a int, b int);
insert into t values(1,1);
create index on t(a);
创建索引时,使用get_opfamily_proc函数查到排序函数的oid为3130(btint4sortsupport)。并且在排序阶段,调用btint4sortsupport。
pg_amop
transformXX
查询中的条件会在语义解析阶段被转化成PG的内部表示,对应的操作符会使用pg_operator中一行数据的oid表示。这里的转换与最终是否使用索引没有任何关系。比如,a列是int2的类型,b列是int4类型,c列是int8类型。对于a=1、b=1、c=1三个条件来说,三个等号对应操作符的oid分别是532、96、416。这三个操作符都是判断两个操作数是否相等,只是左右两侧操作数的类型不同。常数1的类型被认为是int4。
oid为532的等号左侧操作数为int2类型(oid为21),右侧操作数为int4类型(oid为23),返回类型为bool(oid为16)。这个操作符使用oid为158的函数(int24eq)进行比较。
oid为96的等号左侧操作数为int4类型(oid为23),右侧操作数为int4类型(oid为23),返回类型为bool(oid为16)。这个操作符使用oid为65的函数(int4eq)进行比较。
oid为416的等号左侧操作数为int8类型(oid为20),右侧操作数为int4类型(oid为23),返回类型为bool(oid为16)。这个操作符使用oid为474的函数(int84eq)进行比较。
再谈opclass和opfamily
pg_index的indclass列中保存了每个索引列对应opclass的oid,每次打开索引时,PG都会通过opclass的oid查到它们对应opfamily的oid。这个步骤由LookupOpclassInfo完成,读者可自行浏览代码、用gdb跟踪,由于PG存在多种缓存机制,并不是每次转换都需要查询pg_amop系统表。这里查到的opfamily的oid,最终会被传递到match_opclause_to_indexcol函数参数index的opfamily数组中。
使用索引的前提
match_opclause_to_indexcol函数负责将一个条件与一个索引列进行匹配。本文不详细介绍这个函数,仅仅说明这个函数如何判断一个操作符能否被索引使用。很简单,它使用前两步得到的操作符的oid和opfamily的oid作为条件,查询pg_amop系统表。如果查询结果为空,则这个条件一定不能被使用;如果非空,则可能可以被使用。使用(532,1976),(96,1976),(416,1976)查询时,结果都非空。所以,如果在int2、int4、int8类型的列上建有索引,并且条件为 xx = 1,则这个条件可以用于索引扫描。注意PG认为常数1是int类型。