ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

自研数据库选型与迁移:内核自研、生态兼容与场景化架构

自研数据库选型与迁移:内核自研、生态兼容与场景化架构

数据库这个圈子里,真正愿意把架构主张讲清楚的人不多。多数场合听到的还是功能清单、性能倍数和一堆形容词,散场后能记住三句话就算不错。前阵子在一场数据库技术峰会上,听一位自研数据库团队的技术合伙人聊了四点主张,内容不花哨,讲的都是内核、场景、迁移和长期投入这几件笨功夫的事。我边听边在笔记本上改自己项目里的选型方案,散场后还跟同行在走廊上争了半小时。这篇就把这四点主张拆开,结合我这几年做数据库选型、迁移和调优的实操,说说它们在工程上到底意味着什么、能落地到什么程度、哪些地方最容易翻车。如果你正在做自研数据库产品的评估、正在被迁移工期追着跑,或者只是个想搞明白“数据库选型到底该看什么”的开发,下面这些内容应该能省你不少试错时间。

1. 四点主张背后的行业语境拆解

一场技术分享值不值得听,看它敢不敢讲取舍。讲“我们什么都能干”的,基本可以划走;讲“我们在这个场景下放弃了什么、为什么放弃”的,才值得记笔记。这次听到的四点主张,大致可以归为:内核要自研但生态要兼容、架构要按场景定而不是按功能堆、迁移成本必须当成产品指标来考核、以及长期主义式的工具链与人才投入。这四条放在一起看,其实是一条完整的产品逻辑链,从“怎么造”到“给谁用”再到“怎么用得久”,一环扣一环。

我先说说为什么这四条在当下特别有共鸣。这几年本土数据库产品数量增长很快,我随手数一下能叫出名字的就有几十家,但真正在生产环境跑过三年以上核心业务的并不多。原因不复杂:数据库不是一次性交付的软件,它是一个需要长期陪跑的基础设施,上线只是开始,后面还有版本升级、故障处理、性能劣化、数据迁移、人员更替。很多产品在演示环境里跑得飞起,一进真实业务就被慢查询、锁冲突、统计信息失真这些事情拖住。

所以我更愿意把这四点主张理解成“一套自研数据库的自我约束清单”。它约束的不是对手,是自己:别什么都想做,先把内核做扎实;别拿功能数量当卖点,先想清楚目标负载;别把迁移说成“一键搞定”,先把兼容性评估做透;别只卖License,先把工具链和运维体系搭起来。

1.1 为什么“内核自研 + 生态兼容”成了绕不开的组合

先说第一点。内核自研和生态兼容听着像矛盾的两件事,其实是一枚硬币的两面。自研内核解决的是“可控”问题:存储格式自己定、执行路径自己调、遇到性能瓶颈能改到根上,而不是在别人的黑盒外面打补丁。生态兼容解决的是“可用”问题:现有应用的驱动、SQL 方言、运维脚本、监控采集、备份流程,能不能少改甚至不改就接上去。

我见过一个模拟项目,团队花了八个月做内核替换,性能指标确实漂亮,结果卡在应用侧:几百个存储过程、上千条带方言特征的 SQL、一堆依赖特定函数返回值的报表逻辑。应用改造的工作量直接把这个项目的 ROI 打没了。这件事给我的教训很直接:数据库的迁移成本,八成不在数据本身,而在 SQL 语义的细微差异上。分页写法、空值排序、隐式类型转换、日期加减、字符串截取起始位置、锁等待行为,这些地方差一点点,业务结果就可能不一样。

所以“内核自研 + 生态兼容”不是妥协,是务实。内核是长期竞争力,兼容是短期入场券。我判断一个产品值不值得深入评估,会先看它兼容层的实现方式,而不是先看 benchmark 分数。兼容做在解析层、做在驱动层、还是做在协议层,决定了后续维护成本的量级,这个后面单独展开。

1.2 场景优先:功能清单之外的选型逻辑

第二点是“按场景定架构”。这句话说起来像口号,落到工程上其实很具体。同样是关系型数据库,交易型负载关心的是短事务延迟、连接并发、行锁冲突、P99 稳定性;分析型负载关心的是扫描吞吐、压缩比、向量化执行、大宽表 join 的执行计划质量;混合负载关心的是两者之间怎么隔离资源,别让一条大查询把在线交易拖死。这三种诉求,对应的内核设计取向差别很大,一个产品很难在三个方向上都做到顶尖。

我踩过的最典型的坑,是拿交易型的压测结果去推断分析型场景的表现。当时一个模拟项目里,我们用短事务压测跑出来的数字很好看,团队就默认它适合做报表库,结果上线后一条多表关联的聚合查询跑了四十多分钟,把缓冲池冲得一干二净,在线业务跟着抖。根因是它的执行器没有向量化,聚合走的是行式逐条处理,扫描放大严重。这个坑让我后来养成一个习惯:评估任何数据库,都要按目标场景分别设计压测用例,绝不用一套脚本打天下。

2. 第一主张:内核自研与生态兼容的平衡术

把这条主张拆开,前半句是“敢自己写”,后半句是“会低头”。敢自己写,意味着存储引擎、事务管理、查询优化器、执行器这四大件里,至少核心路径是自己的代码,而不是套壳。会低头,意味着在接口、语法、协议、运维习惯上向主流生态靠拢,让用户的迁移成本降到可接受范围。这两件事的平衡点在哪里,是每个团队都要回答的问题。

2.1 自研内核到底难在哪,四个硬骨头

我按难度排一下。最容易的是执行器里的算子实现,投影、过滤、聚合这些,写出来能跑不难。中等难度的是存储引擎,因为要在读放大、写放大、空间放大之间做取舍,还要考虑崩溃恢复的正确性。更难的是事务管理,MVCC 的快照可见性、版本回收、长事务处理,任何一个细节出错都是数据层面的问题。最难的是查询优化器,统计信息采集、代价模型设计、join 顺序枚举、执行计划稳定性,这块基本靠时间和真实负载喂出来。

存储引擎的取舍值得单独说。B 树类和 LSM 类是目前两条主线,前者读性能稳定、范围扫描友好,但随机写会带来页分裂和写放大;后者写入吞吐高、压缩比好,但读放大会随层数增加而恶化,还需要 compaction 后台任务占用 IO。选哪条路,取决于你的目标负载是写密集还是读密集,也取决于硬件条件是机械盘、SSD 还是本地 NVMe。我见过有的产品在 SSD 上表现很好,换到普通云盘上 compaction 直接把 IO 打满,这就是没把部署环境当成设计输入。

注意:评估自研内核时,别只看“是否自研”这个标签,要看它敢不敢公开崩溃恢复、主备切换、版本回收这些边缘路径的设计说明。这些地方才是内核成熟度的真实体现。

事务这块我补充一个实操判断方法:拿一个长事务挂在那里不提交,同时持续写入,观察版本链增长和清理机制是否正常。有的实现会因为长事务导致旧版本无法回收,表空间持续膨胀,跑几天就把磁盘吃满。这个问题在演示环境里几乎不会暴露,但在真实业务里,一个忘记提交的批处理任务就能触发。

优化器这块,我给一个更接地气的判断标准:看它在统计信息缺失或过期时的降级行为。成熟的优化器会有兜底策略,比如基于规则的回退路径、采样估算、执行计划基线锁定;不成熟的优化器一旦统计信息失真,就可能选出一个灾难性的嵌套循环。我在一个模拟项目里遇到过执行计划突变,同一条 SQL 前一天跑 200 毫秒,第二天跑 90 秒,最后定位到是统计信息在批量导入后没有及时更新,优化器把一个本该走 hash join 的表估成了几十行。

2.2 兼容层的三种做法与各自代价

兼容这件事,做法大致分三档,代价差别很大。

兼容方式实现位置优点主要代价
语法层兼容SQL 解析器改动直观,用户感知强语义细节容易漏,边界 case 多
协议/驱动层兼容通信协议与驱动应用几乎零改动,ORM 友好协议细节多,版本演进要持续跟
语义层兼容类型系统与函数库结果一致性好,报表可信工作量大,需要长期对齐

语法层兼容最容易做,也最容易做出“看起来能用”的假象。举个常见例子:某商业数据库的字符串截取函数,起始位置从 1 开始,而另一种主流写法从 0 开始。如果只是把函数名映射过去,参数语义不对齐,结果就会整体偏移一位,而且这种错误不会报错,只会悄悄产生错数据。这类问题在迁移评估阶段极难发现,往往要靠逐条比对结果集才能暴露。

协议层兼容是投入产出比最高的一档。应用侧的连接池、ORM 框架、驱动配置基本不用动,迁移的心理门槛一下就降下来了。但协议兼容要跟版本,上游协议升级、认证方式变化、批量协议扩展,都得跟着适配,这是一笔长期维护开销。我在一个项目里就因为协议版本对不上,导致某个 ORM 框架的批量插入退化成逐条插入,写入性能掉了七成,查了两天才定位到。

语义层兼容是最难也最值钱的。类型转换规则、排序规则、空值参与运算的行为、日期时区处理,这些对齐了,用户才敢把核心报表迁过来。我的建议是:评估阶段就准备一套“语义一致性测试集”,覆盖类型转换、空值、排序、日期函数、数值精度这几类,拿同一批 SQL 在源库和目标库各跑一遍,逐行比对结果。这套测试集一旦建起来,后续每次版本升级都能复用,是长期资产。

3. 第二主张:以场景定架构,拒绝功能堆砌

功能清单是最没有信息量的选型材料。一个产品列了八十个特性,不代表它在你的场景里好用;另一个产品只列了三十个,可能恰好把你最痛的那几个点做透了。我在选型会上最怕听到的话是“这个我们也有”,因为“有”和“好用”之间隔着一条性能与稳定性的鸿沟。

3.1 三类负载的判断方法与关注指标

判断自己的业务属于哪类负载,不用搞得太复杂,看几个特征就够了。事务长短:单个事务涉及的行数和持锁时间,短事务多、并发高,偏交易型。数据规模与扫描比例:单次查询扫描的数据量占表总量的比例,比例高、聚合多,偏分析型。写入模式:是持续小批量写入,还是定时大批量导入,前者考验并发写入路径,后者考验批量加载和索引维护效率。

我把三类负载的关注点整理成一张表,选型时直接对着打分。

负载类型首要指标次要指标典型陷阱
交易型P99 延迟、连接并发上限锁等待时长、事务回滚率大查询混入导致缓冲池污染
分析型扫描吞吐、压缩比大表 join 计划质量、向量化命中率小并发高延迟查询拖垮整体
混合型资源隔离效果两类负载的互相干扰度缺少资源组或队列机制

混合负载是最容易被低估的场景。很多业务早期是纯交易型,做着做着就要在同一个库上跑报表,这时候如果没有资源隔离机制,一条分析查询就能把在线交易拖到超时。评估混合负载能力,我一般会做“干扰测试”:一边跑交易型压测,一边周期性发起大聚合查询,观察交易侧的 P99 恶化幅度。恶化在 20% 以内算可接受,超过 50% 基本可以判定隔离机制不到位。

3.2 用权重打分法把选型从拍脑袋变成算账

选型会上最容易吵起来的就是“A 比 B 好”,因为每个人心里的权重不一样。我的做法是把权重摆到台面上,先吵权重,再打分,最后算总分。吵权重只花半小时,比吵产品吵一下午高效得多。

以我最近一个模拟项目为例,业务是订单交易加日终报表,团队规模不大,运维人力有限。我设的权重是:内核稳定性 0.25、兼容性 0.20、目标场景性能 0.20、运维与工具链 0.15、生态与文档 0.10、总体成本 0.10。打分用 1 到 5 分,5 分最好。

评估维度权重方案A得分方案B得分加权A加权B
内核稳定性0.25431.000.75
兼容性0.20350.601.00
目标场景性能0.20541.000.80
运维与工具链0.15340.450.60
生态与文档0.10240.200.40
总体成本0.10430.400.30
合计1.00——3.653.85

算出来方案 B 略胜,但差距只有 0.2,说明这不是一个可以拍板的差距。这时候就要看“不可接受项”:如果方案 A 在兼容性上只有 3 分,意味着迁移改造工作量可能翻倍,而项目工期只有三个月,那这一项就直接否决,不用看总分。我个人的经验是,权重打分用来排除明显不合适的选项,最终决策还得靠对关键短板的判断。

提示:打分表一定要在 PoC 之前定好,不然测完再定权重,就会不自觉地往“测出来好的那个”上偏。

4. 第三主张:把迁移成本当成产品指标

这条主张我认为是最有诚意的一条。绝大多数数据库产品的宣传材料里,迁移被描述成一个工具的事,“我们有迁移工具,支持一键迁移”。做过的人都知道,工具只解决了数据搬运,真正吃时间的是应用侧的兼容性改造和结果验证。把迁移成本当成产品指标,意味着厂商要为用户的历史包袱负责,这个姿态本身就说明它打算长期做这门生意。

4.1 迁移的真实工作量拆解

我按经验给一个占比参考,不同项目会有浮动,但量级差不多:应用侧 SQL 与逻辑改造 55% 到 65%,数据迁移与校验 10% 到 15%,全链路压测与调优 15% 到 20%,切换与回滚预案 5% 到 10%。可以看到,改造是大头,而改造的难度取决于兼容层的深度。

改造工作又可以分三层。第一层是能自动改的,比如函数名映射、分页语法替换,工具跑一遍基本能覆盖。第二层是要人工判断的,比如分页在深翻页时的性能差异、隐式类型转换导致的索引失效,这些工具改完还得人看执行计划。第三层是必须重构的,比如依赖特定数据库特性的存储过程、自定义聚合函数、复杂的触发器逻辑,这部分只能重写。

我在一个模拟项目里做过统计,一共采集到约一万两千条去重后的 SQL,其中需要人工介入的约 8%,看起来比例不高,但这 8% 集中在核心交易链路上,改造加验证花了整整六周。所以评估迁移工作量,别用 SQL 总条数乘系数,要按“核心链路里的方言 SQL 条数”来估。

4.2 兼容性评估清单与 SQL 采集实操

评估阶段最有价值的动作是把生产环境的 SQL 全量采集下来,做特征归类。采集方式有几种:开慢查询日志(只能抓到慢的,会漏掉高频简单语句)、开全量日志(量大但完整)、在应用侧用代理抓包(对应用透明,但要注意脱敏)。我一般用全量日志采样加代理抓包组合,前者覆盖完整,后者能拿到真实参数。

采集完之后跑一个特征扫描,把高风险语法标出来。下面这个 Python 片段是我常用的简化版,按关键字打标签,实际项目里还会加正则匹配函数名和类型转换写法。

import re from collections import Counter # 高风险方言特征,按需扩充 PATTERNS = { "outer_join_plus": r"\(\+\)", "rownum": r"\bROWNUM\b", "limit_offset": r"\bLIMIT\s+\d+", "top_n": r"\bTOP\s+\d+", "nvl": r"\bNVL\s*\(", "decode": r"\bDECODE\s*\(", "implicit_cast": r"'\s*=\s*\d+|\d+\s*=\s*'", "date_add": r"\bDATE_ADD\s*\(", "to_char": r"\bTO_CHAR\s*\(", "hint": r"/\*\+", } def scan(sql_list): hit = Counter() samples = {} for sql in sql_list: upper = sql.upper() for name, pat in PATTERNS.items(): if re.search(pat, upper): hit[name] += 1 samples.setdefault(name, sql[:200]) return hit, samples # 用法:从日志文件里逐行读 SQL 后传入 # hits, samples = scan(sql_lines) # for k, v in hits.most_common(): # print(k, v, samples[k])

扫描结果出来后,按“出现次数 × 业务重要性”排序,优先处理核心链路上的高频特征。我一般会把结果整理成一张三列的表:特征、出现次数、处理方案(自动改写、人工确认、重构)。处理方案这一列一定要写上负责人和验证方式,否则评估报告很好看,落地时没人认领。

数据校验这块,别只比总行数。总行数对得上但内容错位的情况太常见了。我的做法是分三层校验:第一层比行数和主键集合;第二层比关键字段的聚合值,比如金额求和、状态分布;第三层抽样比对明细,按主键随机抽几千行逐字段比。第三层最费时间但最能发现问题,尤其是数值精度和字符集相关的差异。

注意:涉及金额、税率、积分这类敏感字段,迁移后一定要做全量聚合比对,别只抽样。抽样漏掉的精度差异,可能要等对账日才爆出来。

5. 第四主张:长期主义,工具链、社区与人

前三条讲的是产品怎么做,第四条讲的是产品怎么活得久。数据库的使用周期通常是五到十年,这期间会发生版本升级、硬件换代、团队人员流动、业务规模翻十倍。如果产品背后的工具链和人才供给跟不上,用户就会陷入“想换换不掉、想用用不好”的尴尬境地。

5.1 工具链是产品的另一半

我把数据库周边工具分成必须有的和加分的两类。必须有的:备份恢复、监控告警、慢查询分析、参数配置管理、版本升级工具。加分的:SQL 审核、容量评估、故障注入、执行计划基线管理、数据比对。判断标准很简单,就问一句:出了故障,我能不能只靠厂商提供的工具定位到根因,而不需要登录机器翻日志。

监控指标这块,我列一下自己必看的几项:QPS 与活跃连接数、P99 与 P999 延迟、缓冲池命中率、日志写入速率、checkpoint 频率与耗时、锁等待次数与最长等待、慢查询数量、复制延迟。这几项里,我最看重的是“日志写入速率”和“checkpoint 耗时”的组合,因为写入突然飙高往往意味着有大事务或者批量导入在跑,而 checkpoint 耗时上升通常预示着 IO 压力接近瓶颈。

备份恢复我强调一点:备份不是目的,恢复才是。我要求在项目上线前必须做一次真实恢复演练,把备份文件恢复到一台干净机器上,记录耗时和数据一致性校验结果。演练里最常见的两个问题是恢复耗远超预期、以及恢复后权限和账号信息不全。这两个问题在真故障时都是致命的。

5.2 运维调优的几条经验公式

连接池大小是最容易被拍脑袋设置的一项。拍大了浪费内存还会加剧锁竞争,拍小了业务排队。我给一个常用的估算思路:连接数 ≈ CPU 核数 × (1 + 平均等待时间 / 平均计算时间)。比如 16 核机器,单条 SQL 平均计算 2 毫秒,平均等待(IO、锁)6 毫秒,那系数是 1 加 3 等于 4,连接数在 64 左右比较合适。这个数只是起点,还要结合压测调整,但比“直接设 500”靠谱得多。

批量导入的分批策略也值得说。一次性插入十万行,事务日志会暴涨,checkpoint 被压得喘不过气,还可能触发主从延迟。我的经验是每批 2000 到 5000 行提交一次,批次大小按单行数据量调整,单批总字节控制在几十 MB 以内。同时导入期间把自动统计信息更新关掉,导入完成后手动收集一次,避免统计信息在导入过程中反复刷新拖慢写入。

索引设计上,我踩过最深的坑是“索引越多越好”。一个模拟项目里,一张表建了十一个索引,写入性能只有同规格表的三分之一,因为每次写入要维护十一个索引结构。后来砍到四个,写入提升明显,查询性能没有可感知的下降。判断一个索引该不该留,看它的使用统计,连续一个月没被用到的,基本可以下线。

6. 落地中的常见坑与排查实录

纸面上的主张听着都顺,落地时才见真章。这一节我把这几年在自研数据库迁移和运维中反复遇到的问题整理出来,做成速查表,后面再补几条我认为最值钱的经验。

6.1 典型问题速查表

现象高概率根因排查动作处理方向
P99 突然抬升但 QPS 正常大查询污染缓冲池或统计信息失真看慢查询与执行计划变化资源隔离、锁定计划基线
写入吞吐持续下降索引过多或 compaction 跟不上看索引使用统计、后台任务耗时精简索引、调整合并策略
复制延迟越来越大大事务、单线程回放、网络带宽看事务大小分布与回放线程负载拆小事务、并行回放
连接数打满连接池配置过大或连接泄漏看连接来源与空闲时长收敛连接池、排查未关闭连接
迁移后数据对不上字符集、时区、数值精度差异分层校验,先聚合后明细统一字符集与时区配置
执行计划隔天就变统计信息更新策略不稳定对比两次计划与统计信息固定采样率、计划基线
批量导入后查询变慢统计信息未更新、表膨胀检查统计信息时间戳与表大小导入后手动收集、整理表空间

这张表我建议直接贴到项目群里,出问题时先对一遍,能省掉大量“从零开始想”的时间。表里的处理方向只是方向,具体动作还得结合版本和配置,但排查顺序基本通用:先看现象是否稳定复现,再看资源层(CPU、IO、内存、网络),再看数据库层(锁、慢查询、后台任务),最后看应用层(连接、事务边界)。

6.2 几条我认为最值钱的实操心得

第一条,新数据库上线别一步到位切核心。我的做法是找一个非核心但有真实流量的业务先跑三个月,这三个月里故意制造一些异常:主备切换、磁盘打满、网络抖动、大批量导入。这些场景在演示环境里永远不会发生,但生产环境一定会遇到。三个月的价值不在于跑通,而在于积累故障处理经验,让运维团队形成肌肉记忆。

第二条,压测一定要压全链路。只压数据库没有意义,因为真实的瓶颈经常在连接池、在应用侧的序列化、在网络上。我见过数据库侧 QPS 还有大量余量,但应用侧线程池已经打满的情况,这时候优化数据库是白费力气。全链路压测还要注意数据量的真实性,用一万行数据压出来的结果,和用一亿行数据压出来的结果,可能差一个数量级。

第三条,别信任何没有说明测试条件的 benchmark 数字。看到“性能提升十倍”,先问三件事:数据量多大、并发多少、对比的是哪个版本和什么配置。这三个条件任何一个不明确,数字就没有参考价值。我自己的习惯是,任何厂商给的数字都只当作“上限参考”,真实决策必须靠自己 PoC 跑出来的结果。

第四条,回滚预案必须演练,而且要演练“最坏情况”。什么叫最坏情况:切换进行到一半,发现数据不一致,这时候要不要回滚、多久能回滚、回滚后数据怎么补偿。这个场景想一想都觉得麻烦,但真出事的时候,没有预案就是灾难。我的做法是切换前做一次全量备份加增量日志归档,切换窗口内保留双写能力至少 24 小时,确认无误后再停掉源库写入。

最后分享一个我在调优时常用的小技巧:遇到说不清的性能问题,先别急着改参数,把同一时段的监控指标按时间轴对齐,画在一张图上。CPU、IO、连接数、慢查询数、日志写入速率,这几条曲线一对齐,因果关系往往一眼就看出来了。我靠这个笨办法定位过好几次“参数改了没用”的问题,根因其实在业务侧的一条定时任务上,跟数据库配置一点关系都没有。

返回列表