PostgreSQL存储引擎相关介绍

  • 介绍PostgreSQL中存储引擎相关知识介绍
在PostgreSQL中存在Tuple和Index Tuple两种数据存储格式,其中Tuple为我们组织普通数据的方式,而Index Tuple则是我们组织索引数据所采用的方式。

   存储的过程:

(1) 读取元组时所需要的cache,

存在着两种不同的cache一种存储系统表元组,另外一种存储表的基本信息和元组的模式结构(即元组的column信息)

(2) 元组的数据信息,需要用到缓冲区,因此我们需要对于缓冲区的管理模式需要有所了解。

(3) 当缓冲区没有数据时,需要从外存上进行数据读取,smgr模块需要认识。

(4) VFD

(5) FSM

下面是Tuple的组织形式(图片均来着网上,如果有侵权告知,删除)

tuple_arc
而Tuple Head的组织形式如下图所示:
tuple_head_arc
HeapTupleHeaderData来描述,元组的头信息。 到元组形成的过程中,在写向磁盘之前,我们对于元组的可见性不做要求,而在该数据结构中的第一个域:
union
{
HeapTupleFields t_heap ;
DatumTupleFileds t_datum ;
}

其中的t_heap 描述了该元组的可见性,其由 xmin, xmax , commandid 来描述,因为postgreSQL使用mvcc来描述了元组的可见性。 而mvcc则是由xmin, xmax, commandid来,进行heap visible check.

而 HeapTupleHeaderData中的 ItemPointerData t_ctid用来描述了该元组在磁盘中的数据位置(block中的位置,已经在该block中的偏移量)

由于postgresql使用mvcc方式来进行并发控制, 因此对于元组的更新操作,系统并不会真正的对该元组进行操作,而是新插入一个新元组, 此时便会使用该变量 t_ctid.

new_old_tuples

数据在存储的时候,会将数据的类型也保存。 数据在使用的过程中由于其对于其可见性和数据的类型(长度)等要求不同,故而可以将上述的两类信息进行分开处理,union。

Datums of composite types (row types) share the same general structure as on-disk tuples, so that the same routines can be used to build and examine them.

当数据形式的时候,将填充t_datum填充(In-memory时候),但当数据插入到表中时候,则需要判断该数据的可见性。 Xmin和Xmax总是实际存在,而CMax和Xvac使用同一存储空间。

而t_ctid则永远指向新的数据位置。

表操作和元组操作:
表的操作并不是真正的打开表的物理文件,而是将描述该表的关系的relationdata返回。当打开表的时候其会首先咋relcache中进行插座在,当在cache中查询到相应的关系时候则其会直接从Relcache中返回其所对于的relcache并将其refcount加一,并根据其类型进行枷锁操作,并将该relationdata返回。 同样,更加表名打开表的操作相似,只不过是首先进行由表名到oid的转换,首先将表名转换为oid,然后在根据oid使用relation_open函数进行表的打开工作。

获取元组信息:
heap_beginscan函数进行元组的索引查找。而在该函数内部则使用heap_beginscan_internal来进行索引扫描。 一个完整的Tuple由HeapTupleData和一个TupleDesc来描述。 而HeapTupleData中又由一个HeapTupleHeaderData的结构来描述HeapTuple。

heap_arc_black

其中 HeapTupleDesc描述了,该relation中元组的属性关系,即该表的列的属性等信息的描述信息。 因为数据在磁盘上是属于无结构,因此如果需要让该数据具有相应的类型信息的话,则需要一个描述数据,来描述该数据的类型,例如:第一列是何种类型信息,第二列是何种信息。在插入元组之前,为了将所插入的数据格式化为所需要的信息,首先需要将这些数据按照tuple的格式进行格式化处理。

因此,调用函数heap_formtuple来将所给定的数据构造成相应的tuple。 对于空属性则使用字符’n’ 来描述,而对于非空属性则使用’ ‘一个空格符合来描述。

对于函数heap_formtuple函数的形式参数isnull数组描述了该条数据中那些属性为空,因为对于一张表,其中的某些属性可以为空,而在进行数据插入时候,对于属性可以为空的列,我们的无法使用某个具体的数据来描述这个null值。 因此我们使用了isnull数组来描述属性的是否为空的特性。 而函数heap_compute_data_size用来计算该元组所需要的内存大小,以该大小在内存中分配空间,而后进行tupleheader数据的填充,在完成这些元组的control info的时候,我们就可以使用heap_fill_tuple来进行真实数据的插入填充。在完成数据的填充后,我们就可以使用heap_insert来进行数据的插入。同理,当我删除元组时候,我们并不是真正的进行数据删除而是,在此基础上进行的标识,即mvcc方式,这些标识删除的数据并未真正的在磁盘被删除,而是在进行vaccume时候进行真正数据删除。

而该数据的删除由heap_delete来完成。

heap_delete 删除的流程:首先要根据tid在相关的缓冲区中进行加锁操作,由于其需要在对该缓冲区进行修改操作,因此需要对该缓冲区进行加排他锁(Exclusive Lock)。 当完成对于缓冲区的锁定后,需要判断所需要删除元组的可见性,并根据可见性决定后续的操作步骤。例如:当该元组被当前事务修改时或者已修改,则需要将该元组的ctid指向新的元组地址.

关于索引:(src\backend\access\nbtree文件夹中)

使用ambuild来创建一个新的索引,在使用ambuild之前,索引文件的物理文件以及创建,但是为空。需要通过ambuild来通过IndexBuildHeapScan来在基表中扫描出满足索引条件的tuple,然后再该索引文件上建立索引。

ambuild函数:创建一个索引。–call–> IndexBuildHeapScan ()

aminsert函数 : 向现有的索引中插入一个新的索引tuple,并根据option来判断该索引是否为唯一索引,当该索引要求为唯一索引,则在插入之前会检查该元组是否已经存在该索引中,当该tuple存在该索引中,则返回一个错误信息。

ambulkdelete:从索引中批量删除元组。该函数实际会调用 amvacuumcleanup来执行实际删除。其实质是调用vaccum来对无效元组进行清除操作。 其会在vacuum之后调用,进行一个额外处理:FSM文件清理。空闲文件清除工作。

amcostestimate:进行索引扫描代价估算。通过调用优化器的标准过程完成。

ambeginscan:开始一个扫描,通过构造一个indexscandescData,然后使用该描述符进行扫描。通过调用ambeginscan创建IndexScanDescDat。而该scan函数的主要作用是调用RelationGetIndexScan函数来获得IndexScanDescData。

amgettuple:来获取满足条件的下一个tuple,并通过使用方向标准来移动。同理,amgetbitmap则返回一个bitmap来表示。

amrescan:重启一个扫描。 RelationGetIndexScan。同样也可以进行索引扫描初始化工作。

amendscan 结束索引扫描。

ammarkpos标记当前的扫描函数。 当前的扫描符的opaque字段中。

索引的元组信息:即索引的信息,IndexTupleData 结构来描述了相应的索引元组数据。 在基表元组数据到索引元组数据的过程中,由BTBuildState结构用于保存索引元组。由于HOT链的存在,在进行扫描表的元组时,所获得元组可能是dead tuple,即通过mvcc方式所产生的旧的元组。对于这样的索引元组放在spool2中,因此对于对于唯一索引,不需要检查该元组是否唯一,可直接插入到索引中。(为何要将dead tuple也插入索引数据中?在mvcc机制下,当数据发生了更新后,会产生一个old数据,而该old数据,在执行vacuum时候将会被清除。那为何还要在索引的创建过程中将这些dead tuple建立在索引上?) [dead tuple相对于其它事务而言,本事务内可能是dead tuple但其它事务可能不是dead tuple,因此我们要将这些dead tuple也创建索引。当对于所有的事务都处于dead tuple时候,则可以使用。当该dead tuple对于所有的事务都是dead tuple时候,则可以使用vacuum来进行清除操作。请参考:两个函数中对于dead tuple的判读,HeapTupleSatisfiesVacuum, GetOldestXmin]

每个B-Tree所以都有一个metaPage,该改meta-page用来描述该b-tree的版本号,根节点的位置(所在的块号)以及根节点所以在树的层次信息,meta-page始终在b-tree 的第一页中,但是该meta-page的信息是在索引的创建过程中分步骤进行信息更新的,首先会预留位置,当索引创建完成后,将关于该索引所创建的信息进行赋值。 fastroot接的,当从root节点进行开始查找时候,当从root节点到叶子点之间存在着唯一的路径时候,由于其只有唯一的路径,为了加快到叶子节点的查询速度,可以将root到第一个存在分支路径的路径的中间路径忽略,在查询过程中,只要将从该分支节点开始查找。

btree_arc
btbuild函数:用来创建索引,btbuild首先使用indexBuildHeapScan对表进行扫描,将每个元组封装成索引元组,在封装的过程中还需要判断元组是否为dead tuple,如果非dead tuple则将该索引tuple放在spool中,否则将dead tuple所对于的索引元组放在spool2中。

在完成对基表数据的扫描后,即上述过程。创建一个BTWriteState结构对象,然后将BTBuildState结构中的spool和spool2中的元组进行排序,这样做是为了在索引构建过程中,不需在索引树上来回遍历,在排序完成后,会使用_bt_load函数顺序读取spool中的元组,然后对每个元组使用_bt_buildadd来添加索引元组:将这些数据插入到叶子节点上。当spool和spool2都不为空时候,使用merge方式来将这些spool和spool2数据进行创建索引。

当完成索引添加后,使用_bt_uppershutdown填充元数据。

通过,_bt_leafbuild函数来对已经排好序的元组进行索引构建。

btbeginscan函数,开始扫描。该函数按需要的扫描的索引相关信息生成一个IndexScanDesc结构。而该结构中保存了扫描键值,查询到的位置等信息。