Oracle遷移到KingbaseES實(shí)戰(zhàn)指南:語(yǔ)法差異、函數(shù)映射與避坑指南
做過(guò)數(shù)據(jù)庫(kù)遷移的同學(xué)都知道,最頭疼的不是"能不能遷過(guò)去",而是"遷完上線那天心里發(fā)不發(fā)虛"。跑了十幾年的老系統(tǒng),SQL到處飛、存儲(chǔ)過(guò)程好幾千行、觸發(fā)器一層套一層——但凡有個(gè)函數(shù)行為差了那么一丁點(diǎn),上線就是個(gè)事故。這篇文章不搞虛的,就聊實(shí)操:哪些語(yǔ)法有差異、函數(shù)怎么映射、哪些坑已經(jīng)有人替你踩過(guò)了。
一、先說(shuō)結(jié)論:能直接搬的和不能直接搬的
一句話總結(jié):Oracle的SQL和PL/SQL代碼,大部分搬到KingbaseES上直接就能跑,不需要改。但"大部分"不是"全部"——你提前知道哪幾個(gè)點(diǎn)有差異,比上線以后熬夜排查強(qiáng)一百倍。
1.1 放心搬的部分
以下這些東西從Oracle搬到KingbaseES,一行不用改:
- 28種數(shù)據(jù)類型(NUMBER、VARCHAR2、DATE、TIMESTAMP、CLOB、BLOB一個(gè)不少)
- 基本SQL語(yǔ)法(SELECT、INSERT、UPDATE、DELETE、MERGE)
dual虛擬表(Oracle老項(xiàng)目里到處都是FROM dual,放心用)- ROWNUM、ROWID偽列
- 序列(CREATE SEQUENCE、NEXTVAL、CURRVAL)
- 層次查詢(CONNECT BY,畫(huà)組織架構(gòu)樹(shù)用的那個(gè))
- PL/SQL控制語(yǔ)句(IF、CASE、LOOP、FOR、WHILE)
- 存儲(chǔ)過(guò)程、函數(shù)、包、觸發(fā)器
- 21個(gè)內(nèi)置包(DBMS_OUTPUT、DBMS_SQL、UTL_HTTP等)
- 70+系統(tǒng)視圖(ALL_TABLES、DBA_USERS、V$SESSION等)
- MERGE語(yǔ)句、INSERT ALL、FLASHBACK查詢、DBLink
看著挺多是吧?沒(méi)錯(cuò),覆蓋面確實(shí)廣。但別高興太早——下面這些有差異的地方,才是你遷移時(shí)要花心思的。
1.2 重點(diǎn)盯著的部分
| 類別 | 差異點(diǎn) | 嚴(yán)重程度 | 一句話說(shuō)明 |
|---|---|---|---|
| CHR函數(shù) | 不允許CHR(0) | 中 | Oracle能傳0,這邊直接報(bào)錯(cuò) |
| CONVERT函數(shù) | 參數(shù)順序相反 | 高 | SQL不報(bào)錯(cuò)但返回亂碼,最陰 |
| 正則表達(dá)式 | match_param部分含義不同 | 中 | 涉及正則的SQL要用真實(shí)數(shù)據(jù)驗(yàn)證 |
| CURRENT_TIMESTAMP | 精度可能不同 | 低 | 顯式指定精度就行 |
| NULLIF | 允許參數(shù)為NULL | 低 | 行為有利,不用管 |
| 對(duì)象命名 | 保留字列表有差異 | 中 | 建表時(shí)字段名可能撞關(guān)鍵字 |
| 隱式類型轉(zhuǎn)換 | 規(guī)則存在差異 | 中 | 關(guān)聯(lián)字段類型不一致會(huì)踩坑 |
接下來(lái)一個(gè)一個(gè)掰開(kāi)講。
二、數(shù)據(jù)類型:28種全兼容,但別把壞習(xí)慣也搬過(guò)來(lái)
2.1 建表語(yǔ)句直接搬
數(shù)據(jù)類型這塊是最省心的。Oracle用什么類型,KingbaseES就用什么類型,不需要做任何映射轉(zhuǎn)換??磦€(gè)實(shí)際例子——下面這張表用了十幾種不同的Oracle類型,搬過(guò)來(lái)一字不改:
-- 這條建表語(yǔ)句,Oracle和KingbaseES跑出來(lái)一模一樣
CREATE TABLE orders (
order_id NUMBER(12) PRIMARY KEY,
order_no VARCHAR2(50) NOT NULL,
status CHAR(1),
amount NUMBER(12,2),
discount FLOAT,
order_date DATE,
created_at TIMESTAMP,
delivery_ts TIMESTAMP WITH TIME ZONE,
local_ts TIMESTAMP WITH LOCAL TIME ZONE,
warranty INTERVAL YEAR TO MONTH,
delivery_span INTERVAL DAY TO SECOND,
description CLOB,
attachment BLOB,
raw_data RAW(500),
ext_file BFILE,
row_loc ROWID,
extra_info NVARCHAR2(500),
tags NCHAR(100)
);包括BFILE、UROWID、LONG RAW這些平時(shí)不太用的冷門類型,也全部支持。
2.2 JSON和XML也沒(méi)問(wèn)題
現(xiàn)在新項(xiàng)目基本都繞不開(kāi)JSON了。KingbaseES支持JSON和JSONB兩種類型,前者存原始文本,后者存二進(jìn)制解析后的格式,查詢更快:
-- 建表、插入、查詢,寫(xiě)法跟Oracle一致
CREATE TABLE product_catalog (
id NUMBER PRIMARY KEY,
attributes JSON,
metadata JSONB
);
INSERT INTO product_catalog VALUES (
1,
'{"name": "筆記本電腦", "price": 5999, "tags": ["電子", "辦公"]}',
'{"source": "官方"}'
);
-- 提取JSON字段
SELECT JSON_VALUE(attributes, '$.name') AS product_name FROM product_catalog;
-- JSON_TABLE:把JSON展開(kāi)成表來(lái)查,做報(bào)表很好用
SELECT jt.name, jt.price
FROM product_catalog,
JSON_TABLE(attributes, '$'
COLUMNS (name VARCHAR2(100) PATH '$.name',
price NUMBER PATH '$.price')
) jt;2.3 遷移時(shí)順手修掉的壞習(xí)慣
遷移不是簡(jiǎn)單的"搬家",也是個(gè)給老代碼"體檢"的好機(jī)會(huì)。列設(shè)計(jì)上常見(jiàn)的幾個(gè)問(wèn)題,趁遷移一并修了:
-- 壞設(shè)計(jì):日期用字符串、金額也用字符串、狀態(tài)用數(shù)字
CREATE TABLE bad_order (
order_date VARCHAR2(20), -- 該用DATE
amount VARCHAR2(20), -- 該用NUMBER
status NUMBER -- 只有幾個(gè)值,用CHAR(1)更清楚
);
-- 好設(shè)計(jì)
CREATE TABLE good_order (
order_date DATE, -- 日期就用日期類型
amount NUMBER(12,2), -- 金額就用數(shù)值類型
status CHAR(1) -- 'Y'/'N' 比 1/0 直觀
);還有個(gè)容易忽視的點(diǎn):多表關(guān)聯(lián)的時(shí)候,關(guān)聯(lián)字段的數(shù)據(jù)類型必須一致。Oracle項(xiàng)目里經(jīng)常出現(xiàn)一個(gè)表用INTEGER、另一個(gè)表用VARCHAR來(lái)存同一個(gè)ID的情況。在Oracle里可能靠隱式轉(zhuǎn)換蒙混過(guò)關(guān)了,但遷移之后隱式轉(zhuǎn)換規(guī)則可能不同,關(guān)聯(lián)就出問(wèn)題了。遷移時(shí)務(wù)必檢查一遍關(guān)聯(lián)字段的類型是否統(tǒng)一。
三、函數(shù)遷移:200+函數(shù)哪些直接用,哪些有坑
函數(shù)是遷移中最容易翻車的地方。Oracle里跑得好好的函數(shù),換個(gè)庫(kù)結(jié)果不一樣——這種bug上線以后查起來(lái)特別頭疼,因?yàn)镾QL不報(bào)錯(cuò),只是數(shù)據(jù)悄悄變了。
3.1 放心用的函數(shù)(零差異)
下面這些函數(shù)在KingbaseES中的行為和Oracle完全一致,搬過(guò)來(lái)直接用就行:
-- 數(shù)字函數(shù):26個(gè)全部兼容,行為一模一樣
SELECT
ABS(-15) AS abs_val, -- 15
CEIL(4.3) AS ceil_val, -- 5
FLOOR(4.7) AS floor_val, -- 4
ROUND(3.1415, 2) AS round_val, -- 3.14
TRUNC(3.1415, 2) AS trunc_val, -- 3.14
MOD(10, 3) AS mod_val, -- 1
POWER(2, 10) AS power_val, -- 1024
SQRT(144) AS sqrt_val, -- 12
SIGN(-5) AS sign_val -- -1
FROM dual;
-- 字符函數(shù):19個(gè)全部兼容
SELECT
UPPER('hello') AS upper_str, -- HELLO
LOWER('WORLD') AS lower_str, -- world
INITCAP('hello you') AS initcap_str, -- Hello You
SUBSTR('abcdef',2,3) AS sub_str, -- bcd
INSTR('abcdef','cd') AS pos, -- 3
LPAD('123', 8, '0') AS padded, -- 00000123
TRIM(' hi ') AS trimmed, -- hi
REPLACE('hello','l','L') AS replaced -- heLLo
FROM dual;
-- 日期函數(shù):22個(gè)全部兼容
SELECT
SYSDATE AS now,
ADD_MONTHS(DATE '2025-01-15', 3) AS after_3m,
LAST_DAY(DATE '2025-02-10') AS feb_last,
NEXT_DAY(DATE '2025-03-15', 'MONDAY') AS next_mon,
TRUNC(SYSDATE, 'MONTH') AS month_start,
TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS') AS formatted
FROM dual;
-- 聚集函數(shù):30個(gè)全部兼容,包括LISTAGG
SELECT
department_id,
COUNT(*) AS emp_count,
AVG(salary) AS avg_sal,
SUM(salary) AS total_sal,
LISTAGG(emp_name, ',') WITHIN GROUP (ORDER BY emp_id) AS name_list
FROM employees
GROUP BY department_id;
-- 分析函數(shù):31個(gè)全部兼容,窗口查詢直接搬
SELECT
emp_name,
salary,
ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn,
RANK() OVER (ORDER BY salary DESC) AS rnk,
DENSE_RANK() OVER (ORDER BY salary DESC) AS d_rnk,
LAG(salary, 1) OVER (ORDER BY salary) AS prev_sal,
LEAD(salary, 1) OVER (ORDER BY salary) AS next_sal
FROM employees;3.2 有坑的函數(shù)(務(wù)必逐一測(cè)試)
坑位一:CHR函數(shù)——傳0直接報(bào)錯(cuò)
Oracle允許CHR(0),返回一個(gè)空字符(NUL)。KingbaseES不接受這個(gè)參數(shù),直接拋異常。如果你代碼里用CHR(0)做字段分隔符或占位符,必須改掉:
-- Oracle寫(xiě)法:不報(bào)錯(cuò),返回包含空字符的字符串 SELECT 'A' || CHR(0) || 'B' FROM dual; -- 遷移后報(bào)錯(cuò)!改寫(xiě)方案:用Tab(CHR(9))或換行(CHR(10))替代 SELECT 'A' || CHR(9) || 'B' FROM dual; -- 如果是做字符串分隔,也可以用不可見(jiàn)的Unit Separator SELECT 'A' || CHR(31) || 'B' FROM dual;
坑位二:CONVERT函數(shù)——參數(shù)順序是反的
這個(gè)坑最陰。SQL不會(huì)報(bào)錯(cuò),返回的結(jié)果是一串亂碼,而且你乍一看可能還察覺(jué)不到。原因很簡(jiǎn)單:第二、三個(gè)參數(shù)的位置跟Oracle反過(guò)來(lái)了。
-- Oracle寫(xiě)法:CONVERT(字符串, 目標(biāo)字符集, 源字符集)
SELECT CONVERT('測(cè)試文字', 'ZHS16GBK', 'AL32UTF8') FROM dual;
-- KingbaseES寫(xiě)法:第二三個(gè)參數(shù)順序要反過(guò)來(lái)!
SELECT CONVERT('測(cè)試文字', 'AL32UTF8', 'ZHS16GBK') FROM dual;處理建議:遷移前全文搜索CONVERT關(guān)鍵字,把每一條SQL都揪出來(lái)確認(rèn)參數(shù)順序。
坑位三:正則表達(dá)式——match_param行為有差異
Oracle的正則函數(shù)(REGEXP_REPLACE、REGEXP_COUNT、REGEXP_INSTR)支持一個(gè)match_param參數(shù),用來(lái)控制大小寫(xiě)敏感、多行模式等。KingbaseES也支持這個(gè)參數(shù),但部分標(biāo)志位的行為不完全一致:
-- 這類SQL一定要用真實(shí)數(shù)據(jù)跑一遍,對(duì)比兩邊結(jié)果
SELECT REGEXP_COUNT('Hello World', 'hello', 1, 'i') FROM dual;
-- 涉及time類型的正則場(chǎng)景,差異更容易暴露
SELECT REGEXP_INSTR(time_col::text, '\d{2}:\d{2}') FROM schedule_table;處理建議:全局搜索REGEXP_關(guān)鍵字,把涉及正則的SQL全部標(biāo)記出來(lái),用真實(shí)業(yè)務(wù)數(shù)據(jù)做結(jié)果對(duì)比。
坑位四:CURRENT_TIMESTAMP——精度可能不同
-- 不指定精度的話,兩邊返回的小數(shù)位數(shù)可能不一樣 SELECT CURRENT_TIMESTAMP FROM dual; -- 保險(xiǎn)做法:顯式指定精度 SELECT CURRENT_TIMESTAMP(6) FROM dual;
3.3 函數(shù)映射速查表
遷移的時(shí)候手邊放一張表,遇到拿不準(zhǔn)的隨時(shí)查:
| Oracle函數(shù) | KingbaseES | 狀態(tài) | 備注 |
|---|---|---|---|
| ABS、CEIL、FLOOR、ROUND、TRUNC | 同名 | 綠燈 | 直接用 |
| UPPER、LOWER、SUBSTR、INSTR、TRIM | 同名 | 綠燈 | 直接用 |
| REPLACE、CONCAT、LPAD、RPAD、INITCAP | 同名 | 綠燈 | 直接用 |
| SYSDATE、CURRENT_DATE | 同名 | 綠燈 | 直接用 |
| ADD_MONTHS、LAST_DAY、NEXT_DAY | 同名 | 綠燈 | 直接用 |
| TO_CHAR、TO_DATE、TO_NUMBER | 同名 | 綠燈 | 直接用 |
| NVL、NVL2、COALESCE、DECODE | 同名 | 綠燈 | 直接用 |
| LISTAGG | 同名 | 綠燈 | 直接用 |
| ROW_NUMBER、RANK、DENSE_RANK | 同名 | 綠燈 | 直接用 |
| LAG、LEAD、FIRST_VALUE、LAST_VALUE | 同名 | 綠燈 | 直接用 |
| JSON_VALUE、JSON_QUERY、JSON_OBJECT | 同名 | 綠燈 | 直接用 |
| XMLELEMENT、XMLFOREST、XMLAGG | 同名 | 綠燈 | 直接用 |
| CHR | 同名 | 黃燈 | 不能傳0 |
| CONVERT | 同名 | 紅燈 | 參數(shù)順序反的 |
| REGEXP_REPLACE、REGEXP_COUNT | 同名 | 黃燈 | match_param有差異 |
| CURRENT_TIMESTAMP | 同名 | 黃燈 | 建議顯式指定精度 |
| NULLIF | 同名 | 綠燈 | 允許NULL參數(shù),行為有利 |
四、PL/SQL遷移:存儲(chǔ)過(guò)程、包、觸發(fā)器怎么搬
PL/SQL是遷移里頭最復(fù)雜的部分,也是業(yè)務(wù)邏輯最集中的地方。好消息是KingbaseES對(duì)PL/SQL做到了全面兼容,壞消息是你仍然需要逐個(gè)編譯驗(yàn)證。
4.1 存儲(chǔ)過(guò)程:大多數(shù)直接搬
-- 這個(gè)Oracle存儲(chǔ)過(guò)程,搬到KingbaseES上一行不改就能編譯運(yùn)行
CREATE OR REPLACE PROCEDURE calc_monthly_report(
p_year IN NUMBER,
p_month IN NUMBER,
p_status OUT VARCHAR2
) AS
v_count NUMBER;
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE tmp_report';
INSERT INTO tmp_report
SELECT department_id, SUM(amount), COUNT(*)
FROM transactions
WHERE EXTRACT(YEAR FROM trans_date) = p_year
AND EXTRACT(MONTH FROM trans_date) = p_month
GROUP BY department_id;
SELECT COUNT(*) INTO v_count FROM tmp_report;
IF v_count = 0 THEN
p_status := 'NO_DATA';
ELSE
p_status := 'SUCCESS';
END IF;
EXCEPTION
WHEN OTHERS THEN
p_status := 'ERROR: ' || SQLERRM;
ROLLBACK;
END calc_monthly_report;
/導(dǎo)入之后,跑一條語(yǔ)句檢查編譯狀態(tài):
-- 查看哪些對(duì)象編譯失敗了 SELECT object_name, object_type, status FROM user_objects WHERE status = 'INVALID' ORDER BY object_type;
4.2 包(Package):函數(shù)重載和初始化塊
包是Oracle最有特色的功能之一。KingbaseES不僅支持自定義包,還支持函數(shù)重載(同名不同參數(shù))和包初始化塊:
-- 包規(guī)范:定義對(duì)外接口
CREATE OR REPLACE PACKAGE pkg_order AS
MAX_ITEMS CONSTANT NUMBER := 100;
-- 兩個(gè)同名函數(shù),參數(shù)不同(重載)
FUNCTION get_total(p_order_id NUMBER) RETURN NUMBER;
FUNCTION get_total(p_order_id NUMBER, p_include_tax NUMBER) RETURN NUMBER;
PROCEDURE cancel_order(p_order_id NUMBER);
END pkg_order;
/
-- 包體:實(shí)現(xiàn)具體邏輯
CREATE OR REPLACE PACKAGE BODY pkg_order AS
g_cancel_count NUMBER := 0;
-- 不含稅版本
FUNCTION get_total(p_order_id NUMBER) RETURN NUMBER AS
v_total NUMBER;
BEGIN
SELECT SUM(quantity * unit_price) INTO v_total
FROM order_items WHERE order_id = p_order_id;
RETURN NVL(v_total, 0);
END get_total;
-- 含稅版本(重載)
FUNCTION get_total(p_order_id NUMBER, p_include_tax NUMBER)
RETURN NUMBER AS
BEGIN
IF p_include_tax = 1 THEN
RETURN get_total(p_order_id) * 1.13;
ELSE
RETURN get_total(p_order_id);
END IF;
END get_total;
PROCEDURE cancel_order(p_order_id NUMBER) AS
BEGIN
UPDATE orders SET status = 'CANCELLED'
WHERE order_id = p_order_id;
g_cancel_count := g_cancel_count + 1;
COMMIT;
END cancel_order;
BEGIN
-- 包初始化塊:首次被調(diào)用時(shí)自動(dòng)執(zhí)行
SELECT COUNT(*) INTO g_cancel_count
FROM orders WHERE status = 'CANCELLED';
END pkg_order;
/4.3 觸發(fā)器:三種時(shí)機(jī)都支持
行級(jí)觸發(fā)器、語(yǔ)句級(jí)觸發(fā)器、INSTEAD OF觸發(fā)器——全都兼容。遷移后注意檢查觸發(fā)器之間有沒(méi)有循環(huán)調(diào)用的鏈路:
-- 行級(jí)BEFORE觸發(fā)器:審計(jì)薪資變更
CREATE OR REPLACE TRIGGER trg_salary_audit
BEFORE UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
IF :NEW.salary != :OLD.salary THEN
INSERT INTO salary_audit_log(
emp_id, old_salary, new_salary, changed_by, changed_at
) VALUES(
:NEW.emp_id, :OLD.salary, :NEW.salary, USER, SYSDATE
);
END IF;
END;
/
-- INSTEAD OF觸發(fā)器:讓多表聯(lián)查的視圖也能UPDATE
CREATE OR REPLACE VIEW emp_detail AS
SELECT e.emp_id, e.emp_name, e.salary, d.dept_name
FROM employees e, departments d
WHERE e.dept_id = d.dept_id;
CREATE OR REPLACE TRIGGER trg_emp_detail_update
INSTEAD OF UPDATE ON emp_detail
FOR EACH ROW
BEGIN
UPDATE employees SET
emp_name = :NEW.emp_name,
salary = :NEW.salary
WHERE emp_id = :NEW.emp_id;
END;
/
-- 啟用/禁用觸發(fā)器
ALTER TRIGGER trg_salary_audit DISABLE;
ALTER TRIGGER trg_salary_audit ENABLE;4.4 動(dòng)態(tài)SQL和批量操作
動(dòng)態(tài)SQL有兩種寫(xiě)法,取決于你需不需要在編譯時(shí)確定列的類型:
-- 寫(xiě)法一:EXECUTE IMMEDIATE——大多數(shù)場(chǎng)景夠用
CREATE OR REPLACE PROCEDURE dynamic_count(
p_table_name VARCHAR2
) AS
v_count NUMBER;
BEGIN
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM ' || p_table_name
INTO v_count;
DBMS_OUTPUT.PUT_LINE(p_table_name || ' 共 ' || v_count || ' 行');
END;
/
-- 寫(xiě)法二:DBMS_SQL——列的類型和數(shù)量在編譯時(shí)不確定時(shí)用
DECLARE
v_cursor INTEGER;
v_name VARCHAR2(100);
v_sal NUMBER;
BEGIN
v_cursor := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(v_cursor,
'SELECT emp_name, salary FROM employees WHERE salary > :1',
DBMS_SQL.NATIVE);
DBMS_SQL.BIND_VARIABLE(v_cursor, ':1', 10000);
DBMS_SQL.DEFINE_COLUMN(v_cursor, 1, v_name, 100);
DBMS_SQL.DEFINE_COLUMN(v_cursor, 2, v_sal);
DBMS_SQL.EXECUTE(v_cursor);
LOOP
EXIT WHEN DBMS_SQL.FETCH_ROWS(v_cursor) = 0;
DBMS_SQL.COLUMN_VALUE(v_cursor, 1, v_name);
DBMS_SQL.COLUMN_VALUE(v_cursor, 2, v_sal);
DBMS_OUTPUT.PUT_LINE(v_name || ': ' || v_sal);
END LOOP;
DBMS_SQL.CLOSE_CURSOR(v_cursor);
END;
/批量操作用FORALL和BULK COLLECT,性能比循環(huán)單條執(zhí)行快很多:
-- FORALL:批量刪除,一條語(yǔ)句干掉整個(gè)集合
DECLARE
TYPE id_list IS TABLE OF NUMBER;
v_ids id_list := id_list(101, 102, 103, 104, 105);
BEGIN
FORALL i IN v_ids.FIRST .. v_ids.LAST
DELETE FROM temp_data WHERE id = v_ids(i);
DBMS_OUTPUT.PUT_LINE('刪除了 ' || SQL%ROWCOUNT || ' 行');
END;
/
-- BULK COLLECT:批量查詢到集合里,省去來(lái)回切換上下文
DECLARE
TYPE emp_array IS TABLE OF employees%ROWTYPE;
v_emps emp_array;
BEGIN
SELECT * BULK COLLECT INTO v_emps
FROM employees WHERE department_id = 10;
FOR i IN v_emps.FIRST .. v_emps.LAST LOOP
DBMS_OUTPUT.PUT_LINE(v_emps(i).emp_name || ': ' || v_emps(i).salary);
END LOOP;
END;
/五、SQL語(yǔ)法遷移:分頁(yè)、MERGE、層次查詢?cè)趺磳?xiě)
5.1 分頁(yè)查詢
Oracle的分頁(yè)寫(xiě)法有好幾種,KingbaseES全部兼容:
-- 寫(xiě)法一:傳統(tǒng)ROWNUM分頁(yè)(Oracle 8i就有了,老項(xiàng)目里最多)
SELECT * FROM (
SELECT a.*, ROWNUM rn FROM (
SELECT emp_name, salary FROM employees ORDER BY salary DESC
) a WHERE ROWNUM <= 20
) WHERE rn > 10;
-- 寫(xiě)法二:12c+的OFFSET...FETCH(新項(xiàng)目常用)
SELECT emp_name, salary FROM employees
ORDER BY salary DESC
OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;5.2 MERGE和INSERT ALL
-- MERGE:數(shù)據(jù)同步神器——有就更新,沒(méi)有就插入
MERGE INTO target t
USING source s
ON (t.id = s.id)
WHEN MATCHED THEN
UPDATE SET t.name = s.name, t.val = s.val
WHEN NOT MATCHED THEN
INSERT (id, name, val) VALUES (s.id, s.name, s.val);
-- INSERT ALL:一條SELECT插入多張表
INSERT ALL
INTO emp_active (emp_id, name, salary) VALUES (emp_id, name, salary)
INTO emp_audit (emp_id, action, time) VALUES (emp_id, 'MIGRATE', SYSDATE)
SELECT emp_id, name, salary FROM employees WHERE status = 'ACTIVE';
-- INSERT RETURNING:插入后返回自增ID
INSERT INTO employees(emp_name, salary)
VALUES ('新員工', 10000)
RETURNING emp_id INTO :new_id;5.3 層次查詢:組織架構(gòu)樹(shù)
-- 畫(huà)組織架構(gòu)樹(shù),完全兼容Oracle寫(xiě)法
SELECT
LEVEL,
LPAD(' ', 2 * (LEVEL - 1)) || emp_name AS org_chart,
CONNECT_BY_ISLEAF AS is_leaf,
SYS_CONNECT_BY_PATH(emp_name, ' / ') AS full_path
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id
ORDER SIBLINGS BY emp_name;六、趁遷移優(yōu)化一波:別把壞代碼也搬過(guò)去
遷移是個(gè)"搬家"的過(guò)程,但也是個(gè)"大掃除"的好機(jī)會(huì)。下面這些優(yōu)化建議,建議在遷移時(shí)順手做了。
6.1 查詢優(yōu)化:最容易改、效果最明顯
-- 不要 SELECT *(字段多了浪費(fèi)帶寬,還會(huì)阻止覆蓋索引生效)
SELECT emp_id, emp_name, salary FROM employees WHERE dept_id = 10;
-- IN 子查詢改成 EXISTS(數(shù)據(jù)量大的時(shí)候差距很明顯)
SELECT * FROM orders o WHERE EXISTS (
SELECT 1 FROM vip_customers v WHERE v.customer_id = o.customer_id
);
-- UNION 改 UNION ALL(不需要去重的話,能省掉排序這一步)
SELECT name FROM table_a
UNION ALL
SELECT name FROM table_b;
-- 索引列上別套函數(shù)(套了就用不上索引了)
-- 錯(cuò)誤:WHERE TRUNC(create_time) = DATE '2025-01-01'
-- 正確:
WHERE create_time >= DATE '2025-01-01'
AND create_time < DATE '2025-01-02';6.2 索引檢查:外鍵沒(méi)索引是大坑
遷移完跑一段檢查腳本,看看哪些外鍵缺索引:
-- 查出所有沒(méi)有索引的外鍵字段
SELECT
c.table_name AS "表名",
c.constraint_name AS "外鍵名",
cc.column_name AS "外鍵列",
'缺索引!建議補(bǔ)上' AS "建議"
FROM user_constraints c
JOIN user_cons_columns cc
ON c.constraint_name = cc.constraint_name
LEFT JOIN user_ind_columns ic
ON cc.table_name = ic.table_name
AND cc.column_name = ic.column_name
WHERE c.constraint_type = 'R'
AND ic.index_name IS NULL
ORDER BY c.table_name;如果查出有缺索引的外鍵,補(bǔ)上:
CREATE INDEX idx_order_customer ON orders(customer_id);
為什么這個(gè)重要?因?yàn)橥怄I沒(méi)索引的話,刪主表數(shù)據(jù)的時(shí)候會(huì)鎖住子表全部行,并發(fā)一上來(lái)就卡死。
6.3 大表分區(qū)策略
如果遷移完發(fā)現(xiàn)某張表超過(guò)5000萬(wàn)行了,就該考慮分區(qū)了:
-- 按季度做Range分區(qū)(最常見(jiàn)的方案)
CREATE TABLE order_logs (
id NUMBER,
order_no VARCHAR2(50),
amount NUMBER(12,2),
created_at DATE
) PARTITION BY RANGE (created_at) (
PARTITION p_2025_q1 VALUES LESS THAN (DATE '2025-04-01'),
PARTITION p_2025_q2 VALUES LESS THAN (DATE '2025-07-01'),
PARTITION p_2025_q3 VALUES LESS THAN (DATE '2025-10-01'),
PARTITION p_2025_q4 VALUES LESS THAN (DATE '2026-01-01'),
PARTITION p_max VALUES LESS THAN (MAXVALUE) -- 兜底,必須有
);幾個(gè)硬指標(biāo)記一下:
- 單個(gè)分區(qū)不超過(guò)5000萬(wàn)條,或不超過(guò)100GB
- 全庫(kù)分區(qū)總數(shù)不超過(guò)10萬(wàn)
- Range分區(qū)一定要加
MAXVALUE兜底,List分區(qū)一定要加DEFAULT兜底
6.4 連接池參數(shù)調(diào)整
遷移完應(yīng)用端改了連接串,連接池參數(shù)也得跟著調(diào)。別小看這個(gè)——連接池配錯(cuò)了,輕則響應(yīng)慢,重則整個(gè)系統(tǒng)卡死。
// Java連接池配置示例(HikariCP)
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:kingbase8://10.0.0.1:54321/mydb");
config.setDriverClassName("com.kingbase8.Driver");
config.setUsername("hr");
config.setPassword("password");
// 關(guān)鍵參數(shù)
config.setMaximumPoolSize(36); // 每個(gè)CPU核心×10,別超過(guò)
config.setMinimumIdle(10); // 最小空閑連接
config.setIdleTimeout(300000); // 空閑超時(shí)5分鐘
config.setConnectionTimeout(5000); // 獲取連接超時(shí)5秒
config.setMaxLifetime(1800000); // 連接最大存活30分鐘幾個(gè)經(jīng)驗(yàn)值:
- 連接數(shù) = CPU核心數(shù) × 10(比如2個(gè)18核的CPU,連接數(shù)36~360)
- 用靜態(tài)連接池,別用動(dòng)態(tài)的——動(dòng)態(tài)連接池在高并發(fā)下容易觸發(fā)"連接風(fēng)暴",一分鐘內(nèi)連接數(shù)從100暴漲到幾千
- 別用每次請(qǐng)求都新建連接的方式——頻繁建連/斷連是性能殺手
七、數(shù)據(jù)遷移實(shí)操:導(dǎo)出來(lái)、導(dǎo)進(jìn)去、驗(yàn)證一遍
7.1 小數(shù)據(jù)量:用exp/imp直接搬
# Oracle端導(dǎo)出 exp userid=hr/password@orcl OWNER=hr FILE=hr_backup.dmp LOG=hr_export.log # KingbaseES端導(dǎo)入 imp userid=hr/password@kingbase FILE=hr_backup.dmp LOG=hr_import.log FROMUSER=hr TOUSER=hr
7.2 大數(shù)據(jù)量:用sys_bulkload高速加載
數(shù)據(jù)量到百萬(wàn)級(jí)以上,exp/imp就不夠快了。sys_bulkload專門干這個(gè),速度比普通INSERT快幾十倍:
sys_bulkload -i /data/import/employees.csv \
-t employees \
-d kingbase \
-U hr \
-o "type=csv" \
-o "delimiter=," \
-o "header=y"
7.3 導(dǎo)入后的驗(yàn)證腳本
導(dǎo)入完了別急著上線,先跑五條驗(yàn)證SQL:
-- 驗(yàn)證一:表數(shù)量對(duì)不對(duì)
SELECT COUNT(*) AS table_count FROM user_tables;
-- 驗(yàn)證二:有沒(méi)有編譯失敗的對(duì)象(重點(diǎn)?。?
SELECT object_name, object_type, status
FROM user_objects
WHERE status = 'INVALID'
AND object_type IN ('PROCEDURE', 'FUNCTION', 'PACKAGE', 'PACKAGE BODY', 'TRIGGER', 'VIEW')
ORDER BY object_type, object_name;
-- 驗(yàn)證三:索引狀態(tài)
SELECT index_name, table_name, status
FROM user_indexes
WHERE status != 'VALID'
ORDER BY table_name;
-- 驗(yàn)證四:約束狀態(tài)
SELECT constraint_name, table_name, constraint_type, status
FROM user_constraints
WHERE status != 'ENABLED'
ORDER BY table_name;
-- 驗(yàn)證五:每張表的數(shù)據(jù)量(抽樣跟Oracle比一下)
SELECT table_name,
(SELECT COUNT(*) FROM employees) AS spot_check -- 換成你的表名
FROM user_tables
WHERE table_name = 'EMPLOYEES';如果第二步查出了INVALID的對(duì)象,手動(dòng)重編譯:
-- 重編譯單個(gè)對(duì)象
ALTER PACKAGE pkg_order COMPILE;
ALTER PACKAGE pkg_order COMPILE BODY;
ALTER PROCEDURE calc_monthly_report COMPILE;
ALTER TRIGGER trg_salary_audit COMPILE;
ALTER VIEW emp_detail COMPILE;
-- 或者批量重編譯(生成重編譯語(yǔ)句)
SELECT 'ALTER ' ||
CASE object_type
WHEN 'PACKAGE BODY' THEN 'PACKAGE'
WHEN 'TYPE BODY' THEN 'TYPE'
ELSE object_type
END || ' ' || object_name || ' COMPILE' ||
CASE object_type
WHEN 'PACKAGE BODY' THEN ' BODY'
WHEN 'TYPE BODY' THEN ' BODY'
ELSE ''
END || ';'
FROM user_objects
WHERE status = 'INVALID'
AND object_type IN ('PROCEDURE', 'FUNCTION', 'PACKAGE', 'PACKAGE BODY', 'TRIGGER', 'VIEW')
ORDER BY object_type;八、應(yīng)用端適配:JDBC、ODBC、OCI怎么改
8.1 JDBC連接改兩行就夠
// 改之前(Oracle)
String url = "jdbc:oracle:thin:@10.0.0.1:1521:orcl";
String driver = "oracle.jdbc.OracleDriver";
// 改之后(KingbaseES)
String url = "jdbc:kingbase8://10.0.0.1:54321/mydb";
String driver = "com.kingbase8.Driver";
// 連接池完整示例(Spring Boot配置)
// application.yml
spring:
datasource:
url: jdbc:kingbase8://10.0.0.1:54321/mydb
username: hr
password: password
driver-class-name: com.kingbase8.Driver
hikari:
maximum-pool-size: 36
minimum-idle: 108.2 ODBC連接
# Oracle
Driver={Oracle in OraDb11g_home1};Server=10.0.0.1;Port=1521;DBQ=orcl;
# KingbaseES
Driver={KingbaseES 8 ODBC Driver};Server=10.0.0.1;Port=54321;Database=mydb;8.3 OCI / Pro*C / OCCI
如果應(yīng)用用了Oracle原生的OCI、OCCI或者Pro*C接口,KingbaseES提供了對(duì)應(yīng)的兼容接口。大部分情況改一下頭文件引用和連接串就行,代碼邏輯不用動(dòng)。
九、避坑清單:上線前逐條過(guò)一遍
把這篇文章里提到的坑點(diǎn)整理成一份清單,遷移項(xiàng)目啟動(dòng)的時(shí)候打印出來(lái),一條一條打勾:
數(shù)據(jù)類型(4條)
- 搜索代碼中所有
CHR(0),改用CHR(9)或CHR(31)替代 - 搜索代碼中所有
CONVERT函數(shù),逐條確認(rèn)參數(shù)順序 - 檢查多表關(guān)聯(lián)字段的數(shù)據(jù)類型是否一致
- 檢查JSON/XML字段的處理邏輯
函數(shù)(3條)
- 搜索代碼中所有
REGEXP_開(kāi)頭的函數(shù),用真實(shí)數(shù)據(jù)對(duì)比結(jié)果 - 檢查
CURRENT_TIMESTAMP是否顯式指定了精度 - NVL和DECODE不用改,但跑一遍確認(rèn)業(yè)務(wù)邏輯沒(méi)問(wèn)題
PL/SQL(4條)
- 導(dǎo)入后檢查所有對(duì)象的編譯狀態(tài),有INVALID的重編譯
- 檢查觸發(fā)器之間是否有循環(huán)調(diào)用鏈
- 驗(yàn)證DBMS_SQL、UTL_HTTP等內(nèi)置包是否正常工作
- 檢查動(dòng)態(tài)SQL有沒(méi)有SQL注入風(fēng)險(xiǎn),趁遷移順手修了
SQL(3條)
- 檢查MERGE語(yǔ)句的ON條件字段是否有索引
- 層次查詢用真實(shí)數(shù)據(jù)驗(yàn)證層級(jí)正確性
- 測(cè)試DBLink是否連通
運(yùn)維(3條)
- 確認(rèn)運(yùn)維腳本里的系統(tǒng)視圖都能用(V S E S S I O N 、 V SESSION、V SESSION、VLOCK等)
- 連接池配置用靜態(tài)的,別用動(dòng)態(tài)的
- 遷移完第一件事:配好備份再干別的
十、最后說(shuō)兩句
遷移這事,說(shuō)難不難,說(shuō)簡(jiǎn)單也確實(shí)不簡(jiǎn)單。核心就三步:
評(píng)估——把Oracle里用了哪些特性摸清楚,對(duì)照本文的兼容性列表,標(biāo)出有差異的部分。
遷移——大部分代碼直接搬,有差異的部分單獨(dú)改寫(xiě)。用exp/imp導(dǎo)Schema和數(shù)據(jù),用驗(yàn)證腳本確認(rèn)完整性。
測(cè)試——重點(diǎn)測(cè)那幾個(gè)有坑的函數(shù)(CHR、CONVERT、正則),跑一遍完整的業(yè)務(wù)回歸測(cè)試。
記住一條鐵律:兼容率再高,那幾個(gè)差異點(diǎn)不查清楚,上線遲早出事。 上線前把第九節(jié)的清單過(guò)一遍,過(guò)完了心里就有底了。
到此這篇關(guān)于Oracle遷移到KingbaseES實(shí)戰(zhàn):語(yǔ)法差異、函數(shù)映射與避坑指南的文章就介紹到這了,更多相關(guān)Oracle遷移到KingbaseES內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- KingbaseES中SQL高級(jí)性能優(yōu)化與執(zhí)行計(jì)劃深度解析
- MySQL和KingbaseES中連接、對(duì)象命名空間及用戶權(quán)限的區(qū)別
- mysql切到國(guó)產(chǎn)數(shù)據(jù)庫(kù)KingbaseES后的SQL區(qū)別
- KingbaseES金倉(cāng)數(shù)據(jù)庫(kù):ksql?命令行從建表到刪表實(shí)戰(zhàn)(含增刪改查)
- 國(guó)產(chǎn)數(shù)據(jù)庫(kù)KingbaseES安裝與使用方法詳解
- KingbaseES數(shù)據(jù)庫(kù)開(kāi)發(fā)運(yùn)維:部署、安全、備份與監(jiān)控實(shí)戰(zhàn)
相關(guān)文章
如何解決Oracle EBS R12 - 以Excel查看輸出格式為“文本”的請(qǐng)求時(shí)亂碼
這篇文章主要介紹了如何解決Oracle EBS R12 - 以Excel查看輸出格式為“文本”的請(qǐng)求時(shí)亂碼的相關(guān)資料,需要的朋友可以參考下2015-09-09
SQL?Developer遷移第三方數(shù)據(jù)庫(kù)單表到Oracle的全過(guò)程
這篇文章主要介紹了SQL?Developer遷移第三方數(shù)據(jù)庫(kù)單表到Oracle的全過(guò)程,文章通過(guò)圖文結(jié)合的方式給大家講解的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下2024-06-06
Oracle SQL Developer腳本輸出中文顯示亂碼的解決方法
我們?cè)跍y(cè)試Oracle Select AI(自然語(yǔ)言查詢數(shù)據(jù)庫(kù))時(shí),發(fā)現(xiàn)Run Statement中文顯示正常,而Run Script中文顯示亂碼,所以本文給大家介紹了Oracle SQL Developer腳本輸出中文顯示亂碼的解決方法,需要的朋友可以參考下2024-05-05
在Tomcat服務(wù)器下使用連接池連接Oracle數(shù)據(jù)庫(kù)
本文為大家介紹下在Tomcat服務(wù)器下使用連接池來(lái)連接數(shù)據(jù)庫(kù)的操作,下面有個(gè)不錯(cuò)的示例,大家可以參考下2014-01-01
WMware redhat 5 oracle 11g 安裝方法
本文將詳細(xì)介紹WMware中redhat 5 安裝oracle 11g方法,需要的朋友可以參考下2012-12-12
關(guān)于Oracle多表連接,提高效率,性能優(yōu)化操作
這篇文章主要介紹了關(guān)于Oracle多表連接,提高效率,性能優(yōu)化操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10
使用 Oracle 數(shù)據(jù)庫(kù)進(jìn)行基于 JSON 的應(yīng)用程序開(kāi)發(fā)
本文檔概述了 Oracle Database 19c 和 21c 版本中包含的功能和增強(qiáng)功能以??及相關(guān)的 Oracle 技術(shù),以及為什么 Oracle Database 中的 JSON 功能非常適合滿足當(dāng)今開(kāi)發(fā)人員尋求文檔存儲(chǔ)來(lái)持久化、查詢和處理應(yīng)用程序數(shù)據(jù)的需求,感興趣的朋友一起看看吧2025-04-04

