从 Oracle 到金仓:一次真实数据库迁移的避坑实录

从 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 到金仓,从来都不是一键切换这种"简单动作",它更像是一场:

技术栈重构 + 运维体系重建 + 团队能力升级的综合工程

但只要路线正确、节奏控制好,这条路走通之后,你会发现:

✅ 成本可控

✅ 技术可控

✅ 风险可控

这在当前的大环境下,本身就非常有价值。

如果你正在做迁移,或者准备做迁移,这条路你一定会走。

早踩坑,不如少踩坑。

相关推荐
金海境科技2 小时前
亲测正规数据库修复恢复数据标准
数据库
Hello.Reader2 小时前
Flink SQL 中的 OVER 聚合——为每一行算“窗口统计
数据库·sql·flink
JIngJaneIL2 小时前
基于Java旅游信息推荐系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot·旅游
LSL666_2 小时前
mybatisplus入门案例
数据库·mysql·mybatisplus
ZePingPingZe3 小时前
DriverManager、DataSource、数据库驱动以及数据库连接池的关系
android·数据库·adb
Loiioฅ3 小时前
ctfshow-web入门-sql注入-171-186
数据库·sql
思成不止于此3 小时前
【MySQL 零基础入门】DML 核心语法全解析:表数据的增删改操作篇
数据库·笔记·sql·学习·mysql
wzy06233 小时前
Oracle会话清理脚本简明说明
oracle
yy17962610013 小时前
mysql基本结构及操作
数据库