lectrue5 存储模型和压缩

数据库工作负载:

OLTP(联机事务处理):操作速度快、执行时间短、操作重复性强,且查询通常比较简单,每次仅针对单个实体进行操作。OLTP 工作负载通常处理的写操作多于读操作,并且每次仅读取或更新少量数据。

OLAP(联机分析处理):OLAP 工作负载的特点是:查询运行时间长、逻辑复杂,且需要读取数据库的大部分内容。在 OLAP 工作负载中,数据库系统通常负责对从 OLTP 端收集到的现有数据进行分析,并从中推导出新的数据(结论)。

HTAP(混合事务与处理分析):HTAP 是近年来变得流行的一种新型工作负载类型。在这种模式下,OLTP 和 OLAP 工作负载在同一个数据库上共同存在。


存储模型:

N步存储模型(NSM):在 N 步存储模型中,DBMS 将单个元组的所有属性物理地连续存储在单个页面中。这种方法非常适合以插入为主 (insert-heavy) 且事务往往只针对单个实体进行操作的 OLTP 工作负载。之所以理想,是因为只需一次取值(Fetch)就能获取单条元组的所有属性。

  • 优点:

    • 插入、更新和删除速度快。

    • 对于需要获取整个元组的查询非常友好。

  • 缺点:

    • 对于扫描表的大部分内容和/或仅扫描属性子集(部分列)的查询效率较低。

分解存储模型(DSM):在分解存储模型中,DBMS 将所有元组的单个属性(列)连续存储在一个数据块中。因此,它也被称为列存储 (Column Store)。该模型非常适合 OLAP 工作负载,这类负载包含许多只读查询,且需要对表的属性子集进行大规模扫描。

  • 优点:

    • 减少了浪费的 I/O 量,因为 DBMS 只读取该查询所需的数据。

    • 由于增强了局部性(Locality)和缓存数据复用,查询处理性能更好。

    • 数据压缩效果更好。

  • 缺点:

    • 对于点查询(Point Query)、插入、更新和删除操作较慢,因为涉及元组的拆分/缝合(Splitting/Stitching)。

元组重组:当使用列存储时,为了将元组重新组合在一起,有两种常用的方法:

  • 固定长度偏移量 (Fixed-length offsets):这是最常用的方法。在给定的某一列中,其特定偏移量处的值,与另一列中相同偏移量处的值属于同一个元组。因此,该列中的每一个值都必须具有相同的长度。

  • 嵌入式元组 ID (Embedded tuple ids):这种方法较少见。对于列中的每个属性,DBMS 都会随之存储一个元组 ID(例如:主键)。然后,系统还会存储一个映射表,告诉它如何跳转到具有该 ID 的每个属性。注意,这种方法存储开销很大,因为它需要为每个属性条目都存储一个元组 ID。

混合存储模型(PAX):在混合存储模型中,DBMS在数据库页面内对属性进行垂直分区,这样做的目的是在保留行存储的空间局部性优势的同时,获得列存储的高效处理能力。在PAX中,行被水平划分为行组,在每个行组内部,属性被垂直划分为列,每个行组对于其所包含的行子集来说,就像是一个微型的列存储。PAX文件有一个全局头部,包含一个记录文件内各行组偏移量的目录,每个行组也维护自己的头部,记录有关其内容的元数据。


数据库压缩:由于磁盘I/O几乎始终是性能的主要瓶颈,压缩技术在基于磁盘的DBMS中得到了广泛应用,它在具有只读分析型负载的系统中尤为流行,如果元组事先经过压缩,DBMS就能一次性获取更多有用的元组,代价是压缩和解压会带来更大的计算开销。

内存数据库(In-memory DBMS)的情况则更为复杂,因为它们不需要从磁盘获取数据来执行查询。虽然内存比磁盘快得多,但压缩数据库可以降低对 DRAM(随机存取存储器)的需求并减少处理量。它们必须在速度与压缩比之间取得平衡。压缩数据库不仅能节省内存,还可能在查询执行期间降低 CPU 成本。

如果数据集完全由随机位组成,则无法进行压缩,然而,现实世界的数据集具有一些利于压缩的关键属性:

  • 属性值分布高度倾斜(例如布朗语料库中的齐夫分布,即少数数据出现的频率极高,大多数数据出现的频率极低,压缩算法可以用短编码代表高频数据来节省巨大空间)。

  • 同一元组的属性之间具有高度相关性(例如邮政编码与城市、下单日期与发货日期)。

基于此,我们希望数据库压缩方案具备以下特性:

  • 必须生成定长值。唯一的例外是存储在独立池中的变长数据。这是因为 DBMS 应当遵循字对齐,并且能够通过偏移量访问数据。

  • 允许 DBMS 在查询执行过程中尽可能推迟解压,即后期物化------这也使列存数据库高性能的原因之一,如果可能,DBMS会直接在压缩的数据上进行过滤或聚合,而不是先全部解压成原始元组。

  • 必须是无损方案,因为用户不喜欢丢失数据。任何形式的有损压缩都必须在应用层完成。

压缩粒度:我们想要压缩的数据类型很大程度上影响了可以选择的压缩方案,压缩粒度通常可以分为四个级别。

  • 块级 (Block Level):对同一张表的一块元组进行压缩。

  • 元组级 (Tuple Level):压缩整个元组的内容(仅限 NSM 行存模型)。

  • 属性级 (Attribute Level):压缩单条元组中的单个属性值。可以针对同一元组的多个属性。

  • 列级 (Columnar Level):对存储在多个元组中的一个或多个属性的多个值进行压缩(仅限 DSM 列存模型)。这允许使用更复杂的压缩方案。


朴素压缩:DBMS 使用通用算法(如 gzip、LZO、LZ4、Snappy、Brotli、Oracle OZIP、Zstd)对数据进行压缩。尽管有多种压缩算法可供选择,但工程师通常会选择那些压缩率较低但压缩/解压速度更快的算法。

MySQL InnoDB 是使用朴素压缩的一个典型例子。DBMS 对磁盘页面进行压缩,将它们填充到 2KB 的整数倍,然后存储到缓冲池中。然而,每当 DBMS 尝试读取或修改数据时,缓冲池中的压缩数据必须先进行解压。

由于访问数据需要先解压,这限制了压缩方案的作用域,如果目标是将整张表压缩成一个巨大的数据块,使用朴素压缩方案将是行不通的,因为每次访问都需要对整张表进行压缩/解压,而朴素算法需要从文件的开头解压,直到还原出你需要的位置的数据。因此,由于压缩作用域有限,MySQL 会将表拆分成较小的块。

另一个问题是,这些朴素方案不考虑数据的高层含义或语义。算法既不了解数据的结构,也不了解查询计划如何访问数据。因此,这消除了利用后期物化的机会,因为 DBMS 无法判断何时可以推迟数据的解压。


列式压缩

运行长度编码:RLE将单列中相同值的运行(连续出现的实例)压缩为三元组。

  • 属性的值

  • 该值在列段中的起始位置

  • 该运行中包含的元素数量

DBMS应当事先对列进行智能排序,以最大限度地增加压缩机会。排序可以将重复的属性聚集在一起,从而提高压缩率。需要注意的是,RLE的有效性在很大程度上取决于底层数据的特征(例如每个数据中属性的数量和频率)。

位填充编码:当一个属性的所有值都小于该属性声明的最大大小时,使用更少的比特位来存储它们。

多数派编码:这是位填充的一种变体,它使用一个特殊的标记来指出某个值何时超过了最大大小,并维护一个查找表来存储这些异常值。

位图编码:DBMS 为特定属性的每个唯一值存储一个单独的位图,向量中的偏移量对应于一个元组。位图中的第 i 个位置对应于表中的第 i 个元组,用于指示该值是否存在。位图通常被分割成块,以避免分配大块的连续内存。

这种方法仅在值的基数(即唯一值的数量)较低时才切实可行,因为位图的大小与属性值的基数成线性正比。如果值的基数很高,位图的大小可能会超过原始数据集。

增量编码:与其存储精确值,不如记录同一列中前后相邻值之间的差异。基准值可以内联存储,也可以存储在独立的查找表中。我们还可以对存储的增量使用 RLE,以获得更好的压缩率。

增量前缀编码:这是增量编码的一种类型,其中记录了公共的前缀或后缀及其长度,从而不需要重复存储。这种方法在处理有序数据时效果最好。

字典压缩:最常见的数据库压缩方案是字典编码。DBMS 用更短的代码替换数值中频繁出现的模式。随后,它仅存储这些代码以及将这些代码映射回原始值的属性结构(即字典)。字典压缩方案需要支持快速的编码/解码以及范围查询。

  • 编码与解码:字典需要决定如何编码(将未压缩的值转换为压缩形式)和解码(将压缩值转换回原始形式)。因此,不可能使用哈希函数(因为哈希不保序)。

  • 保序性:编码后的值需要支持与原始值相同的排序顺序(即保序编码)。这确保了在压缩数据上运行的压缩查询所返回的结果,与在原始数据上运行的未压缩查询的结果是一致的。这种保序特性允许直接在代码上执行操作(如比较)。

相关推荐
zhangzhangkeji5 小时前
UE5 C++(44-4):对比一下蓝图中的射线检测节点,源代码,按通道与按对象类型
ue5
暮志未晚Webgl21 小时前
UE使用内置功能查看性能
ue5
AI视觉网奇1 天前
Epic linux 打包。
笔记·学习·ue5
伪善者1 天前
UE5 打包插件
ue5·打包
AI视觉网奇1 天前
ue5 开发 web socket server 实战2026
c++·学习·ue5
zhangzhangkeji2 天前
UE5 C++(39):创建 TimeHandle 定时器
ue5
zhangzhangkeji2 天前
UE5 C++(38):创建 Interface接口
ue5
zhangzhangkeji2 天前
UE5 C++(40):创建 3DWidget 并渲染到屏幕上,涉及类 UUserWidget 与 UWidgetCompopent
ue5
zhangzhangkeji2 天前
UE5 C++(41):创建 ApplyDamage 并接受伤害 TakeDamage
ue5