PostgreSQL之堆表存储(Heap Table)

Posted post_yuan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PostgreSQL之堆表存储(Heap Table)相关的知识,希望对你有一定的参考价值。

我们知道PostgreSQL中的表存储结构属于堆表(Heap Table),这是与mysql不同的(MYSQL中聚集索引表Clustering Index)。那么堆表和聚集索引表到底有什么不同,我们就一起来学习一下。

首先我们了解一下什么是堆表,以下关于堆表的描述来自SQL Server关于堆表的说明

什么是堆表?

SQL Server关于堆表的描述如下,

  • 没有聚集索引的表
  • 堆表在sys.partitions里有1条index_id = 0 的记录
  • 数据存储没有任何的顺序,插入数据也没顺序
  • 由于数据没有任何顺序,查询数据会非常慢
  • 数据页之间没有相互链接
  • 从数据页读取数据,需要从IAM(Index Allocation Map)页里找页号
  • 在sys.system_internals_allocation_units系统视图里,first_iam_page列,指向IAM页链中的第一个IAM页,它用来管理堆表的空间分配
  • 因为没有聚集索引,碎片不能通过重建索引(rebuilding the index)处理
  • SQL Server使用IAM页在堆结构里导航。分配给堆的页没有任何的顺序,且不相互链接。数据页之间唯一的逻辑关联是存在IAM页里的信息。

抛开SQL Server本身的一些用法以外,我们可以总结出来堆表具有的主要特点是:数据存储没有任何顺序,需要借助索引页来提升查询数据性能。下图也是一个示例说明需要通过索引页(IAM)来寻找对应的堆表数据页。

PostgreSQL中的堆表结构

OK,上述了解完什么是堆表之后,我们再来学习一下PG中的堆表。在PG数据库中,一个数据文件是由多个固定长度的页(或者称为块)组成,每个页的默认大小为8KB。在每个文件内部,这些页都有一个从0开始的页编号(block number)。当文件被填充时,PG通过增加一个新的空页到文件末尾来增加文件大小。


上图示例描述一个数据文件的内部结构,从图中可以看出,一个表文件内部由从0至N编号的页面构成,每个页为8KB。右边则是每个页面的内部构成。每个页面包括以下内容:

  • heap tuples -即heap表中的数据记录。这些记录是从页面的底端向上存放的。每个tuple内部的结构我们单独再描述。
  • line pointers -每个line pointer由4个字节表示,是一个指向具体某一条heap tuple的指针,也被称为item pointer。Line pointers组成一个简单的数组,负责索引到对应记录。每个line pointer在block内是从1开始进行编号,也被称为偏移号(offset number)。当一个新的记录被添加到页面时,就会增加一个新的line pointer与之对应。
  • header data -即页面的头信息,放在页面的开始部分。由24字节表示,其中包含一些页面描述的通用信息。主要的内容有:
    (1)pd_lsn -保存页面上次修改生成的XLOG的LSN信息。8字节的无符号整数,与WAL机制有关。
    (2)pd_checksum -保存页面的checksum值(9.3及之后版本)。
    (3)pd_lower,pg_upper -pd_lower指向line pointers的末尾位置,pd_upper指向最新的heap tuple位置。
    (4)pd_special -用于索引。如果此页面是表页面,指向页面的末尾。如果此页面是索引页面,指向特殊空间的开始(此空间根据索引的类型是BTree、GiST还是GIN保存特定的数据)。

在line pointers结尾到最新tuple开始之间的空间代表空闲空间或空洞。
为了确定表中某一个tuple的位置,PG内部使用tuple identifier(TID) 。TID由一组值构成:包括这个tuple页面的页编号block number,以及指向这个tuple的具体的偏移号offset number。即TID=block number + offset number

下面我们具体看一个如何通过全表扫描/索引扫描来定位到需要的表记录。

读取Heap Tuples

读取Heap Tuples通常有两种方式,按sequential scan和按B-Tree Index Scan,

  • Sequential Scan - 通过扫描每个页面中所有line pointers来达到顺序读取所有页面中的tuples记录。
  • B-Tree index Scan - Index文件包括Index tuples,每个Index tuple由一个index key和一个指向对应heap tuple的TID构成。根据key找到对应的Index tuple时,利用对应的TID来读取对应的heap tuple。比如在下图中,所获取的index tuple对应的TID为(block=7,offset=2),表示对应的heap tuple是表对应的第7个页面中的第2个tuple记录。

写入Heap Tuples

根据上述内容,我们知道写入heap tple是从一个页面的底部往上写,每写入一条heap tuple会同时写一个对应的指针即line pointer,同时也会更新其他相关的页面Head信息。具体的说,假如开始一个页面中只包含一条记录Tuple 1,当写入Tuple 2时,这个Tuple会被放置在Tuple 1前面。第2个line pointer被写入放置在第一个line pointer后面并指向Tuple 2。pd_lower会被更新指向第2个line pointer末尾,pd_upper会被更新指向Tuple 2的开始位置。其余的页面header数据如pd_lsn,pg_checksum,pg_flag等会相当的被写入对应的新值。

数据结构之堆

 1 // 优先队列 Heap
 2 // 前缀: Heap
 3 
 4 #ifndef _HEAP_H
 5 #define _HEAP_H     
 6 
 7 #define HEAP_DEFAULT_CAPACITY 10
 8 typedef int Rank;       
 9 typedef int HeapT;
10 typedef struct {
11     int Capacity;   // 容量
12     int Size;       // 元素个数
13     HeapT *Elems;
14 } HeapNode;
15 typedef HeapNode *Heap;
16 
17 Heap Heap_Create( int Capa );
18 void Heap_Destroy( Heap *Pheap );
19 void Heap_Insert( Heap H, HeapT E );
20 HeapT Heap_DeleteMin( Heap H );
21 int Heap_IsFull( Heap H );
22 int Heap_IsEmpty( Heap H );
23 
24 // test
25 void Heap_TestPrint(Heap H);
26 
27 #endif /* _HEAP_H */
技术分享图片
  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include "heap.h"
  4 
  5 Heap Heap_Create(int Capa)
  6 {
  7     if(Capa < HEAP_DEFAULT_CAPACITY) {
  8         Capa = HEAP_DEFAULT_CAPACITY;
  9     }
 10     Heap heap = (Heap)malloc(sizeof(HeapNode));
 11     if(heap == NULL) {
 12         printf("Out of space!!\n");
 13         return NULL;
 14     }
 15     heap->Capacity = Capa + 1;  // 数组0下标处不放数据
 16     heap->Size = 0;
 17     heap->Elems = (HeapT *)malloc(sizeof(HeapT) * (heap->Capacity)); 
 18     if(heap->Elems == NULL) {
 19         free(heap);
 20         printf("Out of space!!\n");
 21         return NULL;
 22     }
 23     return heap;
 24 }
 25 
 26 void Heap_Destroy(Heap *Pheap)
 27 {
 28     if(Pheap != NULL && *Pheap != NULL) {
 29         Heap heap = *Pheap;
 30         free(heap->Elems);
 31         free(heap);
 32         *Pheap = NULL;
 33     }
 34 }
 35 
 36 void Heap_Insert(Heap H, HeapT E)
 37 {
 38     if(H == NULL) return ;
 39     if( Heap_IsFull(H) ) {
 40         printf("Heap is Full!!\n");
 41         return ;
 42     }
 43     
 44     Rank cell = ++(H->Size);
 45     while(cell > 1) {
 46         Rank parent = cell / 2;
 47         if( H->Elems[parent] < E ) {  // 找到插入位置
 48             break;          
 49         } else {  // 未找到位置,上滤
 50             H->Elems[cell] = H->Elems[parent];  
 51             cell = parent;
 52         }
 53     }
 54     // cell位置,要么是根节点(1),要么是插入值小于cell父亲的值
 55     H->Elems[cell] = E;
 56 }
 57 
 58 HeapT Heap_DeleteMin(Heap H)
 59 {
 60     if(H == NULL) return ;
 61     if( Heap_IsEmpty(H) ) {
 62         printf("Heap is Empty!!\n");
 63         return ;
 64     }
 65     
 66     HeapT result = H->Elems[1];
 67     HeapT LastElem = H->Elems[H->Size];
 68     H->Size--;
 69     Rank cell = 1;  // LastElem位置
 70     while(2 * cell <= H->Size) { // 当cell左孩子(2 * cell)存在,即cell不是叶子节点
 71         Rank lchild = 2 * cell, rchild = 2 * cell + 1;
 72         Rank minchild = lchild;
 73         if( rchild <= H->Size && H->Elems[rchild] < H->Elems[lchild] ) { // 如果右孩子存在
 74             minchild = rchild;
 75         }
 76         
 77         // 若cell左右孩子中较小的都大于等于LastElem,则cell为LastElem位置
 78         if( H->Elems[minchild] >= LastElem ) {
 79             break;
 80         } else {  // 下滤
 81             H->Elems[cell] = H->Elems[minchild];
 82             cell = minchild;
 83         }
 84     }
 85     // cell的位置,要么是叶子节点,要么是cell左右孩子值均小于LastElem
 86     H->Elems[cell] = LastElem;
 87     return result;
 88 }
 89 
 90 int Heap_IsEmpty(Heap H)
 91 {
 92     return H != NULL && H->Size == 0 ? 1 : 0;
 93 }
 94 
 95 int Heap_IsFull(Heap H)
 96 {
 97     return H != NULL && H->Size + 1 == H->Capacity ? 1 : 0;
 98 }
 99 
100 void Heap_TestPrint(Heap H)
101 {
102     if(H == NULL) {
103         printf("Heap is NULL.\n");
104         return ;
105     }
106     for(int i = 1; i < H->Size + 1; i++) {
107         printf("%d ", H->Elems[i]);
108     }
109     printf("\n");
110     printf("Size: %d, Capa: %d\n", H->Size, H->Capacity);
111 }
heap.c

 

以上是关于PostgreSQL之堆表存储(Heap Table)的主要内容,如果未能解决你的问题,请参考以下文章

数据结构之堆

STL之堆和优先队列

PostgreSQL存储引擎之heap tuple结构

重温基础算法内部排序之堆排序法

重温基础算法内部排序之堆排序法

算法三之堆排序