从 Oracle 到金仓:一次真实数据库迁移的避坑实录
这两年信创替代推进得非常快,很多企业都不得不走上数据库国产化这条路。而在所有迁移路线里,Oracle → 金仓(KingbaseES),几乎是最常见、也是最难的一条。
我这几年一直在一线参与数据库迁移项目,踩过的坑说实话比写过的 SQL 都多。今天这篇文章,不讲太多"官方介绍",就只聊真实项目里遇到的问题、怎么踩坑、又是怎么爬出来的,希望能给你少走点弯路。
一、OCI 连不上?问题根本不在网络
很多项目刚启动迁移时,第一个翻车点几乎都是:
👉 应用死活连不上金仓数据库
日志里往往是这一句:
ORA-12170: TNS 连接超时
第一反应通常是查网络、查防火墙、查端口。但我可以很负责任地说一句:
绝大多数这种情况,根本不是网络问题,而是协议不通。
1️⃣ 一个真实现场
某金融系统,原来跑在 Oracle 19c 上,应用一启动就报连接失败。网络 Ping 得通,端口也开着,但就是连不上。
一对比连接方式就明白了:
sql
-- Oracle 常见连接方式
(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.1.100)(PORT=1521))
(CONNECT_DATA=(SERVICE_NAME=orcl)))
-- 金仓连接方式
host=192.168.1.100 port=54321 dbname=test user=system password=123456
本质问题只有一句话:
Oracle 用的是 TNS 协议,而金仓底层走的是 PostgreSQL 协议。
你拿 Oracle 客户端对着金仓发 TNS 报文,对面根本"听不懂",那必然连不上。
2️⃣ 正确解法:用 KOCI 兼容层
金仓提供了 KOCI 兼容模式,专门用来"翻译"Oracle 协议。
java
// 原 Oracle 连接方式
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection conn = DriverManager.getConnection(
"jdbc:oracle:thin:@//192.168.1.100:1521/orcl",
"system",
"password"
);
// 金仓标准连接方式
Class.forName("com.kingbase8.Driver");
Connection conn = DriverManager.getConnection(
"jdbc:kingbase8://192.168.1.100:54321/test",
"system",
"123456"
);
// 金仓 OCI 兼容模式(关键)
Class.forName("com.kingbase8.Driver");
Connection conn = DriverManager.getConnection(
"jdbc:kingbase8:oci://192.168.1.100:54321/test",
"system",
"123456"
);
很多项目就是少了中间这一层兼容转换,白白折腾好几天。
3️⃣ 加密方式也是隐藏雷区
有些 Oracle 客户端默认会启用特定的加密、压缩方式,如果金仓端没配齐,也会直接拒绝连接:
sql
-- 开启 SSL
ALTER SYSTEM SET ssl = on;
ALTER SYSTEM SET ssl_cert_file = '/path/to/server.crt';
ALTER SYSTEM SET ssl_key_file = '/path/to/server.key';
-- 兼容 Oracle 加密模式
ALTER SYSTEM SET tns_ssl_mode = 'prefer';
👉 这类问题的特点是:
不报具体错,只给你一个"连接失败"四个字。
二、PL/SQL 迁移:真正的重灾区
如果说连接问题只是"开胃菜",那真正让人崩溃的,一定是 PL/SQL 迁移。
1️⃣ 存储过程不是"复制粘贴"就完事
我参与过一个 ERP 系统迁移,里面有 5000+ 个存储过程。一开始团队天真地觉得:
"先全部复制过来,看哪里报错再改。"
结果就是:
👉 报错刷屏,一天改不了 50 个。
比如 Oracle 里这种代码:
sql
DECLARE
TYPE EmpRec IS RECORD (
emp_id employees.employee_id%TYPE,
emp_name employees.last_name%TYPE,
salary employees.salary%TYPE
);
v_emp EmpRec;
BEGIN
SELECT employee_id, last_name, salary
INTO v_emp
FROM employees
WHERE employee_id = 100;
DBMS_OUTPUT.PUT_LINE('员工姓名: ' || v_emp.emp_name);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('未找到该员工');
END;
在金仓里必须这样改:
sql
DO $$
DECLARE
v_emp RECORD;
BEGIN
SELECT employee_id, last_name, salary
INTO v_emp
FROM employees
WHERE employee_id = 100;
RAISE NOTICE '员工姓名: %', v_emp.last_name;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE NOTICE '未找到该员工';
END $$;
这里涉及的不是语法,而是整个过程控制模型都不一样。
2️⃣ FORALL / BULK COLLECT 的性能坑
Oracle 里的这套写法:
sql
DECLARE
TYPE ID_TABLE IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
v_ids ID_TABLE;
BEGIN
SELECT employee_id BULK COLLECT INTO v_ids
FROM employees
WHERE department_id = 50;
FORALL i IN 1..v_ids.COUNT
UPDATE salaries
SET bonus = bonus * 1.1
WHERE employee_id = v_ids(i);
COMMIT;
END;
到金仓里如果你还一条一条更新,性能会直接掉一大截。
正确做法是改成 集合 + 一次性更新:
sql
DO $$
DECLARE
v_ids INTEGER[];
BEGIN
SELECT ARRAY_AGG(employee_id) INTO v_ids
FROM employees
WHERE department_id = 50;
UPDATE salaries
SET bonus = bonus * 1.1
WHERE employee_id = ANY(v_ids);
COMMIT;
END $$;
👉 这是迁移中最容易被忽视、但影响最大的性能点之一。
三、JSON 相关函数,不换写法必翻车
现在业务系统大量使用 JSON 字段,而 Oracle 和金仓在这块的写法差异也比较明显。
1️⃣ JSON_VALUE 的标准替换
sql
-- Oracle
SELECT
JSON_VALUE(customer_data, '$.name'),
JSON_VALUE(customer_data, '$.address.city')
FROM customers
WHERE JSON_EXISTS(customer_data, '$.orders[0]');
金仓标准写法:
sql
SELECT
jsonb_extract_path_text(customer_data::jsonb, 'name'),
jsonb_extract_path_text(customer_data::jsonb, 'address', 'city')
FROM customers
WHERE customer_data::jsonb ? 'orders';
如果你开启了兼容函数,也可以直接这样写:
sql
SELECT
json_value(customer_data, '$.name'),
json_value(customer_data, '$.address.city')
FROM customers
WHERE json_exists(customer_data, '$.orders[0]');
2️⃣ JSON_TABLE 在金仓的等价写法
Oracle:
sql
SELECT jt.*
FROM orders o,
JSON_TABLE(o.order_items, '$[*]'
COLUMNS (
item_id VARCHAR2(100) PATH '$.id',
quantity NUMBER PATH '$.quantity',
price NUMBER PATH '$.price'
)
) jt
WHERE o.order_id = 1001;
金仓必须改成:
sql
SELECT
(item->>'id')::VARCHAR,
(item->>'quantity')::NUMERIC,
(item->>'price')::NUMERIC
FROM orders o,
jsonb_array_elements(o.order_items::jsonb) AS item
WHERE o.order_id = 1001;
四、迁移成本到底怎么压下来?
迁移贵不贵,关键不在数据库,而在方法对不对。
1️⃣ KDTS 自动迁移工具
迁移前后最基本的校验:
sql
-- Oracle
SELECT COUNT(*), MIN(create_date), MAX(create_date) FROM orders;
-- 金仓
SELECT COUNT(*), MIN(create_date), MAX(create_date) FROM orders;
KDTS 配置示例:
yaml
sources:
- dbType: oracle
dbVersion: 19c
url: jdbc:oracle:thin:@//192.168.1.100:1521/orcl
username: system
password: oracle123
schemas: HR,SALES
target:
dbType: KINGBASE
dbVersion: V9
url: jdbc:kingbase8://192.168.1.101:54321/migrated_db
username: system
password: kingbase123
2️⃣ 零停机迁移靠 KFS
sql
SELECT
subscription_name,
apply_lag,
write_lag,
flush_lag
FROM sys_stat_subscription;
并行对账:
sql
WITH source_stats AS (
SELECT COUNT(*) FROM orders@oracle_link
),
target_stats AS (
SELECT COUNT(*) FROM orders
)
SELECT * FROM source_stats
UNION ALL
SELECT * FROM target_stats;
五、迁移完成后,真正的工作才开始
1️⃣ 性能基线
sql
CREATE VIEW performance_baseline AS
SELECT COUNT(*) FROM sys_stat_activity WHERE state = 'active';
2️⃣ 慢 SQL 监控
sql
SELECT
query,
calls,
total_time,
mean_time
FROM sys_stat_statements
WHERE mean_time > 1000;
3️⃣ 索引优化
sql
SELECT * FROM sys_stat_user_indexes
WHERE idx_scan = 0;
六、我总结的三条实战经验
✅ 迁移前
- 一定要做兼容性扫描
- 先迁边缘系统练手
- 一定要有压测环境
✅ 迁移中
- 分批迁移
- 数据反复校验
- 同时对比性能
✅ 迁移后
- 长期监控
- 定期调优
- 运维能力必须转型
写在最后
从 Oracle 到金仓,从来都不是一键切换这种"简单动作",它更像是一场:
技术栈重构 + 运维体系重建 + 团队能力升级的综合工程
但只要路线正确、节奏控制好,这条路走通之后,你会发现:
✅ 成本可控
✅ 技术可控
✅ 风险可控
这在当前的大环境下,本身就非常有价值。
如果你正在做迁移,或者准备做迁移,这条路你一定会走。
早踩坑,不如少踩坑。