源码目录:/src/gausskernel/storage
引言
我们队伍试图完整的了解查询引擎到存储引擎的数据库完整查询过程,对查询解析,查询优化,查询重写,查询执行,存储引擎展开学习。我所负责的板块是存储引擎。
一、存储引擎需要解决的问题
存储的数据必须要保证ACID,即原子性(A)、一致性©、隔离性(I)、持久性(D)。
支持高并发读写,高性能。
充分发挥硬件的性能,解决数据的高效存储和检索能力
二、opengauss存储引擎概述
openGauss的存储引擎是指数据库系统中负责数据的存储、组织、管理和访问的部分。
从整个数据库服务的组成构架来看,存储引擎向上对接SQL引擎,向下对接存储介质,按照特定的数据组织方式,以页面、列存储单元(CU,compression unit)或其他形式为单位,通过存储介质提供的特定接口,对存储介质中的数据完成读、写操作。如下图所示
shortage主流程代码如下:
磁盘初始化,开关,同步等系统操作。
文件路径:opengauss-server\src\gausskernel\storage\smgr\smgr.cpp
static const f_smgr smgrsw[] = {
/* magnetic disk */
{ mdinit,
NULL,
mdclose,
mdcreate,
mdexists,
mdunlink,
mdextend,
mdprefetch,
mdread,
mdwrite,
mdwriteback,
mdnblocks,
mdtruncate,
mdimmedsync,
mdasyncread,
mdasyncwrite,
NULL
},
这个存储管理器提供了对传统磁盘存储的访问。
函数 | 功能 |
---|---|
mdinit | 初始化 |
mdclose | 关闭 |
mdcreate | 创建 |
mdexists | 检查存在 |
mdunlink | 删除 |
mdxtend | 扩展 |
mdprefetch | 预取 |
mdwirte | 写入 |
mdwriteback | 写回 |
mdnblocks | 获取块数量 |
mdtruncate | 截断文件 |
mdimmedsync | 立即同步 |
mdasyncread/mdasyncwrite | 异步读取/写入 |
当后端服务器启动时调用函数smgrinit |
void smgrinit(void)
{
int i;
for (i = 0; i < NSmgr; i++) {
if (smgrsw[i].smgr_init) {
(*(smgrsw[i].smgr_init))();
}
}
/* register the shutdown proc */
if (!IS_THREAD_POOL_SESSION || EnableLocalSysCache()) {
on_proc_exit(smgrshutdown, 0);
}
InitSync();
}
当后端服务器关闭时调用函数smgrsutdown
void smgrshutdown(int code, Datum arg)
{
int i;
for (i = 0; i < NSmgr; i++) {
if (smgrsw[i].smgr_shutdown) {
(*(smgrsw[i].smgr_shutdown))();
}
}
}
它具有以下特点
联机事务处理:行存储引擎可以方便地对元组进行更新、插入和删除操作,因为不需要对整个页面或列进行重写,只需要修改相应的行即可。
多版本并发控制(MVCC):即为每个数据项保存多个版本,每个版本都有一个唯一的事务版本号,用来标识这个版本是由哪个事务创建的。
原地更新:行存储引擎可以采用原地更新(in-place update)设计,即在不改变元组的大小和位置的情况下,直接覆盖元组中的数据,从而提高更新的效率和性能。
临时数据存储:利用共享缓冲区(shared buffer)来缓存数据库常被访问的索引、表数据、执行计划等内容。大幅减少了磁盘IO,极大地提升了数据库性能。
持久性和可恢复性:用预写式日志(WAL)来保证数据的持久性和可恢复性。
从整个数据库服务的组成构架来看,存储引擎向上对接SQL引擎,为SQL引擎提供或接收标准化的数据格式(元组或向量数组);向下对接存储介质,按照特定的数据组织方式,以页面、列存储单元(CU,compression unit)或其他形式为单位,通过存储介质提供的特定接口,对存储介质中的数据完成读、写操作。
openGauss支持多个存储引擎来满足不同场景的业务诉求,分别是行存储引擎、列存储引擎和内存引擎。
以地震监测为例如下图
内存引擎是一种基于内存的存储引擎,它以内存为主要介质存储数据,并支持持久化和分布式事务。内存引擎的特点是提供极致的性能和低时延,适合对性能要求非常高的场景,例如银行风控、实时推荐等。
行存储引擎是一种基于磁盘的存储引擎,它以行为单位存储数据,并支持多版本并发控制(MVCC)和原地更新(in-place update)。行存储引擎的特点是支持高并发读写,时延小,适合联机事务处理(OLTP)场景,例如订货、发货、银行交易等。
列存储引擎是一种基于磁盘的存储引擎,它以列为单位存储数据,并对数据进行压缩和向量化处理。列存储引擎的特点是提供高压缩比,减少磁盘空间占用和IO开销,同时面向列的计算提高了CPU缓存命中率和计算性能,适合联机分析处理(OLAP)场景,例如数据统计报表分析等。
创建表的时候可以指定为行存储引擎表、列存储引擎表、内存引擎表,支持一个事务中包含对三种引擎表的 DML操作,可以保证事务的 ACID性质。
本文主要介绍openGauss行存储引擎,其他的存储引擎将在后面的文章进行介绍。
storage各模块以及功能
三、open gauss前世今生
正如牛顿所说的站在巨人的肩膀上能看得更远,建立在PG9.2.4基础上的Open Gauss也有着更好的性能。我们先简单介绍一下PG数据库,再对比两者存储引擎的异同。
PG简介
PostgreSQL是一种特性非常齐全的自由软件的对象-关系型数据库管理系统(ORDBMS),是以加州大学计算机系开发的POSTGRES,4.2版本为基础的对象关系型数据库管理系统。PostgreSQL支持大部分的SQL标准并且提供了很多其他现代特性,如复杂查询、外键、触发器、视图、事务完整性、多版本并发控制等。同样,PostgreSQL也可以用许多方法扩展,例如通过增加新的数据类型、函数、操作符、聚集函数、索引方法、过程语言等。
PostgreSQL是一个功能强大的开源对象关系型数据库系统,他使用和扩展了SQL语言,并结合了许多安全存储和扩展最复杂数据工作负载的功能。PostgreSQL的起源可以追溯到1986年,作为加州大学伯克利分校POSTGRES项目的一部分,并且在核心平台上进行了30多年的积极开发。PostgresSQL凭借其经过验证的架构,可靠性,数据完整性,强大的功能集,可扩展性以及软件背后的开源社区的奉献精神赢得了良好的声誉,以始终如一地提供高性能和创新的解决方案。
PG与Open Gauss的sortage结构对比
观察两者storage的目录结构,如上图,我们不难发现open gauss有了以下不同:
openGauss相比于PG有了一些重要的增强。
- 首先,它新增了列存储相关的功能模块,如cmgr和cstore,这使得openGauss能够适应更多的场景。
- 其次,它引入了MOT(Memory-Optimized Table)存储引擎,也就是mot模块,这是openGauss数据库最先进的生产级特性,它针对多核和大内存服务器进行了优化,能为事务性工作负载提供更高的性能。
- 此外,openGauss还新增了外表功能的相关模块,如dfs和bulkload等。最后,它新增了备机页面修复模块remote。
这些增强使得openGauss在许多方面继承并超越了PG。
四、行存储引擎总体框架
openGauss行存储引擎采用原地更新(in-place update)设计,支持 MVCC(Multi- Version Concurrency Control,多版本并发控制),同时支持本地存储和存储与计算分离的部署方式。行存储引擎的特点是支持高并发读写,时延小,适合 OLTP交易类业务场景。
原地更新(in-place update):在不改变元组的大小和位置的情况下,直接覆盖元组中的数据,从而提高更新的效率和性能。
MVCC:让数据库管理系统支持多个事务同时访问同一份数据,而不会产生冲突或不一致的情况。
我们对磁盘引擎的内部结构做更详细的解剖,如下图
行存储缓冲区主流程如下所示:
源码位置:opengauss-server\src\gausskernel\storage\buffer\bufmgr.cpp
步骤 | 主调用函数 | 实现功能 |
---|---|---|
1 | ReadBufferExtended | 查找或者创建一个缓冲区 |
2 | ReleaseBuffer | 释放一个缓冲区 |
3 | MarkBufferDirty | 标记写脏缓冲区 |
- 对于步骤1,经历了以下过程
1.使用存储管理器smgr打开一个缓冲区
2.拒绝读取非局部关系的请求
3.读取缓冲区,更新统计信息数量,反馈缓存命中数量
- 对于步骤2,经历了以下过程
1.错误释放判断处理
2.释放当前缓冲区
- 对于步骤3,经历了以下过程
1.判断缓冲区是否可见
2.判断缓冲区是否写入
3.将未入队的脏页入队
4.如果缓冲区不是“脏”状态,则更新信息计数
五、存储引擎关键技术概述
涉及主要关键技术有
- 基于事务ID以及ctid(行号)的多版本管理。
- 基于 CSN(CommitSequenceNumber,待提交事务的序列号,它是一个64位递增无符号数)的多版本可见性判断以及 MVCC机制。页面,在数据页面中存放元组以及元组的历史版本并集中管理,使用Vacuum(垃圾清理)线程进行定期的空间回收。
- 基于大内存设计的缓冲区管理。
- 平滑无性能波动的增量检查点(checkpoint)。
- 基于并行回放的快速故障实例恢复。
下面仅对基于事务ID以及ctid(行号)的多版本管理做详细介绍
- 基于事务ID以及ctid(行号)的多版本管理是一种数据库中的并发控制技术,它可以让数据库支持多个事务同时访问同一份数据,而不会产生冲突或不一致的情况。基于事务ID以及ctid(行号)的多版本管理的核心思想是为每个数据项保存多个版本,每个版本都有一个唯一的事务ID,用来标识这个版本是由哪个事务创建的。同时,每个版本也有一个ctid,用来标识这个版本在页面中的位置。当一个事务要读取一个数据项时,它会根据自己的事务ID和隔离级别,选择一个合适的版本来读取。当一个事务要更新或删除一个数据项时,它会创建一个新的版本,并把自己的事务ID和新的ctid作为新版本的标识。这样,不同的事务就可以看到不同的数据快照,从而实现并发访问的效果。
- 当一个事务要插入一个新的数据项时,它会为这个数据项分配一个唯一的事务ID,用来标识这个数据项是由哪个事务创建的。同时,它会为这个数据项分配一个ctid,用来标识这个数据项在页面中的位置。然后,它会将这个数据项写入到页面中,并将其对应的行指针添加到页面头部。 当一个事务要读取一个数据项时,它会根据自己的事务ID和隔离级别,选择一个合适的版本来读取。具体来说,它会遍历页面中的所有行指针,根据每个行指针指向的数据项的事务ID和ctid,判断是否满足以下条件:
1.该数据项的事务ID小于等于当前事务的快照号(snapshot number),表示该数据项是在当前事务开始之前就已经存在的。
2.该数据项没有被其他事务删除或更新,或者被删除或更新的事务已经提交或回滚。这可以通过检查该数据项的xmax字段来判断,如果xmax为零或者对应的事务已经结束,则表示该数据项没有被删除或更新。
3.该数据项是页面中所有满足上述条件的数据项中ctid最大的一个,表示该数据项是最新的版本。
- 当一个事务要更新或删除一个数据项时,它会创建一个新的版本,并把自己的事务ID和新的ctid作为新版本的标识。同时,它会将原来的版本的xmax字段设置为自己的事务ID,表示原来的版本已经被删除或更新。然后,它会将新版本写入到页面中,并将其对应的行指针添加到页面头部。
- 当一个事务要提交或回滚时,它会将自己的事务ID写入到全局提交日志(global commit log)中,并将自己持有的锁释放。这样,其他事务就可以根据全局提交日志来判断某个事务是否已经结束,并根据相应的逻辑处理相关的数据项
六、小结
本次以存储引擎为入口展开,对其下的行存储引擎做简单概述。接下来将会先了解磁盘引擎的访存方式,然后对行存储引擎的UP-INDATE设计方式和MVCC技术做深入学习。
新手上路,如有不足还请指正。