3 存储管理
数据库管理系统的任务本质上是向存储设备上写入数据或者从存储设备上读出数据,因此对于
一个DBMS来说,存储的管理是一项非常基础和重要的技术。在 Postgresql中,有专门的模块负责
管理存储设备(包括内存和外存),我们称之为存储管理器。存储管理器提供了一组统一的管理外
存与内存资源的功能模块,所有对外存与内存的操作都将交由存储管理器处理,可以认为存储管理
器是数据库管理系统与物理存取设备的接口。与 Postgresql其他模块相比,存储管理器处在系统结
构的底层,它包含了操作物理存取设备的接口。本章主要介绍存储管理器的功能和实现。
3.1 存储管理器的体系结构
- Pg的存储管理器主要两功能:内存管理和外存管理。
- 除管理内存和外存交互外,
- 存储管理器的另一个主要任务是对内存进行统筹安排和规划。
- 存储管理器的体系结构如图3-1
3.4 表操作与元组操作
- 上层模块通过表和元组的操作来调用存储模块的诸多功能,
- 这些操作是存储模块与上层其他模块交互的接口,
- backend/access/heap及文件heaptuple.c中对它们详细定义
- 接下来将介绍关系表的操作以及对元组的操作。
3.4.1表操作
- 表操作由heapam.c提供接口
- 两种操作表的方式,以表名操作;以表的OID操作
- 以表名操作的函数,
- 先通过表的名称获OID,
- 再调以OID操作的函数(某表被删后又创建一个同样表名的表,获取OID前需处理所有的无效信息保证根据表名得到的OID最新)
- heapam.c实现关系表的打开、关闭、删除、扫描
- 1.打开表
- 表的打开并不是打开具体的物理文件,仅返回该表的Relationdata
- 两种打开方式
- 1)根据表OID:调relation_open,该函数将根据表的OID从 RelCache中找到相应的关系描述符RelationData,并将引用计数加1。
- 如果第一次,则在Relcache中创建一个新的Relationdata(从系统表中获取与表相关的信息填充Relationdata),并将引用计数置为1
- 然后根据参数中的锁类型(lockmode)对其加锁,且返回 Relationdata。
- 2)根据表名:调relation_openrv,与relation_open类似,
- 但它会首先获取到表OID
- 然后调relation_open得到 Relation Data
- 每当一表被打开,都为这个关系表创建一个RelationData
- Relationdata是关系描述符(relation descriptor),
- 它记录该表的全部信息,前面章节里已经多次提到该函数。
- 对表的其他操作都以表的Relationdata作参数
2.扫描表
- 本章顺序扫描的实现,索引扫描4章
- 从表获取一个指定元组的顺序扫描过程
- 需经图3-24的几个层次
- 首先将文件块逐一加载到缓冲区中,
- 然后扫描毎个缓冲区中的每一个元组,
- 以找到满足查询需求的元组。
- 在对一个表扫描时,会用结构体HeapScanDescData_(数据结构3.29)来保存表的基本信息以及当前的扫描状态,称为扫描描述符。
- Pg中对顺序扫描的实现有两策略:
- 基本策略和同步扫描策略。
- rs_allow_sync、rs_startblock和rs_syncscan用于同步
扫描策略。
- 本节先对基本的顺序扫描介绍,同步扫描下一节介绍。
- 对该结枃体的填充分为两种:
- 在扫描前初始化以及在扫描过程中填充扫描状态。
-
初始化扫描时调heap_beginscan_interal对该结构体设置,
-
几个主要参数:
-
进行扫描的表的 Relationdata
-
扫描键的个数。
-
扫描键。
-
是否允许使用缓冲区控制策略。
-
是否使用同步扫描策略。
3.4.2元组操作
- 对元组的操作包括插入、除和更新
- 这三种都把元组当作一个整体处理
- heaptuple.c中还实现了元组内部结构的相关操作,
- 元组的构造、修改、分解、复制、释放
- 一个完整的元组信息对应一个HeapTupleData结构和一个 TupleDesc
- HeapTupleData含一个前面介绍过的HeapTupleHeaderData
- TupleDesc是RelationData的一部分,元组描述符,
- 记录与该元组相关的全部属性模式信息。
- 通过元组描述符可读取磁盘中存储的无格式数据,
- 并根据元组描述符构造出元组的各个属性值,
- natts:属性个数
- attrs数组:从pg_attribute读取,每个表示一个属性。
- constr数组:pg_constraint中读取,每个表示一个约束条件。
- tdtypeid:元组的复合类型OID
- Pg自动为每个表建立一个行类型,
- 表示该表的行结构。
- tdtypemod是元组模式。当tdtypeid有值时,置-1。
- typeid为默认RECORDOID时,可为-1或其在typecache(建立在 syscache之上)中的位置
- tdhasoid:是否有OID
- tdrefcount:元组描述符的引用计数。
- 只有为0时,元组描述符才能被删
- HeapTupleData是元组在内存中的拷贝,
- 是磁盘格式的元组读入内存后的存在方式,
- 数据结构中并没有出现存储元组实际数据的属性,
- 这是因为Pg巧妙地将元组的实际数据存放在HeaptupleheaderData后面的空间中。
1.插入元组
- 插之前,先要根据元组内数据和描述符等信息初始化HeapTuple
- heap_form_tuple实现这功能
- typedef HeapTupleData *HeapTuple;
-
values是要插的元组的各属性值数组
-
isnull标识哪些属性为空
-
它根据俩数组调heap_compute_data_size计算形成元组所需要内存,然后为元组分配足够空间
-
在进行必要的元组头部设置后,调heap_fill_tuple向元组填充实际数据。
-
这个函数执行完毕,内存里面就有了一个元组了。
- 当完成元组数据在内存中的构成后,
- 下一步就可准备向表中插入之组了。
- heap_insert,流程如图3-27
- 1)为新插的元组调newoid为其分配一个OID。
- 2)初始化tup,
- 设t_xmin和tcmin为当前事务ID和当前命令ID、
- 将 t_xmax置无效、
- 设tableoid(包含此元组的表的OID)。
- 3)找到属于该表且空闲空间(freespace)大于 newtup的文件块,
- 将其载入缓冲区以来插人tup(调RelationGetBufferForTuple
- 4)有了插入的元组tup和存放元组的缓冲区后,就调Relation PutHeapTuple将新元组插入至选中的缓冲区
- 5)向事务日志(XLog)写入一条XIog(见7章)
- 6)当完成上述过程后,将缓冲区解锁并释放,并返回插人元组的OID。