PostgreSQL码农集散地

Oracle → 金仓 KingbaseES 数据库平滑迁移实操

  • 目标:Oracle → KES V9R2C14(Oracle 兼容模式),Kingbase FlySync 全量+增量平滑迁移
  • 环境:macOS M 系列(Apple Silicon ARM64) + Docker aarch64 镜像
  • 方法论:KCSM 官方五步法(评估 E1 → 改造 E2 → 测试 E3 → 割接 E4 → 回退 E5)
  • 工具链:KDMS 评估(结构迁移 + 4 象限兼容性) + KDTS 全量 + KFS (FlySync) 增量 + KFS 比对服务(数据校验,精简/详细两种模式)+ KReplay 验证
  • 目标读者:金仓 DBA、迁移工程师、国产化项目经理

目录

  • 一、迁移方法论总览(KCSM 五步法 + 决策启发式)
  • 二、测试环境搭建(Oracle + KES 双容器)
  • 三、E1 评估阶段(KDMS 4 象限兼容性扫描)
  • 四、E2 改造阶段(SQL/PLSQL 方言适配)
  • 五、造数据(业务 Schema + 测试数据)
  • 六、E3 测试阶段(KDTS 全量 + KFS 同步)
  • 七、E4 割接阶段(业务不停机切换)
  • 八、E5 回退阶段(30 秒切回源库)
  • 九、性能验证(KWR/KSH/KDDM 三件套)
  • 十、附录(方言对照表 / JDBC / 快速验证命令)

一、迁移方法论总览

1.1 KCSM 五步法(官方迁移操作系统)

「迁移不是导入导出+改 SQL,而是评估→改造→测试→割接→回退的闭环决策系统。」

阶段
工具组合
核心任务
产出物
E1 评估
KDMS
4 象限兼容性扫描
评估报告(对象/数据/应用/性能)
E2 改造
KDMS 翻译 + 人工
SQL/PLSQL 方言适配
改造脚本、翻译日志
E3 测试
KDTS + KFS
全量迁移 + 同步验证
目标库 + KFS 比对报告(精简/详细两种模式)
E4 割接
KFS + 应用切换
业务切新库
业务零中断
E5 回退
KFS 反向链路
30 秒切回源库
应急通道

1.2 决策启发式(关键判断)

启发式 1(停机窗口 vs 兼容率):
  停机 ≥ 4h 且 对象兼容率 ≥ 95%  →  KDTS-WEB 一次性离线迁移,无需 KFS
  停机 < 1h  或 RTO 要求 < 5min  →  必须 KDTS + KFS + 反向 KFS + 应用单切(本文场景)

启发式 2(PL-SQL 翻译阈值):
  翻译率 < 85%  →  项目升级 P0 风险,需金仓原厂驻场 ≥ 2 周

启发式 3(字符集与 encoding 绑定):
  源 AL32UTF8 → KES initdb 必须同 encoding(不可逆)

启发式 4(5 分钟回滚四件套):
  金融/政务核心系统 → 保留源库 ≥ 30 天,反向 KFS 链路保持活跃

启发式 5(无主键表的复制标识):
  表无主键或无唯一索引 → KFS 源端 ini 必须配置 replicator.extractor.dbms.autoIdentity=full
  否则会出现 KFS 日志持续刷 "alter table xx replicate identity full",同步卡住

启发式 6(KES 严格类型检查):
  Oracle 兼容模式下 KES 默认严格类型检查;WHERE int_col = '123' 会报错
  如需宽松行为,需显式设置兼容参数(生产环境慎用)

1.3 本文适用场景

本文针对最严格的迁移场景:

  • ✅ 业务停机窗口 < 1 小时(要求几乎不停机)
  • ✅ RTO < 5 分钟(回退时间约束)
  • ✅ Oracle → KES Oracle 兼容模式
  • ✅ KFS 全量 + 增量同步
  • ✅ 实验场景采用 macOS ARM64 + Docker 环境

1.4 风险与诚实边界

⚠️ 本文基于 KES V9R2C14 编写,部分信息可能与最终 GA 版有出入。请以金仓官方最新文档为准。

⚠️ KFS 下载、安装、命令路径等以官方文档为准:https://bbs.kingbase.com.cn/documentGuide?recId=c3f448eede450dfbd8cadf68a991b40f

1.5 方案总览与拓扑

本文采用 "KDTS + KFS + 反向 KFS 链路 + 应用切换" 的双轨不停机迁移方案,整体架构如下:

Image

拓扑图:

Image

关键术语约定:

术语
含义
备注
正向 KFS 链路
Oracle → KES 的 KFS 同步服务(服务名 ORA_TO_KES_SYNC)
E3-E4 阶段的主链路
反向 KFS 链路
KES → Oracle 的 KFS 同步服务(服务名 KES_TO_ORA_REVERSE)
割接前预置但保持 offline,仅在 E5 应急回退时短暂启用
应用单切
割接窗口内一次性把业务 JDBC 从 Oracle 切到 KES,Oracle 端停写
配合正向 KFS 链路 drained 校验使用
KFS 比对服务
KFS 自带的数据一致性校验组件(精简/详细两种模式)
E3-E5 阶段产出"比对报告",独立于 KDMS
KDMS 与数据校验
KDMS 核心是结构迁移 + 4 象限兼容性评估,不提供两端数据比对
数据校验统一使用 KFS 比对服务

⚠️ 本文适用边界:

  1. KDMS vs KFS 数据校验:KDMS 用于数据库结构迁移和 4 象限兼容性评估;数据一致性校验由 KFS 自带的比对服务提供(精简/详细两种模式),E3 阶段产出物统一使用 KFS 比对服务。
  2. Oracle 端抽取方式:支持三种抽取模式. Logminer 模式(KFS 与 Oracle 分开部署,不破坏 Oracle 容器边界, 但功能受限);若需同步 BLOB/CLOB 大对象或 DDL,可改用 REDO 模式——但 REDO 模式要求 KFS 与 Oracle 同机部署;官方推荐方案是 KFS 的 无侵入方案(Oracle 端仅部署轻量日志代理,解析放在外部"体外机", 且与 REDO 模式功能对齐),详见 §6.1。拓扑图中三条边(实线 Logminer/虚线 REDO/粗线 REDO+日志代理)对应这三种模式。

二、测试环境搭建

2.1 环境总览

为了完成本文提到的迁移,我在 macOS 上用金仓数据库 Oracle 兼容版及 Oracle 官方镜像进行测试验证。

Image

2.2 下载 KES 镜像(Oracle 兼容版,aarch64)

下载地址:

  • 官网下载页:https://www.kingbase.com.cn/download.html
  • Docker 安装文档:https://docs.kingbase.com.cn/cn/KES-V9R2C14/install/02-docker-install
  • Oracle 兼容版产品手册:https://docs.kingbase.com.cn/cn/KES-V9R2C14/introduction/

本文使用的版本:

# 下载地址(用户已下载)
# 文件名:KingbaseES_V009R002C014B0009_aarch64_Docker.tar
# 即 KES V9R2C14,Oracle 兼容版,ARM64 Docker 镜像

导入镜像:

docker load -i ~/Downloads/KingbaseES_V009R002C014B0009_aarch64_Docker.tar
docker images | grep -i kingbase
# 预期输出:
# kingbase_v009r002c014b0009_single_arm:v1   88981b24a950   1.71GB   838MB

2.3 启动 KES 容器

mkdir -p ~/kbdata

docker run -tid --privileged \
  -p 5432:54321 \
  -v ~/kbdata:/home/kingbase/userdata/ \
  -e NEED_START=yes \
  -e DB_USER=kingbase \
  -e DB_PASSWORD=123456 \
  -e DB_MODE=oracle \
  --name kingbase \
  kingbase_v009r002c014b0009_single_arm:v1 \
  /usr/sbin/init

关键参数:

参数
值
说明
DB_MODE=oracle
oracle
兼容模式在 initdb 阶段决定,事后不可改(启发式 3)
-p 5432:54321
—
宿主 5432 → 容器 54321(KES 默认端口)
DB_USER
/DB_PASSWORD
kingbase/123456
管理员账号
--privileged
—
容器需特权模式(部分 KES 内核操作需要)

验证 KES 启动:

# 等待 KES 就绪
docker logs -f kingbase 2>&1 | grep -E "database system is ready|startup|ready to accept connections"

# 容器内连接(验证 compatible_mode)
docker exec -it kingbase sh -c "ksql -U SYSTEM -W 123456 -d TESTDB"
-- 连接成功后查看:
SHOW compatible_mode;   -- 预期输出:oracle
SHOW server_encoding;   -- 预期输出:UTF8
SHOW pg_compat_version; -- 推荐值:12(KES V9R1 内核)

2.4 下载 Oracle Docker 镜像(aarch64)

官方参考:

  • 镜像库:https://github.com/oracle/docker-images/blob/main/OracleDatabase/SingleInstance/README.md
  • ARM64 部署指南:https://github.com/wilfriedago/oracle-database-23ai-free-setup-guide
# 拉取 Oracle 23ai Free(ARM64)
docker pull --platform=linux/arm64 container-registry.oracle.com/database/free:latest-lite

2.5 启动 Oracle 容器

mkdir -p ~/oracle_data

docker run -d --name oracle-lite \
  -p 1521:1521 \
  -e ORACLE_PWD=123456 \
  -e ORACLE_CHARACTERSET=AL32UTF8 \
  -v ~/oracle_data:/opt/oracle/oradata \
  container-registry.oracle.com/database/free:latest-lite

关键参数:

参数
值
说明
ORACLE_CHARACTERSET
AL32UTF8
KES 必须同样 UTF8 encoding(启发式 3)
ORACLE_PWD
123456
SYSTEM/SYS 用户密码

验证 Oracle 启动:

# 等待 Oracle 就绪(约 3-5 分钟)
docker logs -f oracle-lite 2>&1 | grep -E "DATABASE IS READY|starting|Oracle instance started"

# 容器内连接
docker exec -it oracle-lite sqlplus SYSTEM/123456@//localhost/FREEPDB1
# 连接成功会看到:
# Oracle Database 23ai Free Edition

# 宿主机客户端连接
sqlplus SYSTEM/123456@//localhost:1521/FREEPDB1

2.6 验证网络互通

# KES → Oracle
docker exec kingbase sh -c "nc -zv host.docker.internal 1521"

# Oracle → KES
docker exec oracle-lite sh -c "nc -zv host.docker.internal 5432"

三、E1 评估阶段(KDMS 4 象限)

3.1 评估目标

「先把兼容性四象限查清楚,再谈能不能迁。」—— 金仓官方迁移方法论

KDMS 扫描源库,输出 4 个象限的兼容性报告:

象限
内容
必须达到
对象兼容
表、索引、视图、序列、触发器、存储过程、包
≥ 95%(启发式 1)
数据兼容
字符集、数据类型、NULL/空串行为
≥ 95%
应用兼容
JDBC/ODBC 连接串、ORM 映射
≥ 90%
性能兼容
执行计划、SQL 方言差异
验证后判定

任一象限 < 90% → 项目升 P0 风险(模型 1)

3.2 执行 KDMS 扫描

KDMS 工具由金仓工程师提供。本文以 SQL 脚本做"模拟评估",覆盖核心检查项。

Oracle 端对象扫描(示例 SQL):

-- 在 Oracle SYSTEM 用户下执行
CONNECT SYSTEM/123456@//localhost/FREEPDB1;

-- 1. 表和列统计
SELECT OWNER, TABLE_NAME, NUM_ROWS
FROM ALL_TABLES
WHERE OWNER = 'TESTUSER'
ORDERBY NUM_ROWS DESC;

-- 2. 约束检查(主键、外键、CHECK、UNIQUE)
SELECT CONSTRAINT_NAME, TABLE_NAME, CONSTRAINT_TYPE, STATUS
FROM ALL_CONSTRAINTS WHERE OWNER = 'TESTUSER';

-- 3. 索引
SELECT INDEX_NAME, TABLE_NAME, COLUMN_NAME, INDEX_TYPE, UNIQUENESS
FROM ALL_IND_COLUMNS i
JOIN ALL_INDEXES ix ON i.INDEX_NAME = ix.INDEX_NAME AND i.INDEX_OWNER = ix.OWNER
WHERE ix.OWNER = 'TESTUSER';

-- 4. 触发器
SELECT TRIGGER_NAME, TABLE_NAME, TRIGGER_TYPE, STATUS
FROM ALL_TRIGGERS WHERE OWNER = 'TESTUSER';

-- 5. 存储过程/函数/包
SELECT OBJECT_NAME, OBJECT_TYPE, STATUS
FROM ALL_OBJECTS WHERE OWNER = 'TESTUSER'
AND OBJECT_TYPE IN ('PROCEDURE','FUNCTION','PACKAGE','PACKAGE BODY');

-- 6. 序列
SELECT SEQUENCE_NAME, LAST_NUMBER, MAX_VALUE, INCREMENT_BY
FROM ALL_SEQUENCES WHERE SEQUENCE_OWNER = 'TESTUSER';

-- 7. 视图
SELECT VIEW_NAME FROM ALL_VIEWS WHERE OWNER = 'TESTUSER';

-- 8. 字符集检查(关键)
SELECTVALUEFROM NLS_DATABASE_PARAMETERS WHERE PARAMETER = 'NLS_CHARACTERSET';
-- 预期:AL32UTF8(必须与 KES encoding 一致)

KES 端兼容性确认:

docker exec -it kingbase sh -c "ksql -U SYSTEM -W 123456 -d TESTDB"
-- 检查兼容模式(DB_MODE=oracle 已确认)
SHOW compatible_mode;     -- 预期:oracle

-- 检查字符集(必须与 Oracle 一致:AL32UTF8 → UTF8)
SHOW server_encoding;     -- 预期:UTF8

-- 检查 PG 语法兼容版本
SHOW pg_compat_version;   -- 预期:12(KES V9R1 内核对应 PG 12)

-- 检查已安装扩展
SELECT extname, extversion FROM pg_extension ORDERBY extname;

3.3 4 象限评估结论(示例)

象限
兼容性
高风险项
对象兼容
95%+
(+)
 外连接 → 改 ANSI JOIN;DBMS_JOB → 改 KES 调度(KDMS 翻译率覆盖不到的对象类型)
数据兼容
98%+
Oracle VARCHAR2 → KES VARCHAR2(oracle 模式)
应用兼容
92%+
JDBC URL:jdbc:oracle:thin: → jdbc:kingbase8:
性能兼容
待 E3 验证
迁移后压测

PL/SQL 翻译率估算(KDMS 报告示例):

存储过程(3个):2 个零修改,1 个 JSON 解析需调整 → 67% 零修改
触发器(3个):3 个零修改 → 100%
包(1个):1 个零修改 → 100%
视图(3个):3 个零修改 → 100%
序列(7个):0 个需改 → 100%

综合翻译率:~92% > 85% 阈值,无需原厂驻场

注:以上为 KDMS 评估报告的示例产出。生产实施前必须用 KDMS 对真实源库扫描,翻译率数值以实际报告为准——示例中的 92% 仅为本文示例 Schema 的估算结果。


四、E2 改造阶段(方言适配)

4.1 高频踩坑点(必须改造)

Oracle 写法
KES Oracle 模式
改造方式
WHERE a(+)=bWHERE a LEFT JOIN b
必须改 ANSI JOIN
SEQ.NEXTVALNEXTVAL('SEQ')
函数化调用
DBMS_JOB.SUBMIT
KES 调度(pg_cron 或 KES job scheduler)
用 KES 等价物
RAWTOHEX(x)SYS.RAWTOHEX(x)
 或 HEX(x)
视情况调整
SYSDATESYSDATE
✅ 直接支持
NVL
/DECODE/ROWNUM/CONNECT BY/DUAL
同名
✅ 直接支持
VARCHAR2
/NUMBER(p,s)/DATE
同名
✅ 直接支持

4.2 行为差异清单(不可见坑)

差异点
Oracle
KES
影响
空串 ''
等于 NULL
不等于 NULL
字符串判断逻辑可能反转
隐式类型转换
宽松
严格(Oracle 兼容模式)
WHERE int_col = '123'
 在 Oracle 通过,在 KES 报错;如需宽松行为需显式设置兼容参数(生产环境慎用)
DATE
 精度
含时间
仅日期
时间精度丢失
NUMBER
 无精度
浮点
整数
大数运算精度差异
ROWNUM
 行为
一致
一致
✅

⚠️ 模型 4 提醒:迁移代码必须做行为验证测试。

4.3 改造原则

  1. 零修改优先:能用 KES 直接兼容的写法不要改
  2. 必改项:DBMS_JOB、(+) 外连接、空串/NULL 行为
  3. 字符集绑定:源 AL32UTF8 → 目标 UTF8(initdb 时绑定,不可逆)
  4. 改造预算:翻译率 < 85% 时上浮 30%(启发式 2)

五、造数据(业务 Schema + 测试数据)

本节在 Oracle 容器中创建示例 Schema,覆盖迁移场景需要的各类对象。

5.1 创建用户和 Schema

-- SYSTEM 用户下执行
CREATETABLESPACE TBS_TEST
DATAFILE'/opt/oracle/oradata/TBS_TEST.dbf'
SIZE100M AUTOEXTENDONNEXT10M MAXSIZEUNLIMITED;

CREATEUSER TESTUSER IDENTIFIEDBY123456
DEFAULTTABLESPACE TBS_TEST
QUOTAUNLIMITEDON TBS_TEST;

GRANTCONNECT, RESOURCE, CREATEVIEW, CREATEPROCEDURE, CREATESEQUENCETO TESTUSER;

5.2 创建表结构

-- 切换到 TESTUSER
CONNECT TESTUSER/123456@//localhost/FREEPDB1;

-- 1. 部门表
CREATETABLE DEPARTMENTS (
    DEPT_ID     NUMBER(10)    PRIMARY KEY,
    DEPT_NAME   VARCHAR2(100) NOTNULL,
    DEPT_LOC    VARCHAR2(200),
    PARENT_ID   NUMBER(10),
STATUSVARCHAR2(10)  DEFAULT'ACTIVE',
    CREATE_DATE DATEDEFAULTSYSDATE,
CONSTRAINT CHK_STATUS CHECK (STATUSIN ('ACTIVE','INACTIVE','DELETED'))
);

-- 2. 员工表(含自引用外键 MGR_ID)
CREATETABLE EMPLOYEES (
    EMP_ID      NUMBER(10)    PRIMARY KEY,
    EMP_NAME    VARCHAR2(100) NOTNULL,
    EMP_NO      VARCHAR2(20)  UNIQUENOTNULL,
    DEPT_ID     NUMBER(10),
    HIRE_DATE   DATEDEFAULTSYSDATE,
    SALARY      NUMBER(12,2),
    COMM        NUMBER(12,2)  DEFAULT0,
    MGR_ID      NUMBER(10),
    EMAIL       VARCHAR2(100),
STATUSVARCHAR2(10)  DEFAULT'ACTIVE',
CONSTRAINT FK_DEPT FOREIGNKEY (DEPT_ID) REFERENCES DEPARTMENTS(DEPT_ID),
CONSTRAINT FK_MGR  FOREIGNKEY (MGR_ID)  REFERENCES EMPLOYEES(EMP_ID),
CONSTRAINT CHK_SAL CHECK (SALARY >= 0)
);

-- 3. 订单表
CREATETABLE ORDERS (
    ORDER_ID    NUMBER(20)    PRIMARY KEY,
    CUST_NAME   VARCHAR2(200) NOTNULL,
    EMP_ID      NUMBER(10),
    ORDER_DATE  DATEDEFAULTSYSDATE,
STATUSVARCHAR2(20)  DEFAULT'PENDING',
    TOTAL_AMT   NUMBER(18,2),
    CREATE_TIME TIMESTAMPDEFAULTCURRENT_TIMESTAMP,
CONSTRAINT FK_ORDER_EMP FOREIGNKEY (EMP_ID) REFERENCES EMPLOYEES(EMP_ID),
CONSTRAINT CHK_ORDER_STATUS CHECK (STATUSIN ('PENDING','CONFIRMED','SHIPPED','COMPLETED','CANCELLED')),
CONSTRAINT CHK_TOTAL CHECK (TOTAL_AMT >= 0)
);

-- 4. 订单明细
CREATETABLE ORDER_ITEMS (
    ITEM_ID     NUMBER(20)    PRIMARY KEY,
    ORDER_ID    NUMBER(20)    NOTNULL,
    PROD_ID     NUMBER(10),
    PROD_NAME   VARCHAR2(200),
    QTY         NUMBER(10)   NOTNULL,
    UNIT_PRICE  NUMBER(18,4),
CONSTRAINT FK_ITEM_ORDER FOREIGNKEY (ORDER_ID) REFERENCES ORDERS(ORDER_ID),
CONSTRAINT CHK_QTY CHECK (QTY > 0)
);

-- 5. 产品表
CREATETABLE PRODUCTS (
    PROD_ID      NUMBER(10)   PRIMARY KEY,
    PROD_NAME    VARCHAR2(200) NOTNULL,
    CATALOG_ID   NUMBER(10),
    PRICE        NUMBER(18,4) NOTNULL,
    STOCK_QTY    NUMBER(10)   DEFAULT0,
    LAST_PURCHASE DATE,
STATUSVARCHAR2(10)  DEFAULT'ON_SALE',
CONSTRAINT CHK_PROD_STATUS CHECK (STATUSIN ('ON_SALE','DISCONTINUED'))
);

-- 6. 产品分类
CREATETABLE CATALOGS (
    CATALOG_ID   NUMBER(10)   PRIMARY KEY,
    CATALOG_NAME VARCHAR2(100) NOTNULL,
    PARENT_ID    NUMBER(10),
    SORT_ORDER   NUMBER(5)    DEFAULT0
);

-- 7. 操作日志(触发器写入目标表)
CREATETABLE AUDIT_LOG (
    LOG_ID     NUMBER(20)    PRIMARY KEY,
    TABLE_NAME VARCHAR2(50),
ACTIONVARCHAR2(20),
    OLD_VAL    VARCHAR2(4000),
    NEW_VAL    VARCHAR2(4000),
    OPER_USER  VARCHAR2(100),
    OPER_TIME  DATEDEFAULTSYSDATE,
    IP_ADDR    VARCHAR2(50)
);

5.3 创建序列

CREATESEQUENCE SEQ_DEPT    STARTWITH1INCREMENTBY1NOMAXVALUE;
CREATESEQUENCE SEQ_EMP    STARTWITH1INCREMENTBY1NOMAXVALUE;
CREATESEQUENCE SEQ_ORDER  STARTWITH1INCREMENTBY1NOMAXVALUE;
CREATESEQUENCE SEQ_ITEM   STARTWITH1INCREMENTBY1NOMAXVALUE;
CREATESEQUENCE SEQ_PROD   STARTWITH1INCREMENTBY1NOMAXVALUE;
CREATESEQUENCE SEQ_CATALOG STARTWITH1INCREMENTBY1NOMAXVALUE;
CREATESEQUENCE SEQ_LOG    STARTWITH1INCREMENTBY1NOMAXVALUE;

5.4 创建触发器

-- 触发器1:员工入职自动记录日志
CREATEORREPLACETRIGGER TRG_EMP_INSERT
AFTERINSERTON EMPLOYEES
FOREACHROW
BEGIN
INSERTINTO AUDIT_LOG (LOG_ID, TABLE_NAME, ACTION, NEW_VAL, OPER_USER, OPER_TIME)
VALUES (SEQ_LOG.NEXTVAL, 'EMPLOYEES', 'INSERT',
'{"EMP_ID":' || :NEW.EMP_ID || ',"NAME":"' || :NEW.EMP_NAME || '"}',
USER, SYSDATE);
END;
/

-- 触发器2:部门更新自动记录
CREATEORREPLACETRIGGER TRG_DEPT_UPDATE
AFTERUPDATEON DEPARTMENTS
FOREACHROW
BEGIN
INSERTINTO AUDIT_LOG (LOG_ID, TABLE_NAME, ACTION, OLD_VAL, NEW_VAL, OPER_USER, OPER_TIME)
VALUES (SEQ_LOG.NEXTVAL, 'DEPARTMENTS', 'UPDATE',
'{"DEPT_ID":' || :OLD.DEPT_ID || ',"NAME":"' || :OLD.DEPT_NAME || '"}',
'{"DEPT_ID":' || :NEW.DEPT_ID || ',"NAME":"' || :NEW.DEPT_NAME || '"}',
USER, SYSDATE);
END;
/

-- 触发器3:订单状态变更记录
CREATEORREPLACETRIGGER TRG_ORDER_STATUS
BEFOREUPDATEOFSTATUSON ORDERS
FOREACHROW
WHEN (NEW.STATUS != OLD.STATUS)
BEGIN
INSERTINTO AUDIT_LOG (LOG_ID, TABLE_NAME, ACTION, OLD_VAL, NEW_VAL, OPER_USER, OPER_TIME)
VALUES (SEQ_LOG.NEXTVAL, 'ORDERS', 'STATUS_CHANGE',
'{"ORDER_ID":' || :OLD.ORDER_ID || ',"OLD_STATUS":"' || :OLD.STATUS || '"}',
'{"ORDER_ID":' || :NEW.ORDER_ID || ',"NEW_STATUS":"' || :NEW.STATUS || '"}',
USER, SYSDATE);
END;
/

5.5 创建存储过程

-- 存储过程1:部门统计
CREATEORREPLACEPROCEDURE P_GET_DEPT_STATS(
    P_DEPT_ID   INNUMBER,
    P_EMP_COUNT OUTNUMBER,
    P_AVG_SAL   OUTNUMBER,
    P_TOTAL_SAL OUTNUMBER
) AS
BEGIN
SELECTCOUNT(*), NVL(AVG(SALARY),0), NVL(SUM(SALARY),0)
INTO P_EMP_COUNT, P_AVG_SAL, P_TOTAL_SAL
FROM EMPLOYEES
WHERE DEPT_ID = P_DEPT_ID ANDSTATUS = 'ACTIVE';
END P_GET_DEPT_STATS;
/

-- 存储过程2:创建订单
CREATEORREPLACEPROCEDURE P_CREATE_ORDER(
    P_CUST_NAME  INVARCHAR2,
    P_EMP_ID     INNUMBER,
    P_ITEMS      INVARCHAR2,
    P_ORDER_ID   OUTNUMBER
) AS
    V_ORDER_ID NUMBER;
BEGIN
    V_ORDER_ID := SEQ_ORDER.NEXTVAL;
INSERTINTO ORDERS (ORDER_ID, CUST_NAME, EMP_ID, ORDER_DATE, STATUS, TOTAL_AMT)
VALUES (V_ORDER_ID, P_CUST_NAME, P_EMP_ID, SYSDATE, 'PENDING', 0);
    P_ORDER_ID := V_ORDER_ID;
END P_CREATE_ORDER;
/

-- 存储过程3:月度工资跑批(含 DBMS_OUTPUT,验证 PL/SQL 包)
-- 注意:KES 不支持 Oracle 的 SQL%ROWCOUNT 隐式游标属性,需要先 INTO 变量
CREATEORREPLACEPROCEDURE P_MONTHLY_SALARY_RUN(P_MONTH INVARCHAR2) AS
    V_COUNT NUMBER;
BEGIN
SELECTCOUNT(*) INTO V_COUNT FROM EMPLOYEES WHERESTATUS = 'ACTIVE';
INSERTINTO AUDIT_LOG(LOG_ID, TABLE_NAME, ACTION, NEW_VAL, OPER_USER, OPER_TIME)
SELECT SEQ_LOG.NEXTVAL, 'SALARY_RUN', 'BATCH',
'{"MONTH":"' || P_MONTH || '","EMP_COUNT":' || V_COUNT || '}',
USER, SYSDATE;
    DBMS_OUTPUT.PUT_LINE('月度工资跑批完成: ' || P_MONTH || ', 员工数: ' || V_COUNT);
END P_MONTHLY_SALARY_RUN;
/

5.6 创建视图(含一个 ANSI 写法验证)

-- 视图1:员工详情(用 ANSI JOIN,验证 KES 支持)
CREATEORREPLACEVIEW V_EMP_DETAIL AS
SELECT e.EMP_ID, e.EMP_NAME, e.EMP_NO, e.HIRE_DATE,
       d.DEPT_NAME, d.DEPT_LOC,
       e.SALARY, e.COMM,
       m.EMP_NAME AS MGR_NAME, e.EMAIL
FROM EMPLOYEES e
LEFTJOIN DEPARTMENTS d ON e.DEPT_ID = d.DEPT_ID
LEFTJOIN EMPLOYEES m  ON e.MGR_ID  = m.EMP_ID
WHERE e.STATUS = 'ACTIVE';

-- 视图2:订单汇总
CREATEORREPLACEVIEW V_ORDER_SUMMARY AS
SELECT o.ORDER_ID, o.CUST_NAME, o.ORDER_DATE, o.STATUS, o.TOTAL_AMT,
       e.EMP_NAME AS SALES_REP
FROM ORDERS o
LEFTJOIN EMPLOYEES e ON o.EMP_ID = e.EMP_ID
WHERE o.ORDER_DATE >= TRUNC(SYSDATE, 'MM');

-- 视图3:库存预警
CREATEORREPLACEVIEW V_STOCK_ALERT AS
SELECT PROD_ID, PROD_NAME, STOCK_QTY, PRICE,
CASEWHEN STOCK_QTY < 10THEN'LOW'
WHEN STOCK_QTY < 50THEN'MEDIUM'
ELSE'OK'ENDAS STOCK_LEVEL
FROM PRODUCTS
WHERESTATUS = 'ON_SALE'AND STOCK_QTY < 50
ORDERBY STOCK_QTY;

5.7 创建 PL/SQL 包

-- 包规范
CREATEORREPLACEPACKAGE PKG_EMP_MANAGE AS
PROCEDURE HIRE_EMP(P_NAME INVARCHAR2, P_NO INVARCHAR2,
        P_DEPT_ID INNUMBER, P_SALARY INNUMBER,
        P_EMAIL INVARCHAR2, P_MGR_ID INNUMBERDEFAULTNULL);
    PROCEDURE FIRE_EMP(P_EMP_ID IN NUMBER);
    FUNCTION  GET_EMP_COUNT(P_DEPT_ID IN NUMBER) RETURN NUMBER;
    PROCEDURE TRANSFER_EMP(P_EMP_ID IN NUMBER, P_NEW_DEPT_ID IN NUMBER);
END PKG_EMP_MANAGE;
/

-- 包体
CREATEORREPLACEPACKAGEBODY PKG_EMP_MANAGE AS

PROCEDURE HIRE_EMP(P_NAME INVARCHAR2, P_NO INVARCHAR2,
        P_DEPT_ID INNUMBER, P_SALARY INNUMBER,
        P_EMAIL INVARCHAR2, P_MGR_ID INNUMBERDEFAULTNULL) AS
        V_EMP_ID NUMBER;
BEGIN
        V_EMP_ID := SEQ_EMP.NEXTVAL;
INSERTINTO EMPLOYEES (EMP_ID, EMP_NAME, EMP_NO, DEPT_ID, HIRE_DATE, SALARY, EMAIL, MGR_ID, STATUS)
VALUES (V_EMP_ID, P_NAME, P_NO, P_DEPT_ID, SYSDATE, P_SALARY, P_EMAIL, P_MGR_ID, 'ACTIVE');
END HIRE_EMP;

    PROCEDURE FIRE_EMP(P_EMP_ID IN NUMBER) AS
BEGIN
UPDATE EMPLOYEES SETSTATUS = 'INACTIVE'WHERE EMP_ID = P_EMP_ID;
INSERTINTO AUDIT_LOG (LOG_ID, TABLE_NAME, ACTION, NEW_VAL, OPER_USER, OPER_TIME)
VALUES (SEQ_LOG.NEXTVAL, 'EMPLOYEES', 'FIRE', '{"EMP_ID":' || P_EMP_ID || '}', USER, SYSDATE);
END FIRE_EMP;

    FUNCTION GET_EMP_COUNT(P_DEPT_ID IN NUMBER) RETURN NUMBER AS
        V_COUNT NUMBER;
BEGIN
SELECTCOUNT(*) INTO V_COUNT FROM EMPLOYEES WHERE DEPT_ID = P_DEPT_ID ANDSTATUS = 'ACTIVE';
        RETURN V_COUNT;
END GET_EMP_COUNT;

    PROCEDURE TRANSFER_EMP(P_EMP_ID IN NUMBER, P_NEW_DEPT_ID IN NUMBER) AS
BEGIN
UPDATE EMPLOYEES SET DEPT_ID = P_NEW_DEPT_ID WHERE EMP_ID = P_EMP_ID;
INSERTINTO AUDIT_LOG (LOG_ID, TABLE_NAME, ACTION, NEW_VAL, OPER_USER, OPER_TIME)
VALUES (SEQ_LOG.NEXTVAL, 'EMPLOYEES', 'TRANSFER',
'{"EMP_ID":' || P_EMP_ID || ',"NEW_DEPT":' || P_NEW_DEPT_ID || '}',
USER, SYSDATE);
END TRANSFER_EMP;
END PKG_EMP_MANAGE;
/

5.8 插入测试数据

-- 分类(先于产品)
INSERTINTO CATALOGS VALUES (SEQ_CATALOG.NEXTVAL, '电子产品', NULL, 1);
INSERTINTO CATALOGS VALUES (SEQ_CATALOG.NEXTVAL, '办公用品', NULL, 2);
INSERTINTO CATALOGS VALUES (SEQ_CATALOG.NEXTVAL, '电脑配件', 1, 1);

-- 部门
INSERTINTO DEPARTMENTS VALUES (SEQ_DEPT.NEXTVAL, '技术研发部', 'A栋5楼', NULL, 'ACTIVE', SYSDATE);
INSERTINTO DEPARTMENTS VALUES (SEQ_DEPT.NEXTVAL, '市场营销部', 'B栋3楼', NULL, 'ACTIVE', SYSDATE);
INSERTINTO DEPARTMENTS VALUES (SEQ_DEPT.NEXTVAL, '财务部',     'A栋6楼', NULL, 'ACTIVE', SYSDATE);
INSERTINTO DEPARTMENTS VALUES (SEQ_DEPT.NEXTVAL, '人力资源部', 'B栋2楼', NULL, 'ACTIVE', SYSDATE);

-- 员工
INSERTINTO EMPLOYEES VALUES (SEQ_EMP.NEXTVAL, '张总',   'E001', NULL, DATE'2020-01-01', 50000, NULL, NULL,  '[email protected]', 'ACTIVE');
INSERTINTO EMPLOYEES VALUES (SEQ_EMP.NEXTVAL, '李经理', 'E002', 1,     DATE'2021-03-15', 25000, 5000, 1,     '[email protected]',    'ACTIVE');
INSERTINTO EMPLOYEES VALUES (SEQ_EMP.NEXTVAL, '王工',   'E003', 1,     DATE'2022-06-01', 15000, NULL, 2,     '[email protected]',  'ACTIVE');
INSERTINTO EMPLOYEES VALUES (SEQ_EMP.NEXTVAL, '刘工',   'E004', 1,     DATE'2023-01-10', 12000, NULL, 2,     '[email protected]',   'ACTIVE');
INSERTINTO EMPLOYEES VALUES (SEQ_EMP.NEXTVAL, '陈经理', 'E005', 2,     DATE'2021-07-01', 22000, 8000, 1,     '[email protected]',  'ACTIVE');
INSERTINTO EMPLOYEES VALUES (SEQ_EMP.NEXTVAL, '赵销售', 'E006', 2,     DATE'2024-02-20', 10000, 20000,5,     '[email protected]',  'ACTIVE');

-- 产品
INSERTINTO PRODUCTS VALUES (SEQ_PROD.NEXTVAL, 'ThinkPad X1 Carbon', 3, 12999.00, 50,  DATE'2026-01-15', 'ON_SALE');
INSERTINTO PRODUCTS VALUES (SEQ_PROD.NEXTVAL, 'Dell 27寸显示器',    3,  2999.00, 120, DATE'2026-02-01', 'ON_SALE');
INSERTINTO PRODUCTS VALUES (SEQ_PROD.NEXTVAL, '罗技 MX Keys',        2,   599.00, 200, DATE'2026-03-10', 'ON_SALE');
INSERTINTO PRODUCTS VALUES (SEQ_PROD.NEXTVAL, '华为 MateBook 14',   3,  7999.00, 8,   DATE'2026-06-20', 'ON_SALE');
INSERTINTO PRODUCTS VALUES (SEQ_PROD.NEXTVAL, 'A4 复印纸(500张)', 2,    25.00, 500, DATE'2026-07-01', 'ON_SALE');

-- 订单
INSERTINTO ORDERS VALUES (SEQ_ORDER.NEXTVAL, '北京科技有限公司', 6, DATE'2026-08-01', 'COMPLETED', 89999.00, SYSTIMESTAMP);
INSERTINTO ORDERS VALUES (SEQ_ORDER.NEXTVAL, '上海贸易公司',     6, DATE'2026-08-05', 'SHIPPED',   45999.00, SYSTIMESTAMP);
INSERTINTO ORDERS VALUES (SEQ_ORDER.NEXTVAL, '深圳创新企业',     6, DATE'2026-08-10', 'PENDING',   15999.00, SYSTIMESTAMP);
INSERTINTO ORDERS VALUES (SEQ_ORDER.NEXTVAL, '广州实业集团',     6, DATE'2026-08-12', 'CONFIRMED', 12999.00, SYSTIMESTAMP);

-- 订单明细
INSERTINTO ORDER_ITEMS VALUES (SEQ_ITEM.NEXTVAL, 1, 1, 'ThinkPad X1 Carbon', 3, 12999.00);
INSERTINTO ORDER_ITEMS VALUES (SEQ_ITEM.NEXTVAL, 1, 2, 'Dell 27寸显示器',   10, 2999.00);
INSERTINTO ORDER_ITEMS VALUES (SEQ_ITEM.NEXTVAL, 2, 1, 'ThinkPad X1 Carbon', 2, 12999.00);
INSERTINTO ORDER_ITEMS VALUES (SEQ_ITEM.NEXTVAL, 2, 4, '华为 MateBook 14',   3, 7999.00);
INSERTINTO ORDER_ITEMS VALUES (SEQ_ITEM.NEXTVAL, 3, 5, '罗技 MX Keys',       10, 599.00);
INSERTINTO ORDER_ITEMS VALUES (SEQ_ITEM.NEXTVAL, 4, 1, 'ThinkPad X1 Carbon', 1, 12999.00);

COMMIT;

5.9 验证数据完整性

SELECT'DEPARTMENTS'AS TBL, COUNT(*) AS CNT FROM DEPARTMENTS
UNIONALLSELECT'EMPLOYEES',     COUNT(*) FROM EMPLOYEES
UNIONALLSELECT'PRODUCTS',      COUNT(*) FROM PRODUCTS
UNIONALLSELECT'ORDERS',        COUNT(*) FROM ORDERS
UNIONALLSELECT'ORDER_ITEMS',   COUNT(*) FROM ORDER_ITEMS
UNIONALLSELECT'AUDIT_LOG',     COUNT(*) FROM AUDIT_LOG;

-- 预期:
-- DEPARTMENTS: 4
-- EMPLOYEES:   6
-- PRODUCTS:    5
-- ORDERS:      4
-- ORDER_ITEMS: 6
-- AUDIT_LOG:   0(触发器会在 INSERT/UPDATE 时自动写入)

六、E3 测试阶段(KDTS 全量 + KFS 同步)

6.1 Oracle 端前置配置(KFS 前置条件)

⚠️ 重要(基于《KFS 生命周期管理手册》§7.1.1):KFS 对 Oracle 提供两种增量抽取方式,前置条件不同!

抽取方式
前置条件
适用场景
Logminer
开归档 + 开补充日志(SUPPLEMENTAL LOG DATA)
KFS 与 Oracle 不同主机;不含大对象/不依赖 DDL 同步
REDO
开归档 + 开补充日志
KFS 必须与 Oracle 同机部署;支持大对象、支持 DDL 同步、性能更快
REDO + 日志代理(官方推荐的无侵入方案)
开归档 + 开补充日志 + 单独的代理服务机器
Oracle 端仅部署轻量日志读取代理;解析在外部"体外机"完成;省 Oracle 资源、保留 REDO 全部优点

本文默认采用 Logminer 模式(KFS 在 Linux 容器,Oracle 在 Docker 容器)。

如果要测试大对象(BLOB/CLOB)或 DDL 同步场景,必须采用 REDO 模式——这意味着 KFS 必须部署在 Oracle 容器内部(同机),生产环境慎用。

💡 金仓 KFS 无侵入增量数据同步方案(推荐生产环境采用该方案),无需KFS与Oracle同机部署, 并且功能齐全:

  • 在 Oracle 端仅部署轻量化的增量日志读取代理服务(导管),只负责实时捕获归档日志,CPU/内存占用极低
  • 抓取的日志通过专线实时传输到 KFS 源端"体外机" 上,由体外机完成解析、缓存、排序等重活
  • 效果:Oracle 端资源占用 ≈ 1% CPU / 500MB 内存,传统集中部署要 10% CPU / 2GB 内存
  • 把 REDO 模式"同机部署"的硬约束变成了"代理 + 远程传输",省下 Oracle 容器资源,又不损失 REDO 优点
  • 适用:核心业务系统、对生产库稳定性要求极高的场景

本文后续默认仍以 Logminer 模式为主线,生产环境则强烈建议优先评估无侵入方案。

无侵入方案专属拓扑:

Image
Image

关键边界:

  • 日志代理服务与 KFS 源端服务不在同一台机器——这是无侵入方案区别于传统 KFS-REDO 集中部署的核心
  • Oracle 端只增加日志代理(≈ 1% CPU / 500MB),重活在体外机,生产库零侵入
  • 归档日志传输推荐专线,稳定性优于公网;KSync 是金仓的另一种传输手段(取决于实施版本)
  • 对应的 KFS 抽取方式仍是 REDO 模式(不是 Logminer),所以支持大对象、支持 DDL、解析性能高——与原 REDO 模式等价,且无需与 Oracle 实例部署在同一主机
-- Oracle DBA 执行
CONNECT SYS/123456 AS SYSDBA;

-- 1. 开启补充日志
ALTERDATABASEADD SUPPLEMENTAL LOGDATA (ALL) COLUMNS;
ALTERDATABASEADD SUPPLEMENTAL LOGDATA (PRIMARY KEY) COLUMNS;
ALTERDATABASEADD SUPPLEMENTAL LOGDATA (UNIQUE) COLUMNS;

-- 2. 验证
SELECT SUPPLEMENTAL_LOG_DATA_ALL,
       SUPPLEMENTAL_LOG_DATA_PK,
       SUPPLEMENTAL_LOG_DATA_UI
FROM V$DATABASE;
-- 预期:全部 YES

-- 3. 开启归档(KFS 默认需要归档模式)
SHUTDOWN IMMEDIATE;
STARTUP MOUNT;
ALTERDATABASEARCHIVELOG;
ALTERDATABASEOPEN;

-- 5. 验证归档
SELECT log_mode FROM V$DATABASE;
-- 预期:ARCHIVELOG

6.2 安装 KFS(Linux 容器内运行)

💡 KFS 容器是本文方案的"第三个容器"——Oracle 容器(1521)和 KES 容器(54321)之间,需要一个独立的 Linux 容器跑 KFS 同步服务(端口 8090/8091)。三个容器通过 host.docker.internal 或宿主机 IP 互通。

KFS 安装包和文档:

  • 产品页:https://www.kingbase.com.cn/product/details_552_584.html
  • 技术社区:https://bbs.kingbase.com.cn/documentGuide?recId=c3f448eede450dfbd8cadf68a991b40f
  • 部署参考:命令行工具手册(包含 fspm / replicator / fsrepctl 等)

部署步骤(Linux 容器内):

# 1. 启动 KFS 容器(macOS ARM64 推荐 arm64v8/ubuntu 或 kingbase 官方基础镜像)
docker run -tid --privileged --name kfs \
  -p 8090:8090 \
  -p 8091:8091 \
  -p 3112:3112 \
  -p 11000:11000 \
  --add-host=host.docker.internal:host-gateway \
  arm64v8/ubuntu:22.04 \
  /usr/sbin/init

docker exec -it kfs bash

# 2. 上传 KFS 安装包到 Linux 容器(macOS 宿主机 → Linux KFS 容器)
docker cp ~/Downloads/KingbaseFlySync_xxx_linux.tar.gz kfs:/tmp/

# 3. 解压并配置环境变量
cd /tmp
tar -xzf KingbaseFlySync_xxx_linux.tar.gz
cd KingbaseFlySync
export KFS_HOME=/opt/kingbase/flysync
source${KFS_HOME}/global_env.sh

# 4. 使用 fspm 生成配置 + 安装服务(不是 init-mgr)
${KFS_HOME}/bin/fspm configure    # 交互式生成 flysync.ini
${KFS_HOME}/bin/fspm install       # 安装同步服务组件

# 5. 启动 replicator 服务
${KFS_HOME}/bin/replicator start
${KFS_HOME}/bin/replicator status  # 验证服务状态

# 6. 验证 Manager 健康(管理控制台 8090)
curl -s http://localhost:8090/api/v2/health
# 预期输出:{"status":"UP"}

核心命令清单(基于《KFS 命令行工具参考手册》):

命令
作用
fspm configure/install/delete-service/update
包管理(生成 flysync.ini / 安装服务)
replicator start/stop/restart/status
服务生命周期管理
fsrepctl status/online/offline/purge/reset/load
服务运行态控制
ddlscan
DDL 同步扫描
loader
数据装载工具
repkeyclean
处理无主键表的过滤器

6.3 KFS 架构与数据流

Image

6.4 配置 KFS 全量+增量同步任务

⚠️ 用 fspm 生成 flysync.ini 配置文件,再通过 replicator 控制服务启停,fsrepctl 控制服务状态。

通过 KFS 管理控制台(http://localhost:8090):

  1. 创建源端连接(Oracle)
数据源 → 新建 → Oracle
  名称: ORA19C_SOURCE
  主机: <Oracle 宿主机 IP>
  端口: 1521
  SID:   FREEPDB1
  用户:  SYSTEM
  密码:  123456
  字符集: AL32UTF8
  补充日志: 已启用(验证)
  抽取方式: Logminer 或 REDO(见 6.1 节选择)
  1. 创建目标端连接(KES)
数据源 → 新建 → KingbaseES
  名称: KES_TARGET
  主机: <KES 宿主机 IP>
  端口: 54321
  数据库: TESTDB
  用户:   kingbase
  密码:   123456
  兼容模式: oracle
  1. 创建同步服务(在管理控制台或编辑 flysync.ini)
同步管理 → 新建服务
  服务名称: ORA_TO_KES_SYNC
  源端: ORA19C_SOURCE
  目标端: KES_TARGET
  同步对象:
    - TESTUSER.DEPARTMENTS
    - TESTUSER.EMPLOYEES
    - TESTUSER.PRODUCTS
    - TESTUSER.CATALOGS
    - TESTUSER.ORDERS
    - TESTUSER.ORDER_ITEMS
    - TESTUSER.AUDIT_LOG
  同步类型: 全量初始化 + 增量实时同步
  启动位置: NOW (增量起点)
  DDL 同步: 启用(仅 REDO 模式支持,Logminer 模式不支持 DDL 同步)

或通过配置文件 flysync.ini 关键字段(部分示例):

# flysync.ini 关键配置段(参考《KFS 命令行工具参考手册》)
# [defaults]
# 关键必选项:处理无主键表的复制标识(来自《KES 无中间库不停机迁移方案》:源端 ini 中务必配置)
replicator.extractor.dbms.autoIdentity=full
replicator.service.thlParallelizationStyle=disk

# 启动/停止服务(在 fspm install 之后)
${KFS_HOME}/bin/replicator start
${KFS_HOME}/bin/fsrepctl -service ORA_TO_KES_SYNC status
${KFS_HOME}/bin/fsrepctl -service ORA_TO_KES_SYNC online

⚠️ replicator.extractor.dbms.autoIdentity=full 是源端 ini 的必须配置项(来自《KES 无中间库不停机迁移方案》原文):当源端表无主键或无唯一索引时,KFS 会持续在日志里刷 alter table xx replicate identity full,同步会卡在这一步。配置 autoIdentity=full 后 KFS 会自动处理复制标识,无需人工干预。

6.5 启动并验证同步

# 启动同步服务(基于 replicator + fsrepctl)
${KFS_HOME}/bin/replicator start
${KFS_HOME}/bin/replicator status    # 验证服务状态

# 查看同步链路状态
${KFS_HOME}/bin/fsrepctl -service ORA_TO_KES_SYNC status

# 查看性能
${KFS_HOME}/bin/fsrepctl perf
# 预期:延迟接近 0(秒级),吞吐量显示每秒同步行数

6.6 KES 端数据一致性校验

数据一致性校验统一使用 KFS 自带的比对服务(精简/详细两种模式,端口 8091)。KDMS 不提供两端数据比对功能,只负责结构迁移和 4 象限兼容性评估。

6.6.1 模式选择

模式
适用
优缺点
精简校验(记录数 + 校验和)
大表、校验时间紧
只校验行数和聚合校验和,速度快;不能识别字段级差异
详细校验(逐行逐列)
核心表、数据准确性要求高
识别字段级差异,可推平;耗 CPU/IO,大表慎用

校验频率建议:业务低峰期(如凌晨 3 点)执行;交易类系统按日周期,集采类系统按周周期。差异处理流程详见《KFS 生命周期管理手册》§9.3.1.4。

6.6.2 KFS 比对服务实际调用

通过管控平台(8090)发起比对任务:

# 1. 进入比对服务入口(图形化):管控平台 → 数据比对 → 新建比对任务
#    - 服务: ORA_TO_KES_SYNC
#    - 模式: 精简 / 详细
#    - 对象: TESTUSER.* (按需勾选表)

# 2. 通过 fsrepctl 也可以查询比对状态(命令行方式)
${KFS_HOME}/bin/fsrepctl -service ORA_TO_KES_SYNC status
# 预期:比对进度 100%,差异数 0

6.6.3 辅助手段:人工记录数比对(割接窗口快速验证)

割接窗口(T-5min)场景下时间紧张,可以用人工记录数比对做最朴素的快速验证:

docker exec -it kingbase sh -c "ksql -U SYSTEM -W 123456 -d TESTDB"
-- 逐表对照 COUNT(*)
SELECT'DEPARTMENTS'AS TBL, COUNT(*) AS CNT FROM TESTUSER.DEPARTMENTS
UNIONALLSELECT'EMPLOYEES',     COUNT(*) FROM TESTUSER.EMPLOYEES
UNIONALLSELECT'PRODUCTS',      COUNT(*) FROM TESTUSER.PRODUCTS
UNIONALLSELECT'ORDERS',        COUNT(*) FROM TESTUSER.ORDERS
UNIONALLSELECT'ORDER_ITEMS',   COUNT(*) FROM TESTUSER.ORDER_ITEMS
UNIONALLSELECT'AUDIT_LOG',     COUNT(*) FROM TESTUSER.AUDIT_LOG;

Oracle 端同样 SQL 比对记录数。生产环境的完整校验仍以 §6.6.2 KFS 比对服务为准。

6.7 增量同步验证(关键)

在 Oracle 端产生增量,观察 KES 端实时同步:

-- Oracle 端:插入新员工(触发器会自动写 AUDIT_LOG)
CONNECT TESTUSER/123456@//localhost/FREEPDB1;

CALL PKG_EMP_MANAGE.HIRE_EMP('钱七', 'E007', 1, 18000, '[email protected]', 2);
CALL PKG_EMP_MANAGE.HIRE_EMP('孙八', 'E008', 2, 12000, '[email protected]', 5);

UPDATE DEPARTMENTS SET DEPT_LOC = 'A栋7楼'WHERE DEPT_ID = 1;

-- 修改订单状态
UPDATE ORDERS SETSTATUS = 'SHIPPED'WHERE ORDER_ID = 3;
COMMIT;

等待 2-3 秒后,KES 端验证:

-- KES 端:检查增量数据
SELECT EMP_ID, EMP_NAME, EMP_NO, SALARY FROM TESTUSER.EMPLOYEES
WHERE EMP_ID IN (7, 8);

-- 验证触发器同步
SELECT * FROM TESTUSER.AUDIT_LOG ORDERBY LOG_ID DESCLIMIT10;

-- 验证 UPDATE 同步
SELECT DEPT_ID, DEPT_LOC FROM TESTUSER.DEPARTMENTS WHERE DEPT_ID = 1;

-- 验证订单状态变更
SELECT ORDER_ID, STATUSFROM TESTUSER.ORDERS WHERE ORDER_ID = 3;

✅ KFS 同步延迟正常应在秒级。如果出现"插入了 10 条但 KES 只同步 5 条"或"延迟持续 > 10 秒",检查 KFS 管理控制台的告警面板。


七、E4 割接阶段(业务不停机)

7.1 割接前 7 步检查清单

#
检查项
命令
预期
1
Oracle 补充日志
SELECT SUPPLEMENTAL_LOG_DATA_ALL FROM V$DATABASE
YES
2
Oracle 归档模式
SELECT log_mode FROM V$DATABASE
ARCHIVELOG
3
KFS 同步状态
管理控制台
running
4
KFS 同步延迟
管理控制台监控
< 1 秒
5
KES 数据完整
KFS 比对服务(或人工记录数比对)
100% 一致
6
KES 性能基线
sys_stat_statements
 TOP 10
已采集
7
反向 KFS 链路(KES_TO_ORA_REVERSE)就绪
fsrepctl -service KES_TO_ORA_REVERSE status
(应保持 offline)
30 秒可切回

7.2 业务割接时间线

⚠️ 关键:本文采用 "KDTS + KFS + 反向 KFS 链路 + 应用切换" 的双轨不停机迁移,割接流程中必须包含 KFS 同步方向切换步骤——即 T-13min 把正向 KFS 链路(ORA_TO_KES_SYNC)置为 offline 并 drained,T-8min 切换应用 JDBC。

T-30min  ⚠️ 停止 Oracle 端业务写入(应用层或 DB 端)
T-25min  ✅ 确认 KFS 同步延迟 = 0
T-15min  📊 启动 KWR 快照(采集基线)
         SELECT kwr_snapshot.create_snapshot();
T-13min  🔄 把 ORA_TO_KES_SYNC 链路置为 offline(暂停同步,不再从 Oracle 取增量)
         - 命令: fsrepctl -service ORA_TO_KES_SYNC offline
T-10min  ⏳ 等待 KFS 链路 drained(关键!避免 KES 端有未应用的增量就切应用)
         - 命令: fsrepctl -service ORA_TO_KES_SYNC status
         - 预期: pending events = 0,applied seqno 与最新归档一致
         - 或执行 KFS 比对服务(精简模式)确认记录数一致
T-8min   🔄 修改应用 JDBC URL:jdbc:oracle:thin: → jdbc:kingbase8:
         - 连接串:jdbc:kingbase8://host:54321/TESTDB
         - 用户名:TESTUSER(与 Oracle 同名)
         - 驱动:com.kingbase8.Driver
T-5min   🧪 应用层冒烟测试(核心 3-5 个 SQL)
T-0min   🚀 业务恢复,指向 KES
T+10min  📊 监控核心业务 SQL 响应时间(P95/P99)
T+30min  ✅ 确认无异常,启动新一轮 KWR 快照
T+24h    📊 第二次 KWR 采样,确认稳定

KFS 同步方向切换(与上面时间线并行):

这一步是双轨不停机方案区别于 KDTS-WEB 一次性迁移的关键——切换前先把 KFS 链路置为"末态",避免切应用后 KFS 仍然从 Oracle 写 KES 造成意外抖动。

# T-13min ~ T-10min 之间执行(在应用 JDBC 切换前)
# 1. 把 ORA_TO_KES_SYNC 链路置为 offline(暂停同步,不再从 Oracle 取增量)
${KFS_HOME}/bin/fsrepctl -service ORA_TO_KES_SYNC offline

# 2. 等待 KFS 链路 drained(关键步骤)
#    命令:fsrepctl -service ORA_TO_KES_SYNC status
#    预期:pending events = 0,applied seqno 与最新归档一致
#    或执行 KFS 比对服务(精简模式)确认记录数一致
#    只有 drained 完成后才能继续切应用

# 3. 应用切换完成、业务指向 KES 后,反向 KFS 链路(KES_TO_ORA_REVERSE)保持 offline
#    仅在 E5 触发回退时才置为 online

反向 KFS 链路的预置状态(割接前已经完成):

# 6.x 阶段已经完成:
# - 服务 KES_TO_ORA_REVERSE 已用 fspm 配置好(slave 角色,指向 KES 源端 KUFL)
# - 状态保持 offline,不主动同步
# - 仅在 E5 触发回退时执行:fsrepctl -service KES_TO_ORA_REVERSE online

7.3 应用层切换关键点

// 旧(Oracle)
jdbc:oracle:thin:@//oracle-host:1521/FREEPDB1

// 新(KES Oracle 兼容模式)
jdbc:kingbase8://kes-host:54321/TESTDB

// 驱动类
oracle.jdbc.driver.OracleDriver
→ com.kingbase8.Driver

连接池配置调整:

# 旧(Oracle)
hikari.connection-test-query=SELECT 1 FROM DUAL
hikari.driver-class-name=oracle.jdbc.driver.OracleDriver

# 新(KES)
hikari.connection-test-query=SELECT 1
hikari.driver-class-name=com.kingbase8.Driver

7.4 常见割接问题

问题
原因
解决方案
中文乱码
encoding 不匹配
KES initdb 与 Oracle 同 UTF8(启发式 3)
ORA-00942: 表不存在
JDBC URL 未切换
应用层切换
ERROR: permission denied
KES 用户权限不足
GRANT ALL ON SCHEMA testUSER TO testUSER
data type incompatibility
KES 严格类型检查
显式类型转换
触发器没同步
KFS DDL/DML 配置
检查 KFS 服务 DDL 同步选项

八、E5 回退阶段(30 秒切回源库)

8.1 回退条件(任一触发即回退)

  • ❌ KES 端性能 P95 延迟 > Oracle 端 2 倍
  • ❌ KES 端出现数据不一致(KFS 比对服务校验失败)
  • ❌ 应用层在 KES 端连续报错
  • ❌ 业务系统核心功能不可用

8.2 30 秒回退步骤

方案 A:应用层切回(最快,30 秒):

# 1. 修改应用配置,将 jdbc:kingbase8:// 改回 jdbc:oracle:thin://
# 2. 重启应用(连接池切换)
# 3. 验证 Oracle 端可读写

方案 B:反向 KFS 链路(应急用):

# KFS 支持双向同步(基于 setrole 命令,参考《KFS 命令行工具参考手册》§6.2.20)
# 1. 把 ORA_TO_KES_SYNC 链路角色切换为 slave,让 KES 端成为新主
${KFS_HOME}/bin/fsrepctl -service ORA_TO_KES_SYNC setrole -role slave \
  -url kufl://<KES-host>:<kufl-port>/

# 2. 启动反向 KFS 链路,把 KES 端变更同步回 Oracle
${KFS_HOME}/bin/fsrepctl -service KES_TO_ORA_REVERSE online
# 预期:KES → Oracle 方向同步链路建立,开始把新库变更反向同步回源

8.3 回退后操作

# 1. 停止反向 KFS(避免循环同步)
${KFS_HOME}/bin/replicator stop
${KFS_HOME}/bin/fsrepctl -service KES_TO_ORA_REVERSE offline

# 2. 启动 KFS Oracle → KES 链路(继续验证新库)
${KFS_HOME}/bin/replicator start
${KFS_HOME}/bin/fsrepctl -service ORA_TO_KES_SYNC online

# 3. 排查根因:
#    - KWR 快照 → KSH 长会话 → KDDM 索引建议
#    - 应用日志报错
#    - KFS 告警面板

⚠️ 方案 B 的术语说明:

  1. §1.5 统一术语为"反向 KFS 链路"。"双向同步"在 KFS 内部确实是 setrole 切换后的客观状态,但对外叙述避免使用"双向同步"以免被误读为"两边都在实时同步"。
  2. 割接完成后,正常状态下只有应用 → KES(KES 为单主) 的写入链路,KES → Oracle 的反向 KFS 链路保持 offline,仅在 E5 触发回退时短暂置为 online。
  3. 方案 B 的 setrole 是双向链路底层机制;具体步骤由 §8.4 源库保留策略兜底。

8.4 源库保留(启发式 4)

金融/政务核心系统:保留源库 ≥ 30 天不销毁,反向 KFS 链路保持活跃,并每月演练 1 次。

普通业务系统建议保留源库 ≥ 7 天,确认业务稳定后再清理。


九、性能验证(KWR/KSH/KDDM 三件套)

「金仓性能调优不是套参数模板,而是 KWR 看全景 → KSH 看会话 → KDDM 给建议 的目标—采样—定位—变更—验证闭环。」—— 模型 6

9.1 KWR 工作负载仓库

-- 立即采样
SELECT kwr_snapshot.create_snapshot();

-- 查最近 5 个快照
SELECT * FROM kwr.snapshot ORDERBY snap_id DESCLIMIT5;

9.2 KSH 会话历史(定位长会话/锁等待)

SELECT pid, query_start, state, query
FROM pg_stat_activity
WHERE state = 'idle in transaction'
ORDERBY query_start LIMIT20;

9.3 sys_stat_statements TOP 10

SELECTquery, calls, total_exec_time, mean_exec_time
FROM sys_stat_statements
ORDERBY total_exec_time DESCLIMIT10;

9.4 KDDM 索引建议

SELECT * FROM kddm.recommendation WHEREtype = 'index';
-- 注意:KDDM 推荐索引必须人工复核,确认符合业务查询模式

9.5 关键参数调优(推荐值)

参数
推荐值
备注
shared_buffers
物理内存 × 25%
高于 PG 默认
work_mem
64MB
排序/哈希内存
autovacuum_naptime
60s
频繁 vacuum
idle_in_transaction_session_timeout
5min
防止长事务泄漏
statement_timeout
60s
防止单条 SQL 长期持锁

十、附录

10.1 方言快速对照(Oracle → KES Oracle 模式)

Oracle
KES Oracle 模式
备注
NVL(a,b)
✅ NVL(a,b)
兼容
DECODE(...)
✅ DECODE(...)
132 处可零修改
ROWNUM
✅ ROWNUM
兼容
CONNECT BY
✅ CONNECT BY
V8R3+
SYSDATE
✅ SYSDATE
兼容
DUAL
✅ DUAL
兼容
VARCHAR2
✅ VARCHAR2
兼容
NUMBER(p,s)NUMERIC(p,s)
(存储类型)/ 应用层 NUMBER 写法照旧
Oracle 兼容模式下 CREATE TABLE ... NUMBER(p,s) 语法兼容;底层映射到 NUMERIC(详见《KFS 数据类型映射参考手册》)
BINARY_DOUBLE
✅ BINARY_DOUBLE
仅 REDO 模式支持
BINARY_FLOAT
✅ BINARY_FLOAT
支持
DATE
✅ DATE
兼容
TIMESTAMP
✅ TIMESTAMP
兼容
INTERVAL
✅ INTERVAL
兼容
LONG
✅ LONG
兼容
CLOB
✅ CLOB
兼容
BLOB
✅ BLOB
兼容
DBMS_OUTPUT
/DBMS_LOB/DBMS_STATS
✅ 同名
兼容
DBMS_JOB
⚠️ 部分支持
高频踩坑,建议改 KES 调度
INTERVAL 分区
✅
V8R6C5B0041+
(+)
 外连接
❌ 必须改 ANSI JOIN
高频踩坑
SEQ.NEXTVALNEXTVAL('SEQ')
函数化调用

10.2 JDBC 连接串速查

// 基础连接
jdbc:kingbase8://host:54321/TESTDB

// 主备集群
jdbc:kingbase8://host1:54321,host2:54321,host3:54321/TESTDB

// 读写分离(V8R6+)
jdbc:kingbase8://host1:54321/TESTDB?READONLYHOSTS=host2,host3&usedispatch=true&dispatchMode=1

// 驱动类
com.kingbase8.Driver

10.3 容器内 SQL 客户端命令速查

# Oracle 容器
docker exec -it oracle-lite sqlplus SYSTEM/123456@//localhost/FREEPDB1

# KES 容器
docker exec -it kingbase sh -c "ksql -U SYSTEM -W 123456 -d TESTDB"

# 验证网络互通
docker exec kingbase sh -c "nc -zv host.docker.internal 1521"
docker exec oracle-lite sh -c "nc -zv host.docker.internal 5432"

# 查看 KES 健康
docker logs -f kingbase 2>&1 | grep "ready to accept connections"

# 查看 Oracle 健康
docker logs -f oracle-lite 2>&1 | grep "DATABASE IS READY"

10.4 参考链接

资源
链接
KES 下载页
https://www.kingbase.com.cn/download.html
KES Docker 安装
https://docs.kingbase.com.cn/cn/KES-V9R2C14/install/02-docker-install
KES V9R2C14 产品手册
https://docs.kingbase.com.cn/cn/KES-V9R2C14/introduction/
KFS 产品页
https://www.kingbase.com.cn/product/details_552_584.html
KFS 技术社区
https://bbs.kingbase.com.cn/documentGuide?recId=c3f448eede450dfbd8cadf68a991b40f
Oracle Docker 镜像
https://github.com/oracle/docker-images/blob/main/OracleDatabase/SingleInstance/README.md
Oracle 23ai ARM64
https://github.com/wilfriedago/oracle-database-23ai-free-setup-guide

写在最后

本文的核心思想:

  1. 场景先于工具(启发式 1):停机窗口 < 1h → 必须 KDTS + KFS + 反向 KFS + 应用单切
  2. 可逆性优先(E5 优先于 E4):反向 KFS 链路必须先于割接就绪
  3. 行为差异 > 语法兼容(模型 4):NVL/DECODE 通过 ≠ 行为一致
  4. 实测 > 厂商承诺(模型 6):性能数字必须 KWR/KSH/KDDM 三件套验证

边界:本文基于 KES V9R2C14(2026-03 内测版)编写,部分命令路径、参数、行为可能与 GA 版有出入。请以金仓官方最新文档为准。