目录
openGauss 数据库 SQL 引擎
openGauss 数据库执行器技术
openGauss 存储技术
一、openGauss 存储概览
二、openGauss 行存储引擎
三、openGauss 列存储引擎
Ⅰ、列存储引擎的总体架构
Ⅱ、 列存储的页面组织构造
Ⅲ、 列存储的 MVCC 设计
IV、列存储的索引设计
V、列存储自适应压缩
VI、列存储的长久化设计
四、openGauss 内存引擎
openGauss 事务机制
openGauss 数据库安全
openGauss 存储技术
三.openGauss 列存储引擎
传统行存储数据压缩率低,必须按行读取,即便读取一列也必须读取整行。在剖析性的作业以及业务负载的状况下,数据库往往会遇到针对大量表的简单查问,而这种简单查问中往往仅波及一个较宽(表列数较多)的表中个别列。此类场景下,行存储以行作为操作单位,会引入与业务指标数据无关的数据列的读取与缓存,造成了大量 IO 的节约,性能较差。因而 openGauss 提供了列存储引擎的相干性能。创立表的时候,能够指定行存储还是列存储。
总体来说,列存储有以下劣势:
(1) 列的数据特色比拟类似,适宜压缩,压缩比很高,在数据量较大(如数仓)场景下会节俭大量磁盘空间;压缩比高同时也会进步单位作业下的 IO 效率。
(2) 当表中列数比拟多,然而拜访的列数比拟少时,列存储能够按需读取列数据,大大减少不必要的读 IO,进步查问性能。
(3) 基于列批量数据向量运算,联合向量化执行引擎,CPU 的缓存命中率比拟高,性能比拟好,更适宜 OLAP 大数据统计分析的场景。
(4) 列存储表同样反对 DML 操作和 MVCC,性能齐备,且在应用角度做了良好的兼容,根本是对用户通明的,方便使用。
列存储引擎的总体架构 01
列存引擎的存储根本单位是 CU(Compression Unit,压缩单元),即表中一列的一部分数据组成的压缩数据块。行存引擎中是以行作为单位来治理,而当应用列存储时,整个表整体被依照不同列划分为若干个 CU,实例如图 25 所示。
图 25 CU 划分形式
如图 25 所示,假如以 6 万行作为一个单位,则一个 12 万行、3 列宽的表,则被划分为 8 个 CU,每个 CU 对应一个列上的 6 万个列数据。图中有 Col0、Col1、Col2、Col3 四列,数据依照行切分了两个 Row group(行组),每个 Row group 有固定的行数。针对每个 Row group 依照列做数据压缩,造成压缩单元 CU(Compression unit)。每个 Row group 外部各个列的 CU 的行边界是齐全对齐的。当然,大部分时候,CU 在通过压缩后,因为数据特色与压缩率的不同,文件大小会齐全不同,例如图 26 所示。
图 26 压缩单元示意图
为了治理表对应的 CU,与执行器层进行对接来提供各种性能,列存储引擎应用了 CUDesc(压缩单元描述符)表来记录一个列存表中 CU 对应的元信息,如图 27 所示。
图 27 列存引擎整体架构图
注:Cmn 示意第 m 列的 cuid 是 n 的压缩单元。
每个 CU 对应一个 CU Desc 的记录,在 CU desc 里记录了整个 CU 的事务工夫戳信息、CU 的大小、存储地位、magic 校验码、min/max 等信息。
与此同时,每张列存表还配有一张 Delta 表,Delta 表本身为行存储表。当有大量的数据插入到一张列存表时,数据会被临时放入 Delta 表,等到达到阈值或满足肯定条件或操作时再行整合为 CU 文件。Delta 表能够帮忙咱们防止单点数据操作带来的很重的 CU 操作与开销。
设计采纳级别的多版本并发管制,删除通过引入 Virtual Column Bitmap 来标记删除。Bitmap 是多版本的。
列存储的页面组织构造 02
下面讲到了 CUDesc 表以及其用来记录元信息的目标。CUDesc 的典型构造如图 28。
图 28 CUDesc 的典型构造
其中:
(1)_rowTupleHeader 为传统行存记录的行头,其中蕴含了后面提到过的事务以及地位信息等,用来进行可见性判断等。
(2)cu_mode 理论为此 CUDesc 对应 CU 的 infomask,记录了一些 CU 的特色信息(比方是否 Full,是否有 NULL 等等)
(3)magic 是 CUDesc 与 CU 文件之间校验的要害信息。
(4)min/max(最小值 / 最大值)为稠密索引,后续会进一步开展。
而 CU 文件自身构造,则如图 29 所示。
图 29 CU 文件自身构造
列存储在 CUDesc 表的存储信息根底上设计了一套与下层交互的操作 API。除了下面列存储的页面组织构造以及文件治理中人造能够展现出的构造机制之外,列存储还有一些要害的技术特色:
(1)列存储的 CU 中数据的删除,实际上是标记删除。删除操作,相当于是更新了 CUDesc 表中 CU 对应 CUDesc 记录的 delete bitmap(删除位图)构造,标记列中某行对应数据已被删除,而 CU 文件数据不会被更改。这样能够防止删除操作带来的 IO 放大以及解压、压缩的高额 CPU 开销。这样的设计,也能够使得对于同一个 CU 的 select(查问)和 delete(删除)互不阻塞,晋升并发能力。
(2)列存储 CU 中数据更新,则是遵循 append-only(仅容许追加)准则的,即 CU 文件仅会向后进行延展裁减,亦或是启用新的 CU 文件,而不是在对应行在 CU 中的地位就地更新。
(3)因为 CU 以及 CUDesc 的元数据管理模式,原有零碎中的 Vacuum 机制实际上并不会十分无效的革除 CU 中曾经生效的存储空间,因为 Lazy Vacuum(清理数据时,只是标识无用行的状态能够录入新数据,不会影响对表数据的操作)仅能在 CUDesc 级别进行操作,在少数场景下无奈对 CU 文件自身进行清理。列存储外部如果要对列存数据表进行清理,须要执行 Vacuum Full(除了清理无用行,还会合并数据块,整个过程会锁定表)操作。
列存储的 MVCC 设计 03
了解了 CU、CUDesc 的根本构造,以及 CUDesc 的治理,或者说是其“代理”角色,列存的 MVCC 设计以及治理,实际上就十分好了解了。
因为列存的操作根本单位 CU 是由 CUDesc 表中的行进行治理的,因而列存表的 CU 可见性判断,也是由 CUDesc 的行头信息、依照传统的行存可见性进行判断的。
同样的,列存可见性的单位,也是 CU 级别(CUDesc),不同于行存的 Tuple 级别。
列存表的并发管制是 CU 文件级别的,实际上也等同于其 CUDesc 代理表的 CUDesc 行之间的并发管制。多个事务之间在一个 CU 上的并发管控,实际上取决于在其对应的 CUDesc 记录上是否抵触。例如:
(1)两个事务并发去读一个 CU 是可进行的,两个事务都能够拿到此 CU 对应 CUDesc 行级别的 share lock(共享锁)。
(2)两个事务并发去更新一个 CU,会因为在 CUDesc 上的锁抵触而触发一个事务回滚(当然,如果是 read commited(读已提交)隔离级别并关上容许并发更新的开关,这里会做的事件是拿到此 CUDesc 最新版本的 ctid,而后重跑一部分 queryTree(查问树),来进行更新操作;此局部内容,详见第 7 章事务相干章节)。
(3)两个事务并行执行,一个事务对一个 CU 执行了 delete 操作并后行提交,另一个事务在 repeatable read(可重读)的隔离级别下,其获取的快照,只能看到这个 CUDesc 在 delete 产生前的版本,这个版本中的 CUDesc 中的 delete_bitmap(删除位图),对应数据没有被标记删除。也因为 CU 的行删除是标记删除的机制,因而数据在原有 CU 的数据文件中仍旧可用,此事务仍旧能够在其对应的快照下读到对应行。
图 30 删除 CU 中局部数据所进行的实际操作
删除 CU 中局部数据所进行的实际操作如图 30 所示。
从下面的几个例子能够看出,列存储对于更新的 append only(仅容许追加)策略以及对于删除的标记删除形式,对于列存事务 ACID 的反对,是至关重要的。
未完待续 …….