兄弟们,最近是不是都在忙信创改造?手里一套老Oracle,老板说必须迁到达梦,还要求“平滑迁移”,听起来挺美,真干起来全是坑。今天咱不聊那些高大上的原理,就聊聊我踩过的存储过程迁移的几个“语法地雷”,保准你看了能少熬几个夜。
第一个坑:NULL 和空字符串,它俩不是亲兄弟
在Oracle里,'' 和 NULL 是同一个东西,你写 WHERE name = '',实际上查的是 name IS NULL。但达梦区分得清清楚楚,'' 就是空字符串,NULL 就是啥也没有。当年我迁移一个存储过程,里面有个判断:
IF v_name = '' THEN ...
在Oracle跑得挺欢,到达梦直接不进了。查了半天,发现数据里存的是 NULL,不是空串。改法是啥?统一用 IS NULL 或者 NVL 包一层。记住,在达梦里,'' 和 NULL 是“室友”,不是“本人”。
第二个坑:SELECT INTO 查不到数据时,它俩表现不一样
Oracle里,SELECT INTO 查不到数据会抛 NO_DATA_FOUND 异常,你能用 EXCEPTION 接住。达梦呢?有的版本也抛,但有的版本竟然是“静默返回”,变量保持原值,不报错!这坑大了。我有一个存储过程,原本靠这个异常来做“存在性判断”,迁移后查不到数据时走了正常流程,结果把旧值插进去了,数据全乱了。
所以,迁过去之后,凡是 SELECT INTO,千万别信“异常能捕捉”,最好手动加个计数器,或者用 EXISTS 先查一下,保平安。
第三个坑:OUT 参数和 RETURN 的“性格差异”
Oracle的存储过程,常常用 OUT 参数返回多个值,这没什么。但达梦对 OUT 参数的赋值时机很较真。比如你在存储过程里先 SELECT ... INTO v_out FROM ...,如果查不到,Oracle的 v_out 会变成 NULL,但达梦可能保留进入过程前的值,甚至报错“变量未初始化”。你说气不气?
我的建议是,存储过程一开头就给所有 OUT 参数赋个默认值,比如 v_out := NULL;,别指望数据库帮你“自动清空”。这就好比借别人车,上车先调座椅,别等着记忆功能。
第四个坑:字符串拼接和 || 的兼容性问题
这个倒不是大坑,但容易恶心人。Oracle里 || 拼接时,如果遇到 NULL,结果是 NULL,达梦也是这么设计的。但如果你用了 CONCAT 函数,注意达梦只支持两个参数,Oracle支持多个参数(如 CONCAT('a','b','c'))。迁移时如果没注意,直接编译报错。改法简单,写个 CONCAT(CONCAT('a','b'),'c') 或者干脆用 || 串联。其实我建议团队统一用 ||,最原汁原味。
第五个坑:包(PACKAGE)里的全局变量,作用域比你想的猛
Oracle的包,变量默认是会话级的,一个连接里多次调用,变量值会保持。达梦呢?部分版本对包变量的生命周期处理不一样,有的在事务提交后就被重置了。我遇到过一个计数器,本来跨多次调用累加,结果每次调用都从0开始,逻辑全错。
如果你以前靠包全局变量存状态,迁移后一定要做测试,看它到底是不是“长期记忆”。要是没有,要么改设计,把状态存到临时表,要么换个写法,比如用参数传递。
第六个坑:异常处理里的 SQLCODE 和 SQLERRM
Oracle的异常码和达梦的不完全一致。比如 -01403 在Oracle是 NO_DATA_FOUND,达梦可能返回的是 -20001 或者别的。你如果写死了 WHEN OTHERS THEN,里面再判断 SQLCODE,分分钟跳错分支。
更坑的是,达梦对某些Oracle非法操作的提示信息还不一样,排查起来特费劲。我的土办法:在异常处理里把 SQLCODE 和 SQLERRM 拼起来写进日志表,经常对比两边跑出来的差异,时间长了你就能总结出“翻译对照表”。
最后一个:游标循环里的 FOR UPDATE 动静太大
Oracle里用 FOR UPDATE 加锁,然后边游标边更新,很常见。但是达梦有时锁的粒度更粗,容易造成死锁或锁等待超时。我有一次迁移一个批量处理存储过程,原来跑10分钟,到达梦变成半小时,还时不时报“资源忙”。后来把 FOR UPDATE 去掉,改用先收集主键再分批 UPDATE,世界瞬间清净。
说了这么多,其实核心就一句话:不要拿“兼容模式”当免死金牌,该测的测,该改的改。最好搞一套自动化比对工具,专门跑存储过程的输入输出。如果你们团队也踩过其他坑,欢迎一起交流。
对了,我们最后把项目里总结的迁移注意事项整理成了一个方案文档,有需要的兄弟可以去看看:更多方案可访问 itfangan.com,里面有个达梦迁移速查表,挺实用。祝大家信创路上,少踩坑,多摸鱼。