OceanBase vs 金仓:复杂SQL执行计划与性能对比实测
同一组复杂 SQL 丢到 OceanBase 和金仓里跑,结果往往不是"谁快谁慢"这么简单。我最近因为一个迁移评估项目,把一套包含多表关联、窗口函数、子查询嵌套、批量聚合的报表 SQL,分别在 OceanBase 的分布式集群和金仓的集中式实例上做了完整对比测试。跑完之后最大的感受是:架构差异带来的不是性能数字的浮动,而是执行路径的根本分叉——同一条 SQL,在分布式引擎里会被拆成多个子计划并行下发到不同节点,在集中式引擎里则更依赖单机的优化器决策和资源调度。这篇文章就把这次对比的完整过程拆开讲,包括测试环境怎么搭、SQL 怎么选、执行计划怎么看、哪些地方容易踩坑,以及迁移时到底该关注什么。如果你正在做国产数据库选型、异构迁移评估,或者单纯想搞清楚分布式和集中式在 SQL 执行层面的真实差异,下面的内容应该能帮你少走不少弯路。
1. 为什么同一组 SQL 在两种架构下值得对比
1.1 分布式与集中式的本质分歧点
很多人对分布式数据库的理解停留在"数据分散在多台机器上",这个说法没错但太浅。真正影响 SQL 执行的是三个层面:数据分布方式、计算下推策略、协调节点的工作模式。
OceanBase 这类分布式架构,数据按照分区键被打散到多个 OBServer 节点上,每个节点只持有部分数据。当一条 SQL 进来,协调节点(通常是接收请求的那个节点)需要先做一次"计划拆分"——判断哪些算子可以下推到数据所在节点本地执行,哪些必须把数据拉到上层做汇总。这个拆分过程直接决定了网络传输量和最终延迟。
金仓的集中式架构则不同,数据集中在一个实例(或主备集群)里,优化器面对的是完整的统计信息和本地存储。它的执行计划更接近传统商业数据库的思路:索引选择、连接顺序、排序策略都在单机范围内决策,没有跨节点的数据搬运问题。
注意:这里说的"集中式"是指计算和存储不跨节点分片的部署形态,金仓也支持读写分离和主备高可用,但 SQL 执行的核心决策仍在一个主节点完成。
1.2 复杂 SQL 为什么最能暴露差异
简单的主键查询在两种架构下差别不大,因为都能走索引精确定位。但复杂 SQL 不一样——多表 JOIN、窗口函数、嵌套子查询、GROUP BY 聚合,这些操作涉及大量中间结果的产生和传递。分布式架构下,中间结果可能需要在节点间 shuffle;集中式架构下,中间结果在本地内存或临时表空间里流转。
我这次选的测试 SQL 包含以下特征:
- 4 张表关联,其中两张是大表(千万级),两张是维度表(万级)
- 包含
ROW_NUMBER() OVER(PARTITION BY ... ORDER BY ...)窗口函数 - 有一个相关子查询用于计算同比
- 最终按多个维度 GROUP BY 后排序取 TOP 100
这种 SQL 在报表场景里非常典型,也最能体现两种架构在算子下推、并行度分配、内存管理上的不同策略。
1.3 测试环境的基本设定
为了让对比有意义,我把两边的硬件资源尽量拉平:
| 项目 | OceanBase | 金仓 |
|---|---|---|
| 节点数 | 3 个 OBServer | 1 主 1 备 |
| 单节点 CPU | 16 核 | 16 核 |
| 单节点内存 | 64GB | 64GB |
| 存储 | SSD | SSD |
| 数据量 | 约 8000 万行 | 约 8000 万行 |
| 版本 | 4.x 社区版 | V8 企业版 |
数据用同样的生成脚本灌入,确保行数、字段分布、空值比例一致。这一步很关键,否则后面比出来的数字没有参考价值。
2. 测试 SQL 的设计与数据准备
2.1 四张表的建模思路
我模拟的是一个订单分析场景,四张表分别是:
orders:订单主表,约 5000 万行,包含订单 ID、用户 ID、商品 ID、金额、下单时间、状态等字段order_items:订单明细表,约 3000 万行,包含明细 ID、订单 ID、商品 ID、数量、单价users:用户维度表,约 50 万行,包含用户 ID、注册时间、地区、等级products:商品维度表,约 10 万行,包含商品 ID、类目、品牌、成本价
建表时有个细节需要注意:分布键的选择。OceanBase 里我按 orders.user_id 做 HASH 分区,order_items 按 order_id 做 HASH 分区。这样做的目的是让关联操作尽量在本地完成,减少跨节点数据搬运。金仓这边不需要指定分布键,但需要确保关联字段上有合适的索引。
2.2 数据灌入的坑
灌数据这一步我就踩了坑。OceanBase 如果用普通的 INSERT 逐条写入,速度会非常慢,因为每次写入都要经过协调节点路由到对应分区。正确做法是用 LOAD DATA 或者批量 INSERT 配合并行度参数。
金仓这边相对直接,COPY 命令或者批量 INSERT 都能跑出不错的吞吐。但要注意 wal_level 和 checkpoint 参数的调整,否则大量写入时 checkpoint 频繁触发会拖慢速度。
实操心得:OceanBase 灌数据前先把
enable_major_freeze关掉,等数据全部导入后再手动触发一次合并,这样能避免导入过程中反复做合并操作。
2.3 统计信息的收集
数据灌完后,两边都必须做统计信息收集。这一步经常被忽略,但对复杂 SQL 的执行计划影响巨大。
OceanBase 用 ANALYZE TABLE 或者它的自动统计信息收集机制。金仓用 ANALYZE 命令。我建议在测试前手动跑一次全量统计,确保优化器拿到准确的行数估算。
如果统计信息不准,OceanBase 可能选错分布方式导致大量数据跨节点传输,金仓可能选错连接算法导致笛卡尔积式的性能灾难。
3. 复杂 SQL 在两边的执行计划对比
3.1 测试 SQL 全文
先看这条 SQL 长什么样:
这条 SQL 的复杂度在于:四表关联、相关子查询、窗口函数、聚合后排序,几乎覆盖了分析型查询的所有典型算子。
3.2 OceanBase 的执行计划解读
在 OceanBase 里用 EXPLAIN 看计划,输出会显示每个算子分配到了哪些节点。我截取关键部分:
关键观察点:OceanBase 把 orders 和 order_items 的 JOIN 做成了分布式 HASH JOIN,两个表的数据按照关联键重新分布到相同节点后再做本地 JOIN。这个"重分布"(redistribution)过程会产生网络传输,是分布式执行的主要开销来源。
相关子查询被改写成了半连接(semi join)的形式,这是优化器的自动改写。窗口函数在最后阶段执行,因为需要等聚合结果全部就绪。
3.3 金仓的执行计划解读
金仓的 EXPLAIN 输出格式不同,但信息量同样丰富:
金仓选择了全 Hash Join 的策略,所有连接都在本地内存完成。代价估算(cost)显示主要开销在 Hash Join 和 HashAggregate 上。没有网络传输开销,但单节点的内存压力和 CPU 压力会更大。
3.4 执行计划差异的核心结论
把两边的计划放在一起看,差异可以归纳成一张表:
| 对比维度 | OceanBase | 金仓 |
|---|---|---|
| 连接策略 | 分布式 HASH JOIN + 重分布 | 本地 HASH JOIN |
| 并行方式 | 节点间并行 + 节点内并行 | 节点内并行 |
| 网络开销 | 有,取决于分布键设计 | 无 |
| 内存压力 | 分散到多节点 | 集中在单节点 |
| 子查询改写 | 半连接改写 | 半连接改写 |
| 窗口函数位置 | 聚合后、排序前 | 聚合后、排序前 |
这个对比说明一个核心问题:分布式架构的性能瓶颈在网络,集中式架构的性能瓶颈在单机资源。选型时要根据实际的数据分布特征和查询模式来判断哪种瓶颈更容易接受。
4. 实测性能数据与瓶颈定位
4.1 冷热缓存下的响应时间
我分别在冷缓存(重启后首次执行)和热缓存(连续执行三次取平均)下测了响应时间:
| 场景 | OceanBase | 金仓 |
|---|---|---|
| 冷缓存首次执行 | 18.6s | 12.3s |
| 热缓存平均 | 4.2s | 3.8s |
| 第二次执行 | 5.1s | 4.0s |
| 第三次执行 | 3.9s | 3.7s |
冷缓存下 OceanBase 明显更慢,因为首次执行需要建立节点间的数据通道、加载分布式计划。热缓存下两者差距缩小,说明 OceanBase 的缓存机制在重复查询场景下能有效摊薄分布式开销。
4.2 分布式执行中的网络等待
为了定位 OceanBase 的耗时分布,我查了 GV$SQL_AUDIT 视图里的等待事件统计。发现 EXCHANGE 类等待占了总耗时的 35% 左右,这就是节点间数据传输的开销。
进一步分析发现,orders 和 order_items 的 JOIN 重分布是主要来源。因为 orders 按 user_id 分区,而 JOIN 条件是 order_id,两个表的分布键不一致,导致必须做一次全量重分布。
实操心得:如果能把
order_items也按user_id分区(通过冗余字段实现),或者把 JOIN 条件改成分布键对齐的形式,这个重分布开销可以大幅降低。这是分布式数据库设计时最值得花时间优化的点。
4.3 集中式执行中的内存瓶颈
金仓这边虽然没有网络开销,但我观察到执行期间单节点内存使用率飙升到 85% 以上。Hash Join 需要在内存里建哈希表,四张表的中间结果叠加起来占用很大。
如果数据量再大一些,或者并发查询再多几个,就可能触发磁盘溢写(spill to disk),性能会断崖式下降。这是集中式架构在复杂分析查询面前的典型风险。
我做了个压力测试,同时跑 5 条类似的复杂 SQL:
| 并发数 | OceanBase 平均响应 | 金仓平均响应 |
|---|---|---|
| 1 | 4.2s | 3.8s |
| 3 | 6.8s | 9.5s |
| 5 | 9.1s | 18.2s |
并发上来之后,OceanBase 的分布式优势就体现出来了——多个节点的资源可以同时服务不同查询。金仓在并发 5 时响应时间翻了好几倍,就是因为单节点资源被争抢。
4.4 慢 SQL 的定位手段
两边定位慢 SQL 的方法不太一样,我整理了一下:
OceanBase 用 GV$OB_SQL_AUDIT 或者 ocp 监控平台,能看到每个算子的耗时、等待事件、处理行数。关键是看 EXCHANGE 相关的指标,如果占比高就说明网络传输是瓶颈。
金仓用 pg_stat_statements 扩展或者自带的性能视图,重点看 shared hit 和 shared read 的比例,以及是否有临时文件写入。如果看到 temp read/write 很大,说明内存不够在溢写磁盘。
5. 迁移适配中的真实坑点
5.1 语法兼容性不是最大问题
很多人担心 OceanBase 和金仓的 SQL 语法差异,实际测下来,标准 SQL 部分兼容性都不错。真正的问题在函数行为和隐式转换上。
比如 ROUND 函数在处理 DECIMAL 类型时,两边的小数位保留策略有细微差别。TO_CHAR 的格式化模板也不完全一致。这些差异在简单查询里看不出来,但在复杂 SQL 的中间结果传递过程中可能被放大。
我的建议是:迁移前把所有用到的函数列一个清单,逐个在目标库上验证输出结果,不要假设"标准函数行为一致"。
5.2 执行计划不会自动最优
从金仓迁移到 OceanBase 时,最大的误区是以为"SQL 不用改,优化器会自动处理"。实际上,分布式优化器需要额外的信息才能做出好决策——比如分布键的选择、分区裁剪的条件、并行度的设置。
我遇到一个案例:同一条 SQL 在金仓上跑 3 秒,迁到 OceanBase 后跑了 40 秒。排查发现是因为关联字段和分布键不匹配,导致全量数据重分布。后来调整了分区策略,降到 5 秒。
反过来,从 OceanBase 迁到金仓也有坑。分布式环境下写的 SQL 可能过度依赖并行执行,到了集中式环境如果没调好并行度参数,反而跑不出性能。
5.3 事务与锁的行为差异
复杂 SQL 往往伴随事务操作。OceanBase 的分布式事务需要跨节点协调,提交延迟比单机事务高。金仓的本地事务提交更快,但在高并发写入时锁竞争更集中。
测试中我模拟了"查询的同时有写入"的场景:
| 场景 | OceanBase | 金仓 |
|---|---|---|
| 纯读复杂 SQL | 4.2s | 3.8s |
| 读写混合 | 5.8s | 7.2s |
| 高并发写入下的读 | 6.5s | 12.1s |
这个数据说明:读多写少的分析场景,两者差距不大;写密集或读写混合场景,分布式架构的扩展性优势更明显。
5.4 参数调优的侧重点不同
OceanBase 调优重点在:并行度(parallel_degree)、分区策略、合并策略、租户资源规格。
金仓调优重点在:work_mem、shared_buffers、effective_cache_size、并行 worker 数量。
两边都调过之后我的体会是:OceanBase 的调优更偏"架构级",一旦分区和并行度设对了,性能提升是数量级的;金仓的调优更偏"参数级",需要反复微调找到平衡点。
6. 选型与迁移的实操建议
6.1 什么场景优先考虑分布式
根据这次测试的经验,以下场景分布式架构优势明显:
- 数据量超过单机存储上限,必须分片
- 并发查询数量多,需要横向扩展计算资源
- 写入吞吐要求高,单节点写入成为瓶颈
- 业务能接受最终一致性或分布式事务的额外延迟
6.2 什么场景集中式更合适
- 数据量在单机可承载范围内(比如几 TB 以内)
- 查询以复杂分析为主,对单条 SQL 延迟敏感
- 事务一致性要求严格,不希望引入分布式协调开销
- 运维团队对分布式系统的排障经验不足
6.3 迁移前的检查清单
我把这次迁移评估中总结的检查项列出来,供参考:
- 统计信息是否完整收集,行数估算是否准确
- 分布键/分区键是否与主要查询的关联条件对齐
- 复杂 SQL 的执行计划是否出现意外的全表扫描或笛卡尔积
- 函数行为差异是否逐项验证过
- 并发场景下的资源争抢是否在可接受范围
- 事务隔离级别和锁行为是否符合业务预期
- 慢 SQL 监控和告警机制是否就位
6.4 一个容易被忽略的细节:数据倾斜
分布式架构下,如果分区键选择不当导致数据倾斜,某些节点会承载远超平均的数据量,成为性能短板。OceanBase 里可以通过 GV$OB_TABLET 相关视图查看各分区的数据量分布。
金仓虽然不存在跨节点倾斜,但单表内的数据分布也会影响索引效率。如果某个字段的值高度集中,基于该字段的索引选择性就很差,优化器可能放弃索引走全表扫描。
这次测试中我特意构造了一个倾斜场景:users 表里 80% 的用户集中在三个地区。结果 OceanBase 在按地区聚合时,对应分区的节点明显更慢;金仓则在 region 字段的索引上出现了选择性下降。两边都需要针对性处理。
7. 我在这轮对比中踩过的具体坑
7.1 OceanBase 的并行度设置陷阱
一开始我没调并行度,用的是默认值。结果复杂 SQL 跑得比预期慢很多。后来发现默认并行度偏低,没有充分利用多节点资源。手动设置 parallel_degree 后,响应时间从 12 秒降到 4 秒多。
但并行度也不是越高越好。我试过设到 32,结果因为线程调度开销和资源争抢,反而比 16 还慢。最终在这套 3 节点 16 核的环境下,并行度 12 左右是比较合适的值。
实操心得:并行度建议设置为"可用 CPU 核数的 60% 到 80%",留出余量给其他并发查询和后台任务。
7.2 金仓的 work_mem 调优反复
金仓的 work_mem 控制每个排序或哈希操作可用的内存。默认值通常偏小,复杂 SQL 会频繁溢写磁盘。我一开始调到 256MB,单条查询快了,但并发 5 条时内存直接打满,系统开始 swap,性能反而崩了。
后来改成 64MB,配合 max_parallel_workers_per_gather 设为 4,找到了一个平衡点。这个过程说明:内存参数不能孤立地调,必须结合并发数和总内存一起算。
7.3 统计信息过期导致的计划突变
测试进行到一半时,我发现同一条 SQL 突然变慢了。排查后发现是因为中间做了一批数据删除操作,但没重新收集统计信息。优化器还按旧的行数估算选计划,结果选错了连接算法。
这个坑在两边都出现过。教训是:任何大批量数据变更后,都要重新收集统计信息,不要依赖自动收集的时机。
7.4 字符集和排序规则的隐式影响
这个坑比较隐蔽。OceanBase 和金仓的默认字符集、排序规则可能不同,导致字符串比较、排序、GROUP BY 的结果有细微差异。在复杂 SQL 里,如果 GROUP BY 的字段涉及字符串,最终结果的顺序可能不一致。
如果业务对结果顺序有严格要求,一定要在 SQL 里显式指定 ORDER BY,不要依赖数据库的默认行为。
7.5 连接池配置对测试结果的干扰
最开始测试时,我发现响应时间波动很大。排查后发现是连接池配置不合理——连接复用率低,每次查询都在新建连接。调整连接池参数后,数据才稳定下来。
这个细节提醒我:性能测试要控制变量,连接管理、网络环境、客户端配置都可能影响结果。两边测试时尽量用相同的客户端配置和连接策略。
8. 从这轮对比中沉淀下来的判断框架
8.1 用"数据分布特征"做第一层筛选
判断该选分布式还是集中式,第一个要问的问题是:数据能不能放在一台机器上?如果能,且未来三年增长可预期,集中式的运维复杂度和调优确定性更有优势。如果不能,或者增长曲线陡峭,分布式是更稳妥的选择。
8.2 用"查询模式"做第二层筛选
分析型查询为主、并发不高、单条 SQL 复杂度高——集中式往往表现更稳定。点查为主、并发极高、写入吞吐大——分布式更容易横向扩展。
混合负载场景要特别小心,因为两种架构在混合负载下的表现差异最大。建议做真实的混合负载压测,不要只看单一类型的 benchmark。
8.3 用"团队能力"做第三层筛选
分布式数据库的排障门槛明显更高。一个慢查询可能涉及多个节点、多个租户、多种等待事件。如果团队没有分布式系统的运维经验,上线后遇到问题会很被动。
集中式数据库的排障路径更接近传统数据库,学习曲线平缓。这不是技术优劣问题,而是匹配度问题。
8.4 一个实用的决策流程
我把上面的判断逻辑整理成一个简单的决策流程:
- 先算数据总量和增长预期,确定是否需要分片
- 再分析查询模式,确定是分析型、事务型还是混合型
- 然后评估并发规模和峰值压力
- 最后结合团队运维能力做最终决策
- 决策后一定要做真实数据的 POC 测试,不要只看文档和 benchmark
这套流程在我经手的几个项目里都适用,核心思想是:没有绝对更好的架构,只有更适合当前场景的架构。
8.5 后续可以继续深挖的方向
这次对比主要聚焦在单条复杂 SQL 的执行差异上。实际生产环境中还有很多值得继续验证的点:分布式事务在跨节点写入时的实际延迟分布、集中式架构在数据量继续增长后的扩展方案、两种架构在备份恢复和容灾切换时的表现差异。
我个人的习惯是,每做一次这样的对比测试,就把测试脚本、执行计划、性能数据完整存档。下次遇到类似选型问题时,这些历史数据就是最直接的参考依据,比任何文档都管用。