做过数据库迁移的同学都知道,最头疼的不是"能不能迁过去",而是"迁完上线那天心里发不发虚"。跑了十几年的老系统,sql到处飞、存储过程好几千行、触发器一层套一层——但凡有个函数行为差了那么一丁点,上线就是个事故。这篇文章不搞虚的,就聊实操:哪些语法有差异、函数怎么映射、哪些坑已经有人替你踩过了。
一、先说结论:能直接搬的和不能直接搬的
一句话总结:oracle的sql和pl/sql代码,大部分搬到kingbasees上直接就能跑,不需要改。但"大部分"不是"全部"——你提前知道哪几个点有差异,比上线以后熬夜排查强一百倍。
1.1 放心搬的部分
以下这些东西从oracle搬到kingbasees,一行不用改:
- 28种数据类型(number、varchar2、date、timestamp、clob、blob一个不少)
- 基本sql语法(select、insert、update、delete、merge)
dual虚拟表(oracle老项目里到处都是from dual,放心用)- rownum、rowid伪列
- 序列(create sequence、nextval、currval)
- 层次查询(connect by,画组织架构树用的那个)
- pl/sql控制语句(if、case、loop、for、while)
- 存储过程、函数、包、触发器
- 21个内置包(dbms_output、dbms_sql、utl_http等)
- 70+系统视图(all_tables、dba_users、v$session等)
- merge语句、insert all、flashback查询、dblink
看着挺多是吧?没错,覆盖面确实广。但别高兴太早——下面这些有差异的地方,才是你迁移时要花心思的。
1.2 重点盯着的部分
| 类别 | 差异点 | 严重程度 | 一句话说明 |
|---|---|---|---|
| chr函数 | 不允许chr(0) | 中 | oracle能传0,这边直接报错 |
| convert函数 | 参数顺序相反 | 高 | sql不报错但返回乱码,最阴 |
| 正则表达式 | match_param部分含义不同 | 中 | 涉及正则的sql要用真实数据验证 |
| current_timestamp | 精度可能不同 | 低 | 显式指定精度就行 |
| nullif | 允许参数为null | 低 | 行为有利,不用管 |
| 对象命名 | 保留字列表有差异 | 中 | 建表时字段名可能撞关键字 |
| 隐式类型转换 | 规则存在差异 | 中 | 关联字段类型不一致会踩坑 |
接下来一个一个掰开讲。
二、数据类型:28种全兼容,但别把坏习惯也搬过来
2.1 建表语句直接搬
数据类型这块是最省心的。oracle用什么类型,kingbasees就用什么类型,不需要做任何映射转换。看个实际例子——下面这张表用了十几种不同的oracle类型,搬过来一字不改:
-- 这条建表语句,oracle和kingbasees跑出来一模一样
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这些平时不太用的冷门类型,也全部支持。
2.2 json和xml也没问题
现在新项目基本都绕不开json了。kingbasees支持json和jsonb两种类型,前者存原始文本,后者存二进制解析后的格式,查询更快:
-- 建表、插入、查询,写法跟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展开成表来查,做报表很好用
select jt.name, jt.price
from product_catalog,
json_table(attributes, '$'
columns (name varchar2(100) path '$.name',
price number path '$.price')
) jt;2.3 迁移时顺手修掉的坏习惯
迁移不是简单的"搬家",也是个给老代码"体检"的好机会。列设计上常见的几个问题,趁迁移一并修了:
-- 坏设计:日期用字符串、金额也用字符串、状态用数字
create table bad_order (
order_date varchar2(20), -- 该用date
amount varchar2(20), -- 该用number
status number -- 只有几个值,用char(1)更清楚
);
-- 好设计
create table good_order (
order_date date, -- 日期就用日期类型
amount number(12,2), -- 金额就用数值类型
status char(1) -- 'y'/'n' 比 1/0 直观
);还有个容易忽视的点:多表关联的时候,关联字段的数据类型必须一致。oracle项目里经常出现一个表用integer、另一个表用varchar来存同一个id的情况。在oracle里可能靠隐式转换蒙混过关了,但迁移之后隐式转换规则可能不同,关联就出问题了。迁移时务必检查一遍关联字段的类型是否统一。
三、函数迁移:200+函数哪些直接用,哪些有坑
函数是迁移中最容易翻车的地方。oracle里跑得好好的函数,换个库结果不一样——这种bug上线以后查起来特别头疼,因为sql不报错,只是数据悄悄变了。
3.1 放心用的函数(零差异)
下面这些函数在kingbasees中的行为和oracle完全一致,搬过来直接用就行:
-- 数字函数:26个全部兼容,行为一模一样
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;
-- 字符函数:19个全部兼容
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;
-- 日期函数:22个全部兼容
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;
-- 聚集函数:30个全部兼容,包括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;
-- 分析函数:31个全部兼容,窗口查询直接搬
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 有坑的函数(务必逐一测试)
坑位一:chr函数——传0直接报错
oracle允许chr(0),返回一个空字符(nul)。kingbasees不接受这个参数,直接抛异常。如果你代码里用chr(0)做字段分隔符或占位符,必须改掉:
-- oracle写法:不报错,返回包含空字符的字符串 select 'a' || chr(0) || 'b' from dual; -- 迁移后报错!改写方案:用tab(chr(9))或换行(chr(10))替代 select 'a' || chr(9) || 'b' from dual; -- 如果是做字符串分隔,也可以用不可见的unit separator select 'a' || chr(31) || 'b' from dual;
坑位二:convert函数——参数顺序是反的
这个坑最阴。sql不会报错,返回的结果是一串乱码,而且你乍一看可能还察觉不到。原因很简单:第二、三个参数的位置跟oracle反过来了。
-- oracle写法:convert(字符串, 目标字符集, 源字符集)
select convert('测试文字', 'zhs16gbk', 'al32utf8') from dual;
-- kingbasees写法:第二三个参数顺序要反过来!
select convert('测试文字', 'al32utf8', 'zhs16gbk') from dual;处理建议:迁移前全文搜索convert关键字,把每一条sql都揪出来确认参数顺序。
坑位三:正则表达式——match_param行为有差异
oracle的正则函数(regexp_replace、regexp_count、regexp_instr)支持一个match_param参数,用来控制大小写敏感、多行模式等。kingbasees也支持这个参数,但部分标志位的行为不完全一致:
-- 这类sql一定要用真实数据跑一遍,对比两边结果
select regexp_count('hello world', 'hello', 1, 'i') from dual;
-- 涉及time类型的正则场景,差异更容易暴露
select regexp_instr(time_col::text, '\d{2}:\d{2}') from schedule_table;处理建议:全局搜索regexp_关键字,把涉及正则的sql全部标记出来,用真实业务数据做结果对比。
坑位四:current_timestamp——精度可能不同
-- 不指定精度的话,两边返回的小数位数可能不一样 select current_timestamp from dual; -- 保险做法:显式指定精度 select current_timestamp(6) from dual;
3.3 函数映射速查表
迁移的时候手边放一张表,遇到拿不准的随时查:
| oracle函数 | kingbasees | 状态 | 备注 |
|---|---|---|---|
| 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 | 同名 | 红灯 | 参数顺序反的 |
| regexp_replace、regexp_count | 同名 | 黄灯 | match_param有差异 |
| current_timestamp | 同名 | 黄灯 | 建议显式指定精度 |
| nullif | 同名 | 绿灯 | 允许null参数,行为有利 |
四、pl/sql迁移:存储过程、包、触发器怎么搬
pl/sql是迁移里头最复杂的部分,也是业务逻辑最集中的地方。好消息是kingbasees对pl/sql做到了全面兼容,坏消息是你仍然需要逐个编译验证。
4.1 存储过程:大多数直接搬
-- 这个oracle存储过程,搬到kingbasees上一行不改就能编译运行
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;
/导入之后,跑一条语句检查编译状态:
-- 查看哪些对象编译失败了 select object_name, object_type, status from user_objects where status = 'invalid' order by object_type;
4.2 包(package):函数重载和初始化块
包是oracle最有特色的功能之一。kingbasees不仅支持自定义包,还支持函数重载(同名不同参数)和包初始化块:
-- 包规范:定义对外接口
create or replace package pkg_order as
max_items constant number := 100;
-- 两个同名函数,参数不同(重载)
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;
/
-- 包体:实现具体逻辑
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
-- 包初始化块:首次被调用时自动执行
select count(*) into g_cancel_count
from orders where status = 'cancelled';
end pkg_order;
/4.3 触发器:三种时机都支持
行级触发器、语句级触发器、instead of触发器——全都兼容。迁移后注意检查触发器之间有没有循环调用的链路:
-- 行级before触发器:审计薪资变更
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触发器:让多表联查的视图也能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;
/
-- 启用/禁用触发器
alter trigger trg_salary_audit disable;
alter trigger trg_salary_audit enable;4.4 动态sql和批量操作
动态sql有两种写法,取决于你需不需要在编译时确定列的类型:
-- 写法一:execute immediate——大多数场景够用
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;
/
-- 写法二:dbms_sql——列的类型和数量在编译时不确定时用
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,性能比循环单条执行快很多:
-- forall:批量删除,一条语句干掉整个集合
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:批量查询到集合里,省去来回切换上下文
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语法迁移:分页、merge、层次查询怎么写
5.1 分页查询
oracle的分页写法有好几种,kingbasees全部兼容:
-- 写法一:传统rownum分页(oracle 8i就有了,老项目里最多)
select * from (
select a.*, rownum rn from (
select emp_name, salary from employees order by salary desc
) a where rownum <= 20
) where rn > 10;
-- 写法二:12c+的offset...fetch(新项目常用)
select emp_name, salary from employees
order by salary desc
offset 10 rows fetch next 10 rows only;5.2 merge和insert all
-- merge:数据同步神器——有就更新,没有就插入
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 层次查询:组织架构树
-- 画组织架构树,完全兼容oracle写法
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;六、趁迁移优化一波:别把坏代码也搬过去
迁移是个"搬家"的过程,但也是个"大扫除"的好机会。下面这些优化建议,建议在迁移时顺手做了。
6.1 查询优化:最容易改、效果最明显
-- 不要 select *(字段多了浪费带宽,还会阻止覆盖索引生效)
select emp_id, emp_name, salary from employees where dept_id = 10;
-- in 子查询改成 exists(数据量大的时候差距很明显)
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;
-- 索引列上别套函数(套了就用不上索引了)
-- 错误:where trunc(create_time) = date '2025-01-01'
-- 正确:
where create_time >= date '2025-01-01'
and create_time < date '2025-01-02';6.2 索引检查:外键没索引是大坑
迁移完跑一段检查脚本,看看哪些外键缺索引:
-- 查出所有没有索引的外键字段
select
c.table_name as "表名",
c.constraint_name as "外键名",
cc.column_name as "外键列",
'缺索引!建议补上' 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;如果查出有缺索引的外键,补上:
create index idx_order_customer on orders(customer_id);
为什么这个重要?因为外键没索引的话,删主表数据的时候会锁住子表全部行,并发一上来就卡死。
6.3 大表分区策略
如果迁移完发现某张表超过5000万行了,就该考虑分区了:
-- 按季度做range分区(最常见的方案)
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) -- 兜底,必须有
);几个硬指标记一下:
- 单个分区不超过5000万条,或不超过100gb
- 全库分区总数不超过10万
- range分区一定要加
maxvalue兜底,list分区一定要加default兜底
6.4 连接池参数调整
迁移完应用端改了连接串,连接池参数也得跟着调。别小看这个——连接池配错了,轻则响应慢,重则整个系统卡死。
// 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");
// 关键参数
config.setmaximumpoolsize(36); // 每个cpu核心×10,别超过
config.setminimumidle(10); // 最小空闲连接
config.setidletimeout(300000); // 空闲超时5分钟
config.setconnectiontimeout(5000); // 获取连接超时5秒
config.setmaxlifetime(1800000); // 连接最大存活30分钟几个经验值:
- 连接数 = cpu核心数 × 10(比如2个18核的cpu,连接数36~360)
- 用静态连接池,别用动态的——动态连接池在高并发下容易触发"连接风暴",一分钟内连接数从100暴涨到几千
- 别用每次请求都新建连接的方式——频繁建连/断连是性能杀手
七、数据迁移实操:导出来、导进去、验证一遍
7.1 小数据量:用exp/imp直接搬
# oracle端导出 exp userid=hr/password@orcl owner=hr file=hr_backup.dmp log=hr_export.log # kingbasees端导入 imp userid=hr/password@kingbase file=hr_backup.dmp log=hr_import.log fromuser=hr touser=hr
7.2 大数据量:用sys_bulkload高速加载
数据量到百万级以上,exp/imp就不够快了。sys_bulkload专门干这个,速度比普通insert快几十倍:
sys_bulkload -i /data/import/employees.csv \
-t employees \
-d kingbase \
-u hr \
-o "type=csv" \
-o "delimiter=," \
-o "header=y"
7.3 导入后的验证脚本
导入完了别急着上线,先跑五条验证sql:
-- 验证一:表数量对不对
select count(*) as table_count from user_tables;
-- 验证二:有没有编译失败的对象(重点!)
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;
-- 验证三:索引状态
select index_name, table_name, status
from user_indexes
where status != 'valid'
order by table_name;
-- 验证四:约束状态
select constraint_name, table_name, constraint_type, status
from user_constraints
where status != 'enabled'
order by table_name;
-- 验证五:每张表的数据量(抽样跟oracle比一下)
select table_name,
(select count(*) from employees) as spot_check -- 换成你的表名
from user_tables
where table_name = 'employees';如果第二步查出了invalid的对象,手动重编译:
-- 重编译单个对象
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;
-- 或者批量重编译(生成重编译语句)
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;八、应用端适配: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
如果应用用了oracle原生的oci、occi或者pro*c接口,kingbasees提供了对应的兼容接口。大部分情况改一下头文件引用和连接串就行,代码逻辑不用动。
九、避坑清单:上线前逐条过一遍
把这篇文章里提到的坑点整理成一份清单,迁移项目启动的时候打印出来,一条一条打勾:
数据类型(4条)
- 搜索代码中所有
chr(0),改用chr(9)或chr(31)替代 - 搜索代码中所有
convert函数,逐条确认参数顺序 - 检查多表关联字段的数据类型是否一致
- 检查json/xml字段的处理逻辑
函数(3条)
- 搜索代码中所有
regexp_开头的函数,用真实数据对比结果 - 检查
current_timestamp是否显式指定了精度 - nvl和decode不用改,但跑一遍确认业务逻辑没问题
pl/sql(4条)
- 导入后检查所有对象的编译状态,有invalid的重编译
- 检查触发器之间是否有循环调用链
- 验证dbms_sql、utl_http等内置包是否正常工作
- 检查动态sql有没有sql注入风险,趁迁移顺手修了
sql(3条)
- 检查merge语句的on条件字段是否有索引
- 层次查询用真实数据验证层级正确性
- 测试dblink是否连通
运维(3条)
- 确认运维脚本里的系统视图都能用(v s e s s i o n 、 v session、v session、vlock等)
- 连接池配置用静态的,别用动态的
- 迁移完第一件事:配好备份再干别的
十、最后说两句
迁移这事,说难不难,说简单也确实不简单。核心就三步:
评估——把oracle里用了哪些特性摸清楚,对照本文的兼容性列表,标出有差异的部分。
迁移——大部分代码直接搬,有差异的部分单独改写。用exp/imp导schema和数据,用验证脚本确认完整性。
测试——重点测那几个有坑的函数(chr、convert、正则),跑一遍完整的业务回归测试。
记住一条铁律:兼容率再高,那几个差异点不查清楚,上线迟早出事。 上线前把第九节的清单过一遍,过完了心里就有底了。
到此这篇关于oracle迁移到kingbasees实战:语法差异、函数映射与避坑指南的文章就介绍到这了,更多相关oracle迁移到kingbasees内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论