把数据同步到 OceanBase Oracle 模式时,常会遇到一种诡异的慢:JDBC batch 开了、并发也拉满了,监控里增量 RPS 长期在 10 上下,单次写入耗时冲到二十多万毫秒,写入队列一路堆积。
问题通常不在配置,而在一个被忽视的细节:JDBC batch 调用成功,不等于数据库服务端真的做了集合化执行。
CloudCanal v6.3.0.0 (2026 年 7 月 30 日发布)引入 临时表合并模式(Staging Merge) 专门解决此问题。OceanBase Oracle 模式实测:执行写入耗时从 25 万 ms 压到约 8600 ms,快了约 30 倍,RPS 从约 10 提到 700+。本文剖析根因以及 Staging Merge 的实现方式。
batch 开了,为什么写入还是慢?
拖慢 OceanBase Oracle 写入的,通常是两个场景。
场景一:batch MERGE 在服务端"失效"
数据同步到 OceanBase Oracle 模式时,遇到主键冲突通常要用 MERGE INTO ... USING ... ON ... 实现"存在则更新、不存在则插入"。客户端代码看起来规规矩矩:
plain
ps.addBatch();
ps.executeBatch();
但 addBatch() / executeBatch() 只代表 JDBC 把请求批量发出去了 ,不代表服务端把多行改写成一条集合 SQL。在 OceanBase Oracle 模式下实际排查,经常会看到服务端仍在执行 N 次单行 MERGE------也就是说,batch 在服务端层面"失效"了。

表现到监控上(优化前): 
- 增量 RPS 长期在 10 上下,几乎没有有效吞吐
- 执行写入耗时峰值 25.4 万 ms
- 写入队列等待时间超 10 亿 ns,数据基本堵在队列里
场景二:高频 INSERT/UPDATE/DELETE 把 batch 切碎
增量同步的数据按源端事务和日志事件到达。哪怕同一张表短时间内涌入大量数据,只要事件类型不停切换:
plain
INSERT -> UPDATE -> DELETE -> UPDATE -> INSERT ...
普通批量写入只能按操作类型分开执行。本可以合并的一组同表数据,被切成大量只有几行、几十行的小批次。直接后果:
- 目标表被反复 INSERT / UPDATE / DELETE / MERGE,同一主键短时间内被多次探测和修改
executeBatch()调用次数暴增,小事务刷盘与提交开销叠加- batch 越碎,参数绑定和网络交互的固定成本占比越高
根因:batch API 成功 ≠ 服务端集合执行
很多人以为"调了 executeBatch() 就是批量写入"。实际上 JDBC batch 解决的是客户端到服务端的网络往返成本,它不保证服务端把多条 DML 合并成一条集合化执行计划。
具体到 OceanBase Oracle 模式,这个问题集中体现在 MERGE 上:客户端发出去的是一条批量 MERGE,服务端却没有把它改写成集合执行,而是拆成 N 次单行 MERGE 逐条跑,batch 的集合化收益完全拿不到。这不是个例------OceanBase 官方知识库 也收录了这一现象:Oracle 模式下批量语句常常无法启用 BATCH 优化。结果就是:你以为发出去的是 1 条批量 MERGE,服务端其实跑了 1024 次单行 MERGE。
所以优化方向不是"再多开几个并发",而是让服务端真正集合化执行------用一条集合 SQL 把同一批数据一次处理掉。
解法:CloudCanal Staging Merge
Staging Merge 用一条统一路径同时解决上面两个问题:先把一批数据批量写入 staging 临时表,再通过集合 SQL 修改目标表。
plain
业务数据
-> JDBC batch insert staging table
-> DELETE / MERGE target using staging table
-> commit / 清理 session 数据
关键在于换一种写入形态:不再指望服务端把批量 MERGE 改写成集合执行,而是先把整批数据用批量 INSERT 灌进临时表(实测单次可写入上千行),再由集合 SQL 一次性改完目标表------比如一条 MERGE INTO 目标表 USING 临时表,本身就是集合执行,跟 batch 改写无关。原本的 1024 次单行 MERGE,就这样被压缩成 1 条集合 MERGE。

为什么用临时表,不直接改目标表?
因为集合 MERGE 需要 source 数据。OceanBase / Oracle 的 GLOBAL TEMPORARY TABLE(GTT)天然适合做这个 source:
- 结构全局可见,数据 session 私有,不同连接互不影响
ON COMMIT DELETE ROWS让连接 commit 后自动清理 staging 数据
开启方式与适用范围
Staging Merge 通过两个新参数开启:
useStagingMerge:开启临时表合并模式stagingSchema:指定临时表所在的 schema,临时表会集中建在这个 schema 下便于管理
OceanBase for Oracle 目标端在全量、增量阶段均可启用该模式。
效果:RPS 从 10 提到 700
下面这组实测数据来自 **OceanBase for Oracle(Oracle 租户模式)**目标端。
同样的环境:阿里云 ECS 8C16G、184 列宽表、writeParallel 32、increBatchSize 1024,全程无 CPU / 内存瓶颈。
OB Oracle batch MERGE 失效场景
优化前: 
开启 useStagingMerge 后: 
- 增量 RPS 从 ~10 提升到 479(均值)~ 718(峰值)
- 执行写入耗时从 25.4 万 ms 压到 约 8600 ms ,快了约 30 倍
- 写入队列等待时间同步回落,数据不再堆积
高频 I/U/D 混合场景
第二个问题也被同一条路径解决。原本会被切成大量小 batch 的混合 I/U/D,先在内存里按主键压缩成最终状态,再用"集合 DELETE + 集合 MERGE"两条 SQL 一次性落库,省掉大量目标表反复探测和小事务提交开销。以 MySQL → OceanBase Oracle 实测为例: 
同一套机制也适用于 Oracle 目标端。
混合 DML 如何收敛成两条集合 SQL
对于同一张表里交错的 INSERT/UPDATE/DELETE,Staging Merge 会先在内存里按主键算出每个主键的最终状态,再收敛成两条集合 SQL 一次性落库:需要删的主键走一条集合 DELETE,需要写或更新的走一条集合 MERGE。
压缩的关键不是简单取最后一行,而是要识别最后一次 INSERT 和 DELETE 的相对位置,保证结果正确:
| 原始事件序列 | 压缩后目标动作 |
|---|---|
| INSERT -> UPDATE -> UPDATE | 只写最终值一次 |
| UPDATE -> UPDATE -> DELETE | 只删除一次 |
| DELETE -> INSERT -> UPDATE | 删旧行后写最终值 |
| INSERT -> DELETE | 最终不保留 |
适用场景与代价
收益
- 高冲突批量写入性能更稳
- 大幅减少服务端单行 MERGE 执行次数
- 高频 I/U/D 交错时,多个小 batch 收敛为少量集合 DML
- 宽表场景比单行 MERGE 更友好
代价
- 会在目标库的指定 schema 下自动创建临时表,任务删除时自动清理,无需手动管理
快速开始
如果你正在做 OceanBase for Oracle(或 Oracle)方向的数据同步,遇到类似的写入瓶颈,可以升级到 CloudCanal v6.3.0.0 ,开启 useStagingMerge。
老用户一键升级到 v6.3.0.0(Docker 托管镜像版):
bash
curl -fsSL https://tgzdownload.clougence.com/support/upgrade_on_docker.sh | bash -s -- 6.3.0.0 ./cc_home
新用户一键安装(Docker 托管镜像版):
bash
curl -fsSL https://tgzdownload.clougence.com/support/install_on_docker.sh | bash -s -- 6.3.0.0 ./cc_home
./cc_home为安装目录,可按需调整。安装完成后浏览器访问http://<你的机器 IP>:8111进入控制台。完整步骤见 安装文档 / 升级文档。
部署或使用中遇到任何问题,欢迎联系技术支持。