做一个好的数据库管理者,没有理解数据库的运行,是做不到的,就花了些课余时间分析了一下pasql 的数据存储相关的部分源代码,当然,由于是在课余的时间(现在临近考试了嘛:p),所以感觉还是有很多不清楚的地方。呵呵,肯定有很多不足之处,希望你们看到指正:)
Storage/page/smgr/file
1 代码总体分析
1.1 简介
在pgsql中,storage支持以统一的方式进行数据存取,进行磁盘文件的读写和管理以及实现数据库的故障恢复,是整个数据库的数据驱动模块。本报告所分析的3个模块page/smgr/file是文件管理、页管理器、存储/磁盘管理器部分,他们紧密联系构成磁盘数据页定义,缓冲数据页定义和磁盘文件管理的3层的机构关系,如图1-1:
Storage/SMGR |
Storage/FILE |
Storage/page |
图1-1
1.2代码分布
以下表列出不本报告分析的源代码文件以及功能的基本描述。
2 模块分析
2.1 Storage/page
<?xml:namespace prefix = st1 />2.1.1 page数据结构
本模块是对磁盘文件页的定义,包含对了插入、删除数据,以及作为索引块的插入、删除。首先给出一个物理页文件的数据结构:
项 | 描述 |
PageHeaderData | 20 字节长。包含关于页面的一般信息,包括自由空间指针。 |
ItemPointerData | 一个记录(偏移量,长度)配对对的数组,指向实际项。每个项 4 字节。 |
Free space(自由空间) | 未分配的空间。新项指针从这个区域的开头开始分配,新项从结尾开始分配。 |
Items(项) | 实际的项本身。 |
Special Space(特殊空间) | 索引访问模式相关的数据。不同的索引访问方式存放不同的数据。在普通表中为空 |
表2-1
每个页面的头20个字节组成页头(PageHeaderData)。它的格式在 表2-2里详细介绍。 头两个字节跟踪与此页面相关的最近的 WAL 项。 然后跟着三个 2 字节的整数字段 (pd_lower,pd_upper, 和 pd_special)。 这些字段分别包含页面开始位置与未分配空间开头的字节偏移,与未分配空间结尾的字节偏移, 以及与特殊空间开头的字节偏移。 页面头的最后 2 字节,pd_pagesize_version, 存储页面尺寸和版本指示器。从 PostgreSQL 8.0 开始, 版本号是 2;PostgreSQL 7.3 和 7.4 使用版本号 1; 以前的版本使用版本号 0。 (基本页面布局和头格式在这些版本里都没有改变,但是堆的行头部布局有所变化。) 页面大小主要用于交叉检查;目前在一次安装里,还没有支持多于一种页面大小的东西。
字段 | 类型 | 长度 | 描述 |
pd_lsn | XLogRecPtr | 8 字节 | LSN: 最后修改这个页面的 xlog 记录最后一个字节后面第一个字节 |
pd_tli | TimeLineID | 4 字节 | 最后修改的 TLI |
pd_lower | LocationIndex | 2 字节 | 到自由空间开头的偏移量 |
pd_upper | LocationIndex | 2 字节 | 到自由空间结尾的偏移量 |
pd_special | LocationIndex | 2 字节 | 到特殊空间开头的偏移量 |
pd_pagesize_version | uint16 | 2 字节 | 页面大小和布局版本号信息 |
表2-2
在页头后面是项标识符(ItemPointerData),每个需要四个字节。 一个项标识符包含一个到项开头的字节偏移量,它自己以字节计的长度, 以及一套属性位,这些属性位影响它的解释。 新的项标识符根据需要从未分配空间的开头分配。 项标识符的数目可以通过查看 pd_lower 来判断,在分配新标识符的时候会递增。 因为一个项标识符在其释放前绝对不会移动,所以它的索引可以用于长时间地引用一个项, 即使该项本身因为压缩自由空间在页面内部进行了移动也如此。实际上,PostgreSQL 创建的每个指向项的指针(ItemPointer,也叫做 CTID)都由一个页号和一个项标识符的索引组成。
项本身存储在从未分配空间末尾开始从后向前分配的空间里。 它们的实际结构因表包含的内容不同而不同。表和序列都使用一种叫做 HeapTupleHeaderData 的结构,在下面描述。
最后一段是"特殊段",它可以包含任何访问方法想存放的东西。 比如,b-tree 索引存储指向页面的左右同宗的链接,以及其他一些和索引结构相关的数据。 普通表并不使用这个段(通过设置 pd_special 等于页面大小来表示)。
2.1.2page工作方式
l 声明一个新的数据页
就是将上诉的页面文件头进行初始化
1.XLOG、LOWER、UPPER初始化为数据开始。
2.TLI从系统日志继承
3. SPECIAL由调用函数初始化(含特殊处理)
4. PAGESIZE _VERSION,Size是可设定的,初始为8192。显然设定值必须大于specialSize + SizeOfPageHeaderData
l 对归并算法支持
提供了进行归并算法的支持。
特殊页的申请、数据比较、特殊页的写回,其中数据比较是对一个数据块自身提供快速排序算法。
l 数据插入、删除
插入数据——直接从LOWER往后进行数据加入。
在LOWER已经和UPPER相等的情况下,从LOWER到UPPER进行循环,找到空闲的。(必然正确!由PageGetFreeSpace保证)
数据删除——将需删除的打上标记
l 数据碎片的整理
PageRepairFragmentation()
上面我们在数据块的插入删除中看到,一个数据的删除并没有真正从数据页中删除,在这个方法中将删除的line pointers放进数组unused[],这里利用了考虑可能事务回滚的可能性。
l 索引的删除
与数据页的删除不同,在删除后,页文件头相应删除,同时将所有的文件进行移动,以达到其的紧致特性
即将其数据进行移动如图2-1。
图2-1
可以看出,在pgsql中对于索引是对外封闭的,这是系统维护的数据。
2.2Storage/FILE
2.2.1file主要数据结构
缓冲页文件结构 | 意义 |
File | 文件标示 |
isInterXac | 锁 |
Dirty | 是否脏块 |
curOffset | 文件的第几块 |
Pos | 作为读入和写出时的计数器 |
Nbytes | 数据总数 |
buffer[BLCKSZ] | 数据区 |
表2-3
可以看出,当数据从磁盘进入主存时,DBMS又在数据块前面加上了一个外包,这个外包里面就包含了作为缓冲数据所需要的信息。它指示了缓冲页属于哪一个文件,加锁信息,是否赃块等一系列信息。最后,我么看到是一个以物理的块文件大小的缓冲区,这也说明在pgsql中一个物理块在内存中就是一个缓冲数据页。
缓冲文件中所支持的文件最高块数(0x40000000 / BLCKSZ)
可以看出,系统所支持的文件大小最大1G,超过容量的关系必须分割成多个文件进行管理。
2.2.2file工作方式
在定义了一个缓冲数据块的同时提供了很多对其的管理函数
l makeBufFile(File firstfile);
l extendBufFile(BufFile *file);
l BufFileLoadBuffer(BufFile *file);
l BufFileDumpBuffer(BufFile *file);
l BufFileFlush(BufFile *file);
以上函数实现了缓冲页文件的初始化,新建缓冲页文件,从磁盘文件读取数据到缓冲文件,以及2个实现将缓冲文件写回磁盘的函数。注意:这里实现了2个将缓冲文件回写的函数,BufFileDumpBuffer是系统在日志点进行的数据写回,BufFileFlush是用户可调用的。
在Buffile.c中定义了缓冲文件的同时,在Fd.c中系统也定义了一个数据结构,将现在所有的活跃文件组成了一个列表。
我们知道,一个系统的打开文件是有限制的,尽管我们尽量保持文件打开不关闭可以在系统下一次的访问时节省一次磁盘I/O的开销,但是由于有数量限制,所以我们在达到文件数量限制的时候,必须淘汰一个文件。在pgsql中采用了一个文件的双链表结构完成了一个LRU淘汰算法。如图2-2:
0节点 |
使用少 |
使用较少 |
使用多 |
图2-2 |
可以从上图简单的看出,首先有个固定的0节点,不代表任何文件,然后有个链指向使用频率较高的文件节点,最后最高的节点又指向0构成一个环,同理,另外一边是指向使用频率较低的文件,形成一个环。哪一个文件使用后频率发生变化就和较高的比较,要是比他高就和他交换位置,若果比新位置的较高指针的还高,就再移动位置到不能移动为止。
以上就是这个算法的描述。
l 打开文件
l 关闭文件
很简单由上诉的思想可以得出。注:由于程序局部性原理,最新打开的文件的频率为最高,放在双链表的最高位置。
2.3 storage/smgr
这个模块管理所有的文件打开关闭操作·
2.3.1 主要数据结构
对于文件的组织,在磁盘管理中,对于VFD(活跃文件表)采取了一个对应的队列,这个队列就指出了物理文件的各个块的情况,如图2-3
|
文件下一块 |
文件第1块 |
图2-3 |
这个表指出了文件的各个块现在在内存和外存的位置,相当于一个活跃文件列表的地址转换表,当一块进入内存,则就将相应的对应地址改写为内存地址。
2.3.2 工作方式
在磁盘的管理里实现了2个经典的数据恢复函数
l smgr_redo(XLogRecPtr lsn, XLogRecord *record)
l smgr_undo(XLogRecPtr lsn, XLogRecord *record)
这2个函数的实现就是经典的undo,redo方法。
3 MORE
SMGR模块还是实现了B+数索引,Hash索引的关系查找,但是这部分没有理清。
参考 postsql 8.0 for linux 源代码
postsql 8.0 帮助文档。
所有评论(0)