
搞国产数据库内核的人多少都经历过这种时刻一条本该毫秒级返回的SQL在生产环境里硬生生跑了十几秒或者一主一备切换后明明日志显示切换成功客户端却连着重试了半分钟。翻遍MySQL、Oracle的教科书式排查手册总感觉差口气——因为问题根本不在应用层而在内核层的架构设计上。这两年核心架构这个词被反复提起尤其是国产数据库从能用走向好用内核设计成了绕不开的关口。网上的资料要么是官方文档的复读要么是PPT式的架构图真正把内核拆开讲清楚的文章不多。这篇内容想做的就是这件事从SQL执行旅程、存储引擎选型、分布式事务、存算分离架构到高可用设计再落到真实性能调优把国产数据库内核的底牌翻开来看看。适合正在做数据库选型评估的人、刚转国产库的DBA以及想搞明白内核到底干了啥的开发同学。1. 从一条SQL的旅程说起内核到底管了哪些事很多人把数据库内核想得很玄实际上一条SQL从客户端发出去到最后拿到结果经过的路径非常固定只是每家的实现细节和工程取舍不一样。1.1 解析层不只是检查语法这么简单相错误以为解析层就是语法对不对、表存不存在这种外围工作。真正的解析器要做的是把SQL文本转成抽象语法树这一步看似简单但里头坑不少方言适配国产数据库为了降低迁移成本普遍要兼容MySQL或Oracle的SQL方言。同一个LIMIT子句在Oracle风格里是ROWNUM在MySQL风格里是LIMIT off, cnt。解析器要同时维护多套语法产生的AST结构后续优化器才能统一处理。这直接决定了你从Oracle迁过来时存储过程、触发器这些能不能少改代码。参数化与软解析很多人在排查数据库CPU高企时发现硬解析风暴就是每条SQL文本略有不同导致每次都要重新解析。国产内核普遍会做归一化处理把WHERE id 123和WHERE id 456归一成同一条模板SQL命中缓存就直接走软解析。这块做得好不好直接关系到高并发下CPU消耗差一倍还是两倍。一个没做参数化硬顶的生产案例我记得特别清楚。某客户的业务是物联网上报SQL拼接里带了设备ID和毫秒时间戳一秒几千条新SQL进来解析器直接把CPU打满业务全部堆积。后来在数据库前面统一改了PreparedStatement风格问题立刻缓解。做内核的兄弟看到这种现场只能说解析这层远没有想象中简单。1.2 优化器国产内核最见功力的地方AST生成之后真正决定SQL快慢的是优化器。MySQL的优化器是典型的RBOCBO混血规则多统计信息靠show index里的基数估算Oracle的优化器是CBO的集大成者但它的统计信息体系太复杂。国产数据库的优化器路线基本分成三派基于PostgreSQL内核演进的比如openGauss系继承了PG的CBO框架代价模型相对成熟对复杂关联查询的支持更好兼容MySQL语法但自研优化器的比如OceanBase、TiDB这类对分布式场景的代价模型做了针对性设计要额外考虑数据分布和网络传输代价面向分析型场景的列存引擎优化器会更激进地做谓词下推、向量化执行、延迟物化甚至基于Runtime Filter做动态剪枝。真正要评估优化器强不强别看官方TPC-H的跑分自己造两套数据表数据分布极度倾斜比如订单表按城市分布80%的订单集中在三个城市多表关联且关联列的可选择性很差比如性别字段。然后把同样的慢SQL在目标数据库上开EXPLAIN看它是走了哈希连接还是嵌套循环有没有做分区裁剪统计信息是不是严重失真。我见过不少号称兼容Oracle优化器行为的国产库在复杂子查询上直接选择了一条坏路径原因就是子查询折叠和等价改写做得不到家。1.3 执行引擎向量化是国产库的分水岭优化器给出执行计划后执行引擎开始干活。老派数据库的执行模型是火山模型——每行数据逐级向上传递一个简单的全表扫描加过滤加聚合要经过好几层虚函数调用一行行处理。这个模型实现简单、容易维护但效率确实不行。现代数据库内核都在搞向量化执行一次处理一批数据比如一次取1024行在CPU缓存里做循环列式内存布局配合SIMD指令性能差距可以到3到10倍。国产数据库在这块的差异化非常明显。分析型场景的库基本全向量化OLTP类数据库这几年也在查漏补缺比如对select sum(amount) from orders where status done这种高频统计型SQL做向量化加速。但这不意味着所有SQL都能收益——如果你的业务全是单行点查和短事务向量化反而因为批处理装配开销导致响应时间变长。选型时要看业务负载里是扫描型SQL多还是点查多不是参数越先进越好。2. 存储引擎选型的分岔口B树、LSM与混合路线存储引擎是内核的地基。执行引擎算得再好数据落不下去或者读不出来都是白搭。国产数据库在存储引擎上基本走三条路各自对应不同的业务画像。2.1 B树阵营传统OLTP的主场B树结构大家都熟叶子节点有序链表、非叶子节点做索引、三层到四层就能覆盖千万级数据。优点很突出范围查询效率高点查稳定事务并发控制相对好做。国产库里的openGauss默认行存引擎、达梦的DM8、人大金仓的KingbaseES整体思路都是B树加多版本并发控制。这套路线的核心挑战在于缓冲池管理。B树的读路径依赖内存命中率如果缓冲池太小或者淘汰策略不好磁盘随机读会拖垮一切。国产内核在这块有一些共性设计预读机制顺序扫描时提前把后续页面加载到缓冲池LRU变体区分冷热页防止一次全表扫描把热数据全部冲刷掉脏页刷盘策略用后台线程分批刷避免集中刷盘造成的IO尖刺。我记得测试某国产库时连续两次跑select count(*)第一次10秒第二次0.8秒这1秒多的差距就是缓冲池命中率从0到90%带来的。2.2 LSM-Tree阵营写密集场景的解药LSM的核心思路是顺序写换随机读。数据先写进内存里的MemTable达到阈值后冻结成不可变的SSTable再通过后台合并Compaction逐层下沉。写入路径从磁盘随机写变成内存写加顺序写吞吐量自然就上来了。OceanBase的存储引擎就是这个路线配合它的多副本Paxos日志同步写入性能非常夸张。但LSM的代价全在读路径上。一个键的值可能分散在多层SSTable里点查在最坏情况下要给每层都发一轮查。内核用布隆过滤器来快速判断这层肯定没有能挡掉大部分无效IO。看一个LSM引擎做得好不好直接看三方指标读放大一次点查实际读了多少数据写放大一条写入实际触发了多少后台写入空间放大实际占用磁盘比逻辑数据大多少。这三个指标互相制约压缩率高的算法往往拖慢Compaction层数多则读放大严重而写放大缓解。调优时最典型就是write_buffer_size设置过大导致内存紧张、Compaction频繁触发或者max_background_jobs太小导致写放大积压后台任务追不上业务写入。2.3 混合模式与云原生存储现在越来越多国产库在做行列混合。一张表可以同时有行存格式支撑高频点查也有列存格式支撑分析类SQL由优化器根据查询模式决定走哪套数据。这听起来很美好实际落地时最大的坑是数据一致性——行存和列存是同一份数据的两种形态写入时要同步维护列存构建或合并时的锁和IO开销很容易成为瓶颈。很多项目最终退化成白天用行存凌晨定时把增量同步到列存因为实时同步代价实在不划算。至于云原生存储本质是把数据页放到对象存储或分布式块存储上本地只留缓存。这让计算节点随时重启、存储不丢数据成为可能。但网络延迟和带宽是硬伤所以内核层必须做自适应缓存淘汰和批量预取。国内不少云数据库已经在走这条路读多写少、弹性扩缩容要求的场景值得优先看这类架构。3. 分布式事务与一致性协议两阶段提交之外的国产化探索单机数据库的事务好做靠锁和日志就能搞定。一旦把数据库拆成多个节点事务就变成了分布式系统问题。这块是国产数据库内核和传统单机数据库差异最大的地方也是踩坑的重灾区。3.1 二阶段提交能用但不好用经典的两阶段提交协调者先问所有参与者能不能提交大家都说能再广播提交。问题在于协调者单点、参与者长时间阻塞、网络分区时可能脑裂。很多国产分布式数据库在早期版本直接挪用2PC结果就是分布式事务性能差、故障恢复复杂。拿一个简单转账场景举例应用发起跨节点事务涉及A节点的扣款和B节点的入账。2PC在第一个阶段要锁定两个节点的资源如果B节点响应慢整个事务悬在那A节点的行锁一直不释放。并发稍微一上来锁等待和死锁检测就能把数据库拖垮。生产环境里我见过分布式事务占比不到5%、却消耗了30%以上数据库资源的情况。3.2 Percy化的改进时间戳排序和OCC后来大家发现与其靠锁去协调不如把冲突检测后置。流行的一种做法是模拟Google Percolator的思路全局分配单调递增的时间戳TSO事务在提交时向TSO取一个提交时间戳所有读写操作都基于MVCC的快照隔离。这样读写不阻塞只在提交阶段检查写写冲突冲突就让事务回滚重试。OceanBase、TiDB在早期路线里都有这种影子。TSO服务本身又变成新的单点所以国产库普遍会把TSO做成高可用集群比如三节点Paxos。这又引出一个经典问题TSO的分配延迟直接影响事务延迟。为了降低这个延迟时间戳分配器会一次预分配一批但批量分配会造成事务可见性缺口——一个事务明明提交在前取到的时间戳却比后提交的事务还大导致后者看不到前者的数据。东八区的工程师们包括国内团队特别喜欢在时间戳上做文章比如混合逻辑时钟HLC、原子钟方案用物理时钟加逻辑计数器的方式避免全局协调。物理时钟要真准跨机房部署时还得靠硬件。这种方案的好处是事务提交不用跨节点拿时间戳写入路径少一次RTT但时钟漂移很难彻底消除工程上需要大量对冲策略。3.3 一致性协议Raft和Paxos的实际抉择分布式数据库的最大卖点就是任意节点挂了数据不丢业务不中断这背后靠的是共识协议。Paxos理论上完备工程实现极难Raft容易理解成为国产库主流选择。但具体到架构里有一个关键差异日志复制和数据副本绑定每个分片Tablet/Region自己玩一套Raft组。写入要经过Leader日志复制到多数派返回成功后才算提交。这套逻辑每个分片独立保证了强一致但跨分片事务依然需要额外协调事务延迟直接叠加了两跳RTT。日志复制和存储分离计算节点只写日志存储节点负责把日志应用到数据副本。日志本身就是共识层节点挂了直接拉日志重建状态机。这套架构的优势是计算无状态弹性伸缩快。这两种路线直接决定了故障切换的RTO。前者因为数据副本和日志复制绑定在同一节点切换时新Leader要尽量把旧Leader的日志同步完极端情况要等磁盘刷盘或网络分区恢复RTO不稳定。后者因为存储层本身就冗余了数据计算节点切换基本是秒级。4. 计算存储分离的架构权衡从Scale-Up到Scale-Out过去的数据库架构是典型的一台大机器包办一切算力、内存、存储全绑在一台物理机上一旦资源不够只能垂直扩容换更大的机器。国产数据库在云原生大趋势下普遍在往计算存储分离走。这从内核角度看是个大手术不是简单把存储换成云盘就行。4.1 为什么一定要分离计算和存储分离解决的不是慢的问题而是扩容不灵活和成本的问题。对一个单库容量10TB的OLTP业务如果存储和计算绑死为了多出来的2TB容量要买一颗更高的CPU等于给不需要的计算能力付钱。分离之后存储节点和计算节点独立伸缩容量扩容只加存储节点计算资源不够只加计算节点这在云上尤其划算。内核要做的配套改造不少数据页缓存管理计算节点原来靠缓冲池扛热点分离后缓冲池变成本地缓存缓存未命中时要走网络拉数据页缓存命中率直接变成第一敏感指标日志和检查点机制数据页写入路径从落本地盘变成落共享存储顺序写优势大幅削弱内核需要重新设计日志批量提交和异步刷盘逻辑元数据管理表结构、分区信息、权限这些元数据原来放本地现在要做成全局元数据服务而且要保证多计算节点看到的元数据版本一致。4.2 缓存一致性的老大难存算分离架构最考验内核的地方是共享数据页的一致性。两个计算节点同时修改同一份数据的不同页或者一个节点修改了页另一个节点还在用旧缓存页都会出问题。常见方案是牺牲一点实时性用租约的方式让缓存页有有效期到期必须重新向存储确认版本。但租约太短会让缓存命中率下降、网络请求暴涨租约太长又会让数据更新延迟变大。实际调优时要注意一个参数失效缓存的时间窗口。我们做过一组对照测试租约从30秒调到2秒写入链路的延迟上升了大约40%但读多写少的报表类查询受到的影响就很小。所以没有通解只能根据业务特性调。写多读少的场景不适合激进短租约读多写少的场景可以适当缩短因为在读多场景下缓存数据快速失效其实代价不高反而保证了新数据的可见性。4.3 数据重分布和扩缩容的内核处理计算存储分离之后数据重分布、扩缩容的瓶颈从搬数据变成改元数据。以前加一个节点要把一部分数据分片搬过去搬的时候要卡住写入来保证快照一致这个窗口期极长。现在很多国产库可以改元数据、做逻辑迁移数据实际还是躺在共享存储上只是归属关系变了迁移成本低很多。但由此带来一个新问题热点分片。数据归属关系可以灵活调整但一张大表的分片在共享存储上的访问热度可能极度不均某个分片被高频访问计算节点压力就上来了其他分片空闲。光靠元数据切换解决不了物理热点还是要配合业务层面的拆分策略比如把订单表按时间分区而不是按哈希分片。做架构设计时得分清哪些问题让内核解决哪些问题应该让业务模型规避。5. 高可用与容灾的内核级设计RPO/RTO不是口号谈到数据库高可用是永远绕不开的话题。国产数据库在核心系统落地时客户问的第一个问题往往是机房断电怎么办服务器坏了怎么办数据能丢多少业务多久能恢复这些问题的答案不在外围的运维脚本里而在内核的高可用设计里。5.1 主备复制从异步到同步的代价单机高可用的基本盘是主备复制。异步复制性能好备库落后主库的数据可能从几毫秒到几秒之差。半同步复制稍微安全一点主库等备库把binlog/redo日志刷盘成功再返回事务提交但备库落盘和实际应用日志之间还有时间差。全同步复制最安全但每个事务都要等备库应用完数据网络往返延迟全部叠加吞吐量损失肉眼可见。国产数据库在工程上普遍做的是组复制改良版主库写入日志同步给多数派节点就算提交成功不必等所有备库都确认。这样在保证RPO接近零的同时把延迟控制在一次或两次RTT内。但这里有个隐含条件一旦集群进入少数派存活状态比如三节点坏了两个剩下的一个节点是没有办法自己形成多数派的整个集群会拒绝服务。这个机制保护了数据一致性但也牺牲了可用性——极端情况下你宁可让业务停也不能让数据错架构上必须清楚这个取舍。5.2 脑裂问题的内核防线高可用架构最怕脑裂分区两边的节点都认为自己是主同时接受写入数据从此分叉。传统数据库靠仲裁盘和心跳来尽量避免分布式数据库则靠共识协议天然防脑裂——Raft里只有拿到多数派选票的节点能成为Leader网络分区时少数派那边的Leader会自动降级。但这不代表高枕无忧。如果应用层配置错误比如负载均衡把写流量同时打到了新旧两个主节点即使内核层没有脑裂应用层也制造了伪脑裂。很多国产数据库集群异常都源于这类配置疏忽而不是协议本身有问题。排查时第一件事就是列出所有节点的角色看看有没有两个Leader并存然后再看监控里的选主时间和配网状态不要一上来就怀疑内核。5.3 切换后的隐藏问题连接池、序列和会话状态我见过太多主备切换成功但业务故障持续十分钟的案例问题根本不在切换本身而在切换后恢复的隐藏环节应用连接池还握着指向旧主库的连接新主库已经上线但连接池没感知所有请求还在往黑洞里发。这需要数据库proxy层或应用连接池配置里的优雅重连机制配合而很多国产库的兼容协议在这块有差异全局自增序列在主备切换后出现跳号或单调性中断。如果内核没有对序列状态做持久化和同步切到备库后序列可能从旧值重新开始导致主键冲突业务直接被唯一键约束挡住未提交事务的悬挂锁。主节点挂了它上面未提交事务持有的锁不会天然消失新主库通过日志恢复时如果逻辑没处理干净这些锁可能残留很久导致相关表的写入全部卡住。做国产数据库容灾演练除了测切换了多少秒一定要压测切换完成后的前5分钟否则RTO指标好看实际恢复体验很差。6. 真实部署中的性能瓶颈与调优经验前几节讲的都是架构层面最后落到实践。再牛的内核参数配不对、业务模式不适配照样跑不出性能。这里分享几个我实际调优遇到的典型场景。6.1 执行计划突变统计信息是罪魁祸首很多人在国产库上遇到昨天还快今天突然慢的诡异问题大概率是统计信息过期导致优化器选了坏计划。国产库普遍相信统计信息如果表数据从10万行涨到1亿行而统计信息没刷新优化器还按10万行的基数估算关联查询直接选择嵌套循环等于灾难。调优动作很简单也很有用定期调用统计信息更新别等凌晨大窗口分表分区域增量更新如果优化器还是选错直接看EXPLAIN里每个算子的预估行数和实际行数的差值差距超过两个数量级就是统计信息的问题不得已时可以手工绑定执行计划但只能当短期止血方案长期还靠统计信息维护。6.2 热点行更新导致的锁等待雪崩分布式数据库的并发更新一个热点行所有事务都要抢同一行的行锁等待队列一长数据库的活跃会话全部堆积。内核层的死锁检测器能发现问题但等它反应过来业务已经超时了一遍。缓解手段是疏散热点。比如计数器类的业务不要把数量全放在一行里可以拆成N个槽位每次随机选一个槽位更新读时求和。这个改动应用层只动几行代码但对内核的并发压力是数量级的下降。国产数据库很多都支持分区表分区裁剪来降低锁粒度但根本解法还是避免单点极热的数据模型。6.3 刷盘与IO延迟redo log的隐藏瓶颈很多慢事务的根子在日志刷盘。事务提交前redo日志必须刷到磁盘如果磁盘延迟大每个事务提交都在等IO。内核提供了组提交机制多个事务的日志在内存里攒一批一次性刷盘延迟均摊。这个机制能不能生效取决于业务的并发度——并发太低时组提交基本用不上并发高时效果很明显。我们压测过一组数据在相同磁盘条件下单线程点查写入TPS约350050并发时组提交效果显现TPS可以冲到8000到12000。所以如果你在生产环境发现并发上去了TPS反而跌了先看是不是redo响应的组提交没有打开这比加内存、换SSD更省钱也更见效。6.4 关于参数优化的几个实在建议不要上来就按网上流传的万能调优配置文件操作每套参数都要配合业务负载压测验证内存参数不是越大越好给缓冲池分配太多内存留给排序、哈希连接、事务日志的内存可能反而不够导致大查询频繁落盘连接数不是越多越好线程切换成本在高并发时极高连接池配到活跃会话的两倍左右往往最优在对国产库做调优之前一定要先确认版本。同一个参数在不同版本里的默认值和行为可能完全不一样很多过时博客的结论会害死人。踩过几次国产库性能问题之后我最大的体会是内核架构决定了性能天花板但实际能摸到多高取决于你对业务负载的理解和对参数的敬畏程度。这套东西没有银弹只有老老实实压测、观察、调参、再看执行计划的循环。希望这篇拆解能帮你在面对国产数据库内核时少走点弯路无论是选型还是排查都能从架构层面多一份底气。