
从去年年底开始我把openGauss的源码当成了主要研究对象。这篇是系列文章的第二篇继续把openGauss这个数据库的底子摸一遍讲清楚它的核心特性、整体架构、源码目录和编译体验算是给后面的源码解析铺路。上一篇讲了openGauss的来源和基本定位这一篇更聚焦适合已经对openGauss有初步了解、但又不知道从哪里下手读源码的开发者。openGauss数据库源码解析系列文章——openGauss简介下很多人第一次接触openGauss时会先看官方宣传里的高性能、高可用、高安全、高智能四个词看完也就过去了。我研究源码这段时间最大的感受是这四个词不只是市场话术每一项背后都有对应的代码模块在支撑。这篇会把它们逐一拆开告诉你在源码里能去哪里找到对应实现然后再带你过一遍openGauss的整体架构和源码目录让后续读代码时不至于迷路。1. 核心特性逐条拆解每个关键词背后都有代码1.1 高性能背后从表结构到执行引擎的联动先说说高性能。openGauss的高性能不是靠某一个单一优化做到的而是从存储、执行、调度三个层面一起堆出来的。存储层面默认的Astore行存引擎在PostgreSQL的heap存储基础上做了不少改造。我看代码时印象最深的是它对批量写入和延迟清理的处理。传统行存储在频繁更新时会积累大量旧版本数据需要定期vacuum而openGauss的Astore引入了批量和延迟的清理策略减少了大事务场景下的I/O抖动。这个逻辑对应在存储引擎的页面管理和版本管理模块里后面讲存储引擎时我会专门展开。执行层面的关键是列存和向量化。Cstore列存引擎按列组织数据配合批量压缩在做分析类查询时能大幅减少需要扫描的数据量。更重要的是向量化执行CPU一次处理一批数据而不是一行数据这对聚合、过滤这类操作特别友好。我实际跑过几组十万到百万行级别的查询列存加向量化在聚合场景下比普通行存快一到两个数量级这个差距不是玄学是指令集和数据布局直接决定的。调度层面的并行框架也值得单独说。openGauss的并行查询不是简单地把一个任务拆成几个线程而是从并行扫描、并行聚合到并行排序一套完整的体系。查询规划阶段会根据表大小、可用资源等因素决定是否走并行计划并行的度也是动态计算的。源码里对应的是优化器和执行器里的并行算子的实现这块代码量不小但逻辑很清晰。日常增删改查SELECT/INSERT/UPDATE/DELETE的路径其实差异很大。SELECT会走到查询优化和计划执行INSERT、UPDATE、DELETE则要处理版本链、索引维护、WAL日志等逻辑。在源码里这些操作最终会汇聚到存储引擎的接口上理解这条路径后面看什么模块都顺。1.2 高可用主备复制和容灾设计高可用方面openGauss的主备复制是典型的物理流复制思路和PostgreSQL的流复制在概念上相近但实现细节上有自己的取舍。复制模式支持同步、异步和quorum多数派确认三种。同步复制的特点是主库提交事务时必须等到备库确认收到日志后才返回成功数据一致性最高但性能会有额外开销。异步复制的性能好但故障切换时可能丢数据。quorum模式则是在多个备库之间定一个最小确认数比如三个备库中至少两个确认才提交兼顾了一致性和可用性。这个机制在源码里对应的是日志发送和接收模块主库端是walsender备库端是walreceiver两者通过复制协议通信。openGauss的备库是可读的。也就是说备库在应用日志的同时还能接受只读查询。这个能力对读写分离场景非常有用但实现上有个麻烦备库应用日志和查询并发时如何保证查询看到一致快照。源码里对这部分做了专门处理把日志应用和查询请求做了合理的调度。故障恢复方面openGauss支持日志的并行回放备库在apply日志时可以多线程并行处理不同范围的WAL记录大大缩短了故障切换后的恢复时间。我在环境里模拟过主库宕机再切备的流程几百MB的WAL日志回放只需要几十秒这个速度对生产环境很重要。如果你用过第三方数据库同步软件比如基于逻辑解析的同步工具会发现openGauss在这块的思路是物理复制优先逻辑复制能力也在逐步补充。物理复制的好处是性能高、实现简单坏处是异构数据库之间没法互通。这也是为什么很多同步场景还是要靠逻辑日志解析来完成。1.3 高安全全密态、脱敏和权限体系openGauss的高安全特性是我觉得最值得深入研究的点因为它涉及的不是简单的权限管理而是数据进到数据库之后数据库管理员也看不到明文这种级别的设计。全密态等值查询是核心。它的思路是数据在客户端使用密钥加密后以密文形式发送给数据库数据库端存储和计算的都是密文。查询等值条件时由于采用的是确定性加密算法相同明文加密后的密文也相同所以数据库可以直接在密文上做等值比较返回结果后在客户端解密。服务端从头到尾接触不到明文数据。要理解这个机制可以去看安全模块里的加解密实现代码里能看到加密算法、密钥管理和密文比较的完整链路。动态数据脱敏做得也很细。它不是在应用层做脱敏而是在数据库内部根据用户角色和脱敏策略对查询结果实时处理。比如普通用户查身份证号看到的是中间几位打码的版本而有权用户看到的是完整数据。这个功能对应的源码会在查询执行的返回结果阶段介入对输出的行数据进行脱敏变换。权限体系上openGauss引入了三权分立的思路把传统数据库管理员的权限拆分给了系统管理员、安全管理员和审计管理员三个角色互相制约。加上它对国密算法SM2/SM3/SM4的支持在需要合规的政企场景里确实很能打。1.4 高智能数据库里长了AIAI和数据库结合是openGauss的一个鲜明标签。AI4DB指的是用AI技术来优化数据库自身比如参数自调优、索引推荐、SQL诊断等。传统的DBA调参靠经验而openGauss可以通过采集运行时的指标数据建立模型来推荐更合适的参数组合。我试过它的索引推荐功能在特定业务负载下推荐的索引确实比我自己拍的合理。DB4AI则反过来把机器学习算法跑在数据库内部。它的价值在于数据不需要导出到外部训练环境直接在SQL语句里就能完成模型训练和推理。语法上提供了类似SELECT * FROM train_model(...)这样的调用方式。这个特性在源码里对应一套内置的算法库和SQL执行引擎做了深度融合。智能特性对运维人员很友好但说实话这部分源码的复杂度比传统数据库模块高不少因为涉及算法实现和数据库内核的交叉。入门阶段可以先把它当黑盒理解后续再深入也不迟。2. openGauss整体架构先建地图再进源码2.1 从客户端到存储的层层递进openGauss是典型的客户端/服务器架构服务端采用多进程模型。这种模型和MySQL的线程模型区别很大最初从MySQL转过来的人容易不适应。用一个餐厅来类比主进程gaussdb是大堂经理负责接收客人连接请求、安排桌位、调度整体运营。每个连接到来时主进程会fork一个后端进程相当于给客人安排一个专属服务员这个服务员独立处理该连接的全部SQL请求。辅助进程则像后厨团队有专门负责写日志的、有负责刷脏页的、有负责检查点的各司其职。理解了这个模型你就知道为什么openGauss的连接池比MySQL更重要了。因为每个连接对应一个进程连接多了进程数暴涨内存开销不小。这也是为什么生产环境里通常会配一层中间件连接池把前端连接收敛到少数几个后端连接。一条SQL从客户端到返回结果的完整路径是解析Parser→ 分析Analyzer→ 重写Rewriter→ 规划Planner→ 执行Executor→ 存储引擎Storage。解析器把SQL文本变成抽象语法树分析器结合系统表做语义检查生成查询树重写器处理视图和规则规划器决定怎么查最快生成执行计划执行器按照计划一步步执行最终落到存储引擎读写数据。这套流程是数据库内核的骨架也是任何数据库源码阅读者必须先建的地图。2.2 三种存储引擎怎么选openGauss的存储引擎不是单一实现而是支持Astore、Cstore、Ustore三种各有适用场景。Astore是默认的行存储引擎核心逻辑继承自PostgreSQL的heap存储做了不少优化。它的特点是追加写插入性能好但在频繁更新场景下旧版本数据膨胀是个问题需要依赖清理机制回收空间。Cstore是列存储引擎数据按列存放适合OLAP分析类负载。我在前面提到过列存的优势在于查询只需要读取涉及的列配合压缩和向量化执行分析性能提升非常明显。Ustore则是原地更新的存储引擎思路和MySQL InnoDB的undo机制有点像。更新时直接在原位置修改数据旧版本通过undo链管理。这种设计避免了Astore在频繁更新场景下的索引维护开销和表膨胀问题。对于更新密集型的业务Ustore有天然优势。三种引擎的选择直接影响后面的SQL性能和源码阅读方向。建议你上手时先用默认的Astore跑通流程再逐个切换引擎对比观察这样能把存储引擎的差异理解得更透。2.3 和PostgreSQL到底什么关系openGauss的内核源头可以追溯到PostgreSQL 9.2.4后来逐步形成了自己独立的体系。这个背景决定了它的源码里有很多PostgreSQL的影子但又不完全是PostgreSQL。具体来说语法解析、查询规划这些上层模块保留了PostgreSQL的基本框架所以我之前把PostgreSQL源码翻过一遍后再看openGauss的解析器和规划器会有很强的既视感。但往下层走存储引擎、执行器、安全模块、高可用模块这些关键部分openGauss做了大量重写和增强看代码时你会发现很多函数名的前缀、结构体定义和PostgreSQL已经差异很大了。还有一个值得注意的点是目录结构。openGauss把核心内核代码集中放在了src/gausskernel目录下和传统PostgreSQL的src/backend布局不完全一样。我最初按PostgreSQL的目录习惯去找代码绕了不少弯路。后面第三节会专门讲目录。对读者来说懂PostgreSQL确实是读openGauss源码的加分项但不是前置条件。不懂PostgreSQL直接从openGauss本身入手也完全可以只是刚开始看代码时会有一些为什么这么设计的疑问这些疑问往往能在PostgreSQL的历史里找到答案。3. 源码目录导读别被代码量吓到3.1 src/gausskernel核心目录速查openGauss整个源码工程的代码量很大几十万行级别。如果按文件一个个读不现实也没必要。正确做法是先看目录结构知道每个模块在哪儿然后按图索骥。我把src/gausskernel下最核心的几个目录整理成了一张速查表方便你对照着找代码目录职责对应学习优先级parserSQL语法解析把文本变成语法树高optimizer查询优化生成执行计划高executor执行计划并返回结果高storage存储引擎页面、版本、空间管理高access索引和访问方法B-tree、hash等中tcop顶层控制连接和命令处理高commandsSQL命令的具体实现如CREATE TABLE中catalog系统表管理中communication客户端通信协议和线程模型中utils公共工具函数内存、字符串、排序等低用到再查这张表只是给你一个导航。实际读代码时你不需要按顺序从第一个目录读到最后一个而是应该带着问题去找对应目录。比如你好奇一条SELECT语句是怎么被规划的就直奔optimizer想知道数据最终是怎么落盘的就钻进storage。3.2 我推荐的第一条阅读路线最推荐的第一条阅读路线是跟一条最简单的查询语句走完它的全生命周期。你可以从客户端输入SQL开始一路跟踪到结果返回这样一圈下来整个数据库的骨架就搭起来了。具体来说先从tcop目录入手找到连接建立和命令处理的入口。大部分查询会经过类似exec_simple_query这样的处理函数这个函数名从PostgreSQL继承而来openGauss里做了演化。它会调用parser做语法解析然后送到优化器生成计划再由执行器跑计划最后把结果返回给客户端。在这条路线里你不需要理解每个函数的每个细节只需要知道每个阶段在做什么、输入是什么、输出是什么。我当初就是靠这个方法用了大概两周的时间把一条SQL从文本到结果的完整路径在代码层面走通了。走通之后再回来看其他模块就有了上下文不再是一头雾水。强烈建议在读代码的过程中自己画一张流程图标注出每个阶段的关键函数和数据结构。等你把这张图完整画出来你就已经具备给同事讲openGauss查询流程的能力了。3.3 准备一套顺手的调试环境读源码不能只靠眼睛看一定要配合调试工具。我的建议是准备一台8核16G以上内存的Linux机器最好是CentOS 7.6以上或openEuler磁盘至少预留30G空间。openGauss的编译对依赖库有要求编译前需要装好gcc、g、make、flex、bison、readline-devel、libaio-devel等软件包。装齐之后编译本身不算复杂但第一次编译耗时较长耐心等就行。调试时由于openGauss是多进程模型每个后端进程对应一个连接你用gdb调试时得先找到目标进程的PID然后gdb attach上去再设置断点。如果你用的是VSCode也可以配置codelldb进行远程调试体验会好很多。日志方面openGauss的日志输出逻辑很完整必要的时候可以在源码里临时加打印日志重新编译后再跑这种笨办法在追踪复杂问题时反而最有效。4. 编译源码并跑起来从代码到可执行文件4.1 下载源码和编译的完整步骤在开始读代码之前我建议你先把openGauss源码编译一遍让整个数据库在自己的机器上跑起来。没有运行环境支撑很多代码细节看了也记不住。源码可以从Gitee上的openGauss仓库获取社区版本一直在更新下载时注意选择稳定分支。获取方式很简单git clone https://gitee.com/opengauss/openGauss.git编译前先确认依赖都装好了。以CentOS为例核心的几个包有gcc、gcc-c、make、flex、bison、readline-devel、libaio-devel、ncurses-devel。缺少哪个就通过yum安装。然后进入源码目录执行编译脚本chmod x build.sh ./build.sh -p /opt/openGauss -d其中-p指定安装路径-d表示编译Debug版本方便后续调试。编译过程可能需要几十分钟到数小时不等取决于机器配置。编译完成后openGauss的可执行文件会安装在指定目录的bin子目录下。这块我踩过的坑是依赖版本不匹配。比如flex版本太老或太新都可能导致语法解析器生成失败报错信息还比较隐蔽。如果你遇到奇怪的编译错误先检查依赖版本是不是符合官方文档的要求。4.2 初始化和连接让数据库先跑起来编译完成后需要初始化一个数据目录。openGauss提供了gs_initdb工具参数和PostgreSQL的initdb略有差异通常需要指定节点名单机环境一般用single和数据库超级用户的密码。以下是初始化数据目录、启动服务和连接数据库的完整流程# 初始化数据目录节点名单机环境用single gs_initdb -D /data/openGauss/data --nodenamesingle -w YourPassword # 启动数据库 gs_ctl start -D /data/openGauss/data # 连接数据库默认端口5432 gsql -d postgres -p 5432 -U gaussdb连接成功后你就拥有一个本地运行的openGauss实例了。这时候再去看代码任何疑问都可以拿实际环境来验证。比如修改一个存储引擎参数重启数据库看看行为是否和代码里分析的一致。这种代码-运行-验证的循环是读内核代码最有效的学习方式。4.3 初读源码的三个误区第一个误区是一头扎进存储引擎。存储引擎是数据库最底层的部分涉及页面布局、缓冲区管理、WAL日志等大量概念没有全局视角直接钻进去很容易被细节淹没过几天就放弃了。正确的顺序是先看查询链路再回头看存储。第二个误区是不看官方文档和架构资料硬啃代码。openGauss官方有详细的文档包括内核架构、SQL引擎、存储引擎、安全等章节读源码前先过一遍文档能帮你减少很多这个设计是为什么的困惑。第三个误区是只读不跑。很多人在读代码时不动手看过就忘。我自己的经验是每研究一个模块都去实际环境里做一次对应的操作然后回代码里看这次操作涉及哪些函数。比如你想弄懂存储过程就去写几个存储过程跑一跑再回来看plpgsql相关模块的代码。只有这样代码才不只是抽象的符号。5. 常见问题与排查技巧实录5.1 编译期问题速查表我帮不少朋友排查过openGauss编译问题下面这几个是最常见的整理出来供你参考报错特征常见原因解决办法flex/bison相关错误flex或bison版本不兼容安装官方文档指定的版本后重试缺少头文件如readline.h未安装readline-develyum install readline-devel链接阶段报undefined reference缺少libaio等库安装libaio-devel后重新make编译很慢甚至卡住内存不足或磁盘空间不够增加swap或清理磁盘空间代码格式化校验失败本地工具链版本太新按官方README要求切换gcc版本这些问题的本质多半是环境差异。社区文档和issue区里都能找到对应记录遇到问题先搜issue很多时候别人已经踩过坑了。5.2 运行期排查思路数据库跑起来之后真正麻烦的是运行期问题。这里分享两个高频场景的排查思路。第一个是连接失败。先看数据库日志openGauss的日志默认在数据目录下的pg_log文件夹里通过日志定位是认证失败、端口冲突还是资源限制。认证失败时检查密码策略和pg_hba.confopenGauss里对应的鉴权配置文件里的认证方式是否匹配端口冲突就换端口资源限制则检查系统进程数和文件句柄数上限。第二个是死锁问题。日常开发中数据库死锁这个词出现的频率很高定位方法不复杂。openGauss提供了锁相关的系统视图通过查询pg_locks这类视图可以看到当前持有的锁和等待的锁。遇到死锁时先看报错日志里锁冲突的双方是谁然后回到业务SQL分析两个事务加锁的顺序把顺序调成一致即可。我在写存储过程时就踩过死锁的坑两个过程互相更新对方的资源后来通过固定加锁顺序解决了现在写复杂事务都会先过一遍加锁顺序。5.3 我的一些避坑心得读openGauss源码这段时间我自己最大的几个收获都来自踩坑之后的反省。第一gdb调试多进程程序时不要直接gdb gaussdb启动这样只会调试主进程。要先用ps -ef | grep gaussdb找到目标连接的postgres后端进程PID然后gdb attach上去。注意后端进程可能是临时fork出来的调试前最好在业务侧保持连接不断开。第二openGauss的日志系统很强大但默认日志级别不一定够用。排查问题时可以临时把日志级别调高甚至在目标位置临时加elog(LOG, ...)这类日志输出语句重新编译。虽然笨但效率极高。第三读代码时遇到宏定义不要跳过。openGauss的代码风格偏底层大量使用宏来做抽象很多看似古怪的写法其实是宏展开后的结果。用gcc的-E选项或者IDE的宏展开功能把它们展开成完整代码再看能少走很多弯路。这套环境你花上一周时间搭建好后面所有源码分析才有落地的根基。不要觉得准备环境浪费时间在数据库这类底层系统里动手能力本来就是一半的功力。