lectrue4 数据库存储

上一节中我们讲到,目前页布局的两种主要方法,分别是槽位页和日志结构。

槽位页设计存在的问题:

1.碎片化:删除元组会在页面中留下空隙,导致空间利用率不全。

2.无用的磁盘I/O:由于非易失性存储(磁盘存储)是按块操作的特性,即使只更新一个元组,也需要读取整个块。

3.随机磁盘I/O:更新20个不同的元组可能需要磁盘磁头跳转到20个不同的位置,速度很慢。

日志结构存储概述:此结构下,内存中我们有一张跳表mtable,mtable中存储的是日志记录,日志记录中的日志条目记录操作而非对应元组的完整状态。当mtable积累一定量后,会将其压缩为SSTable文件并提交到磁盘上。

如何理解SSTable文件:比如有甲乙丙丁四人,数据库存储它们的登录时间。

第一次提交:假如第一次提交之前,有甲在时间1登录,乙在0登录,丙在2登录,它们三人登录时间记录被压缩为一个SSTable并提交到磁盘中。

第二次提交:假如第一次提交之后,第二次提交之前,有甲在4登录,乙在5登录,丁在7登录,它们三人的登录时间又被压缩为一个SSTable并提交到磁盘中。

可以看到SSTable是在特定的某一段时间内,数据库被修改元组的集合。最新的SSTable和之前的所有SSTable压缩可以成为一个完整的数据库。

注:SSTable之间是有时间顺序的,假如最新的SSTable和一个旧的SStable之间都有关于元组A的信息,那么显然应该使用最新SSTable的记录。

查询元组:现在mtable中查找相关元组的日志记录,假如刚好有相关元组的PUT记录,那么直接在内存中查找到元组。如果内存中没有找到,那么需要按照时间顺序将磁盘中的SSTable文件加载到内存中再进行查找。

压缩:注意区分,日志记录转换为SSTable并非压缩,SSTable之间的压缩才叫压缩。假如SSTable之间永远不压缩,那么会出现无数个SSTable。这在时间和空间上都是不可接收的。因此DBMS会定期进行压缩。

压缩策略:

1.通用压缩:一种懒惰的SSTable合并方式,等到SSTable文件实在太多才进行压缩。但文件重叠严重,好处是写放大低。

2.分层压缩:Level0为乱序区,Level1及以上为整齐区,每一层内的所有文件都必须排好序,且互不重叠,只有当上一层达到上限时才会合并到下一层。比如Level1的文件1存1到100,文件2存101到200。这种方式空间利用率更高,但是写放大极大,写一条数据,可能因为层层合并被翻来覆去的反复写。

不论哪种策略,压缩的开销都很大,且会导致写放大,即一次逻辑写入可能导致多次物理写入。

记录内容:包含元组的唯一标识符、操作类型(PUT/DELETE),对于 PUT 操作还包含元组的具体内容。

优缺点:写速度极快,读速度可能较慢。磁盘写入是顺序的,且现有页面不可变,这减少了随机磁盘 I/O。非常适合"仅追加"的存储介质。

索引加速:为了避免漫长的读取过程,DBMS 可以建立索引(簿记)来跳转到日志中的特定位置。


索引组织存储

无论是面向页的存储,还是日志结构存储,由于表本身是无序的,它们都依赖于额外的索引来查找单个元组。

在索引组织存储的方案中,DBMS直接将表的元组作为索引数据结构中的值进行存储,在这种模式下,DBMS会使用类似于插槽页的页面布局,并且元组在页面内通常是按照键进行排序存储的。


数据表示

元组中的数据本质上只是字节数组 ,它本身并不记录属性所属的数值类型。DBMS 必须负责记录如何追踪并解析这些字节。数据表示方案指的就是 DBMS 如何为某个值存储字节。

DBMS必须确保元组是字对齐的,以便CPU能够直接访问而不会出现意外行为或产生额外的处理开销,通常采用两种方案:

  • 填充 (Padding):在属性后添加空位,以确保元组实现字对齐。

  • 重排序 (Reordering):在物理布局中交换属性的顺序,以确保它们是对齐的。

元组中可以存五种高层数据类型:整数,变精度数字,定点精度数字,变长值以及日期/时间。

整数:大多数DBMS使用IEEE-754 标准指定的原生C/C++ 类型来存储整数。这些值是定长 的。 示例:INTEGER, BIGINT, SMALLINT, TINYINT

变精度数字:这些是不精确的、变精度的数值类型,使用 IEEE-754 标准指定的"原生"C/C++ 类型。这些值也是定长 的。 对变精度数字的操作计算速度比任意精度数字更快,因为 CPU 可以直接对它们执行指令。然而,由于某些数字无法精确表示,在进行计算时可能会出现舍入误差 。 示例:FLOAT, REAL

定点精度数字:这些是具有任意精度和标度的数值类型。它们通常以精确的、变长 的二进制形式存储(几乎像字符串一样),并带有额外的元数据,用于告知系统数据的长度以及小数点的位置。 当舍入误差不可接受时,会使用这些数据类型,但 DBMS 为了获得这种准确性会付出性能代价 。 示例:NUMERIC, DECIMAL

变长数据:这些代表任意长度的数据类型。它们通常存储有一个头部 (Header),用于追踪字符串的长度,以便轻松跳转到下一个值。头部还可能包含数据的校验和 (Checksum)。

大多数 DBMS 不允许单个元组的大小超过单个页面的大小。允许超过的系统会将数据存储在特殊的溢出页中,并在元组中包含对该页面的引用。这些溢出页可以包含指向额外溢出页的指针,直到存储完所有数据。

某些系统允许将这些大值存储在外部文件 中,此时元组将包含指向该文件的指针。例如,如果数据库存储照片信息,DBMS 可以将照片存储在外部文件中,而不是让它们占用 DBMS 中的大量空间。这种做法的一个缺点是 DBMS 无法操作该文件的内容。因此,不具备持久性 (Durability) 或事务保护 。 示例:VARCHAR, VARBINARY, TEXT, BLOB

日期和时间:不同系统对日期/时间的表示方式各不相同。通常,这些被表示为自 Unix 纪元以来的某种单位时间(微秒/毫秒)。 示例:TIME, DATE, TIMESTAMP

空值类型:DBMS 中有三种常见的表示空值 (Null) 的方法:

  • 空列位图头部 (Null Column Bitmap Header):在集中式的头部中存储一个位图 (Bitmap),指明哪些属性为空。这是最常用的方法。

  • 特殊值 (Special Values):为某种数据类型指定一个特定的值来代表 NULL(例如,使用 INT32 的最小值)。

  • 按属性设置 Null 标记 (Per Attribute Null Flag) :存储一个标记来指明某个值是否为空。不推荐这种方法,因为它的内存效率不高。为了避免破坏字对齐,DBMS 必须为每个值使用超过一个位 (Bit) 的空间。


系统目录:为了让DBMS能够解析元组的内容,它维护了一个内部目录,用于记录关于数据库的元数据。

元数据内容:

  • 数据库拥有的 ,以及这些表上的任何索引

  • 数据库的用户 及其拥有的权限

  • 关于表的统计信息以及其中包含的内容(例如,某个属性的最大值)。

大多数DBMS将它们的目录以自身存储表时所使用的格式存储在数据库内部,它们使用特殊的代码来引导这些目录表。

既然目录也是表,那么读取目录表也需要目录信息,因此DBMS会在代码中硬编码最核心目录表的结构(如pg_class或pg_attribute),从而开启整个系统。

相关推荐
陈友松2 天前
UE5运行时操作撤销系统插件
ue5·ue4·运行时回退撤销
北冥没有鱼啊2 天前
UE5 离谱问题,角色动画不播放
游戏·ue5·ue4·游戏开发·虚幻
WinstonJQ4 天前
AirSim无人机仿真入门(一):实现无人机的起飞与降落
python·机器人·游戏引擎·ue4·无人机
小江村儿的文杰6 天前
UE4 PSO介绍六:PSO与Shader编译的关系
ue4
小江村儿的文杰7 天前
UE4 PSO介绍五:Build PSO
ue4
二DUAN帝8 天前
像素流与UE通信
前端·javascript·css·ue5·html·ue4·html5
归真仙人9 天前
【UE】UMG安卓相关问题
android·ue5·游戏引擎·ue4·虚幻·unreal engine
小江村儿的文杰14 天前
UE4 PSO介绍四:PSO Precache
ue4
小江村儿的文杰14 天前
UE4 PSO介绍三:认识.scl.csv
ue4