2023年春天,我接到了一个电话,内容很简单:“我们系统要国产化了,数据库从Oracle换到金仓,你过来帮我们搞。”
说"简单"其实是我高估了自己。挂了电话才开始意识到,这事儿不是装个新库、导个数据就完事的。一套跑了七八年的Oracle系统,几十张表、几百个存储过程、一堆定时任务,还有外面挂着的十几个应用——换掉底层的数据库,等于给跑着的火车换轮子。
后面这一年,我把整个迁移过程分成了三个阶段反复倒腾。第一个阶段叫"能跑就行",第二个阶段叫"不那么卡了",第三个阶段叫"好像比原来还快了点"。这篇文章就是这三个阶段的记录。
一、迁移前的摸底
甲方原来用的是Oracle 11g R2,库不大,核心数据大概200GB出头。业务系统是某省政务审批平台,高峰期几百个并发,对数据库的依赖不算轻——存储过程几百个、定时任务十几个、还有各种奇葩的SQL写法。
他们选的替代方案是金仓数据库KingbaseES V9,Oracle兼容版。
为什么选金仓?这个决定不是我做的。但既然定了,我就得搞清楚一个问题:金仓到底兼容Oracle到什么程度?
看了官方那份兼容性说明,第一感觉是"还行"——数据类型全兼容(NUMBER、VARCHAR2、DATE这些都没问题),SQL语法里CONNECT BY、MERGE INTO、WITH子句这些Oracle特色的东西也原生支持,PL/SQL包像DBMS_OUTPUT、DBMS_SCHEDULER、UTL_FILE这些常用的也都兼容。感觉迁移量不会太大。
但常识告诉我:官方文档说支持和你实际跑起来没问题,中间差了十万八千里。所以我做了一件事:把Oracle上的所有对象导出来,分类列了一个清单。
- 表、索引、序列、视图:约170个对象
- 存储过程和函数:约220个
- 包(Package):31个
- 触发器:50多个
- DB Link:3个
- 定时任务(DBMS_SCHEDULER):十几个
然后拿着这个清单逐项评估迁移工作量。表结构迁移基本可以自动化——用KDTS工具导过去,数据类型映射工具自动处理。存储过程的工作量最大——虽然语法兼容性好,但几个用了Oracle特有特性的复杂包大概率要手工改。
这个评估花了我两周的时间。结论是:核心功能迁移大概八周,测试和调优另算四周。
二、数据迁移:KDTS上场
正式迁移的第一步是数据。金仓提供了KDTS(Kingbase Data Transfer Service)工具,支持从Oracle、MySQL、SQL Server等异构数据库迁移到金仓。
KDTS有两种形态:B/S版(浏览器操作界面)和SHELL版(命令行)。我们用的B/S版,部署在一台单独的服务器上,配好源端(Oracle)和目标端(KingbaseES)的连接信息之后,在界面上勾选要迁移的对象,点一下"开始"就开始跑了。
第一次迁移跑了12个小时。200GB的数据,对于一个离线迁移工具来说中规中矩。但中间出了两个问题:
第一个问题是日期格式。Oracle里有一张表的时间字段存的值是
0099-09-30,这个值在Oracle里是合法的(Oracle的DATE范围很宽),但KDTS迁移到金仓之后变成了
99-09-30。金仓在解析这个日期的时候把"99"当成月份了(格式不对),报错中断了。排查了半天才发现是这个数据的问题。
解决方法是修改了迁移配置,加了一个参数:
datestyle = 'ISO, YMD'
让金仓在解析日期时按ISO标准走,问题就解决了。
第二个问题是字符集。Oracle那边用的
NLS_LENGTH_SEMANTICS 是CHAR语义,金仓这边默认也是CHAR,但具体实现上有细微差别——Oracle的CHAR(10)填充到10个字符,金仓也填充,但在某些边界场景下(比如联合索引、比较操作)行为不一样。结果有一条SQL迁移后查出来的数据和原来对不上。最后统一在迁移配置里指定了字符集映射,字段长度也做了对齐。
这些问题在当时看来很头疼,但现在回想,都属于"已知差异"——金仓的迁移文档里其实都写了,只是我没来得及看完而已。如果当时按文档逐一排查这些配置项,能省下不少排查时间。
表结构迁移完之后,用KDTS把数据也同步过来了。KDTS支持并行迁移——对一张大表,可以拆分成多个分区并行传输。我们有一张3.2亿行的日志表,串行迁移估计要跑两天,拆成8个并行通道之后,8个多小时就跑完了。
数据迁移这一步,KDTS整体是好用的。对于常规的表、视图、序列、索引这类对象,基本是"一键迁移"的体验。但对于存储过程、函数、包这类PL/SQL对象,它的转换能力有限——只能做基础的语法映射,复杂的Oracle特性还是得人工处理。我们31个包里,有7个需要手工改。
三、"能用"了,但很痛苦
数据和对象都迁完了,应用也改完连接串了。在测试环境启动应用,登录、查询、提交审批——流程走通了。
“能用了”。
但我高兴得太早了。上了生产之后,第一天就收到了用户的投诉:“系统比原来慢了。”
不是慢一点点,是慢了很多。原来Oracle上跑3秒的一个审批列表查询,现在要25秒。原来1秒就能打开的统计报表,现在转圈转到超时。
我连夜查问题。先说结论: 不是金仓不行,是迁移之后什么都没调。
Oracle原来的表上有十几个索引,建索引的SQL是当年DBA手写的,很多索引就是为了那个版本的Oracle的执行计划量身定做的。迁移到金仓之后,这些索引还在,但执行计划完全不一样了——因为两个数据库的优化器和代价模型不一样。原来能走索引的查询,现在走了全表扫描。原来用Hash Join最优的,现在选了NestLoop。
举个例子:
SELECT a.task_id, a.task_name, b.approver_name, a.create_timeFROM biz_task aLEFT JOIN biz_approval b ON a.task_id = b.task_idWHERE a.status = 'PROCESSING' AND a.create_time >= '2024-01-01'ORDER BY a.create_time DESC;
Oracle上走的是:
biz_task 用
idx_task_status_time 索引做Index Scan过滤出500行,然后NestLoop去连
biz_approval(用
idx_approval_task_id),总耗时3秒。
金仓上走的却是:
biz_task 全表扫描(50万行),然后Hash Join去连
biz_approval(也是全表扫),最后Sort排序,总耗时25秒。
根本原因:金仓的优化器对
idx_task_status_time 这个复合索引(status, create_time)的代价估算和Oracle不一样。Oracle认为通过这个索引能很快过滤出符合条件的行(选择性约1%),金仓则认为全表扫描更划算。两个优化器的"判断"不同。
解决方案也不复杂——收集统计信息:
ANALYZE biz_task; ANALYZE biz_approval;
跑完之后再查执行计划,金仓终于走了索引扫描,查询时间从25秒降到了4秒。
但4秒还是比Oracle的3秒慢了一点。这时候我开始意识到:迁移不仅仅是把数据搬过来、把语法改通, "能跑"和"跑得快"之间差了整整一个调优过程。
四、从"能用"到"好用"的调优之路
性能问题不是一条SQL的问题。第一天的"慢"反馈只是冰山一角。真正把所有慢查询都找出来、分析一遍、逐个优化,花了我大概三周的时间。
第一步是用
sys_stat_statements 定位TOP SQL。装好金仓的这个插件之后跑了一天,就看到了全库最耗时的前10条SQL分别是什么、平均耗时多少、调用频率多少。按DB Time排序,前3条SQL占总数据库时间的65%。
打开这三条SQL的执行计划,逐条分析。
第一条就是我们刚才说的审批列表查询。ANALYZE之后已经解决了。
第二条是一个统计报表SQL——8张表关联、多层子查询嵌套、还用了窗口函数。金仓生成了一份极其复杂的执行计划,里面有三次Sort。把
work_mem 从默认的4MB调到64MB之后,排序从磁盘临时文件挪到了内存,时间从47秒降到了12秒。但还不够。
仔细看执行计划,发现优化器对外层子查询的代价估算偏差很大——估算返回200行,实际返回了8万行。因为这个偏差,选择了错误的连接顺序。用HINT强行指定了连接顺序之后,降到了4.5秒。
第三条是一条 UPDATE 语句,关联了三张表做条件更新。金仓走了行级逐条更新,效率极低。改写成了MERGE INTO语法(金仓原生支持),一次表扫描完成更新,时间从38秒降到了3秒。
三条TOP SQL处理完,系统整体响应时间下降了70%。但这时还远远没到"好用"的程度。真正的麻烦在剩下的200多条存储过程里。
金仓和Oracle在PL/SQL上兼容度很高——我们31个Package在迁移后大部分能直接跑。但有几种情况需要手工改造:
第一种:包内同名的存储过程和函数。 Oracle允许一个包里有同名的存储过程和函数(参数相同),靠上下文区分。金仓不允许。这个问题在迁移时KDTS没有报错,但运行时调用的地方报"歧义调用"。把其中一个重命名就好了。
第二种:Object type方法的链式调用。 Oracle允许
obj.method1.method2.method3 这种写法,金仓不支持。改写时得拆成中间变量。
第三种:隐式游标和引用游标。 有几个复杂的存储过程里用了大量的Oracle特有的游标处理方式。金仓的游标功能是支持的,但部分边界行为不一样。花费了一个多星期逐条测试、改写。
存储过程全部调通之后,系统才算真正"能用了"——功能没问题了,大部分查询的性能也回到了可接受的范围。
但距离"好用"还有段距离。当时每天晚上有一个批处理任务,要把当天的审批数据汇总后生成报表。这个批处理在Oracle上跑45分钟,在金仓上要跑2小时20分钟。甲方接受不了。
这就是从"能用"到"好用"的最后一段路。花了一周时间对这个批处理做专项优化:
-
发现批处理里有一条SQL用到了函数索引,但函数索引的定义在迁移后变了——Oracle里是
SUBSTR(column,1,10),金仓迁移后变成了"SUBSTR"(column,1,10)(引号导致大小写敏感)。重建了函数索引。 -
批处理涉及的两张大表统计信息过旧。ANALYZE之后执行计划变了,其中一个Hash Join改成了Merge Join,性能提升明显。
-
把
checkpoint_timeout从默认的5分钟调长到15分钟,减少了批处理过程中的检查点IO尖峰。 -
在批处理SQL里加了一个并行HINT,让关键查询走并行扫描。
这一套组合拳打完之后,批处理时间从2小时20分钟降到了52分钟。虽然还是比Oracle的45分钟慢了一点,但在可接受范围内了。甲方也认可了这个结果。
五、从MySQL迁移的另一种体验
前面说的都是Oracle迁移。其实这个项目里还有一个子系统是从MySQL迁过来的,顺便也说说。
MySQL那个子系统数据量不大,几十GB,核心是几个查询密集型的统计接口。MySQL版本是8.0,迁到金仓的MySQL兼容版。
迁移过程比Oracle顺畅很多——MySQL的语法相对简单,金仓对它的兼容度也高。数据类型(TINYINT、INT、BIGINT、VARCHAR、ENUM、SET、TEXT这些都支持),函数(NOW、CURDATE、DATE_FORMAT、GROUP_CONCAT这些自然也支持),SQL语法(LIMIT、ON DUPLICATE KEY UPDATE这些也原生支持),基本上没有遇到需要"手工改"的语法差异。
最多的问题是大小写敏感。MySQL默认大小写不敏感(表名、列名不区分大小写),金仓默认是敏感的。有几个接口传参时大小写不一致,导致查不到数据。初始化金仓实例的时候加上
--enable-ci 参数就能解决这个问题。
另外就是存储过程里的变量引用方式——MySQL允许SQL语句里直接用
@变量名,金仓不支持。改写量不大,几十处替换。
总的来说,MySQL迁移比Oracle迁移轻松很多,主要是语法的复杂度不在一个量级上。Oracle那套PL/SQL包体系太庞大了,兼容起来工作量大得多。
六、踩坑记(四个最有价值的教训)
一年下来踩的坑不少,挑四个最有代表性的写出来,希望有人看到能少走点弯路。
第一个坑:索引全盘照搬。 我犯的最大错误就是把Oracle上的索引全部原样迁移过来。后来才发现,Oracle针对自己的优化器设计的索引(特别是复合索引的列顺序、函数索引、位图索引等)对金仓不一定适用。金仓的优化器有自己的选择逻辑。有一张表在Oracle上7个索引跑得好好的,金仓上全表扫描反而比走索引快。后来根据金仓的查询特征重建了索引策略,删了3个、改了2个、新增了1个。
教训:索引是跟着优化器走的。换了数据库,索引策略要重新评估,不要无脑搬运。
第二个坑:迁移测试只测了功能,没测性能。 第一轮测试按功能用例跑了一遍,全通过了,就觉得没问题了。结果一上生产就被性能打脸。再回到测试环境构建了和生产规模一致的数据量,重新跑性能测试,找出了二十多条慢查询。
教训:迁移测试必须包括性能测试。性能测试必须用和生产接近的数据量。200行数据和200万行数据,执行计划可能完全不同。
第三个坑:统计信息不是不重要的"后端事务"。 以前在Oracle上,表哥(ANALYZE)是DBA的常规操作,每周做一次。但迁移到金仓后,我犯懒了,没有及时建立自动统计信息收集的策略。结果就是执行计划越来越歪,系统越来越慢。后来把
autovacuum 的阈值调小、对核心表强制每天做一次ANALYZE,执行计划才稳定下来。
教训:金仓的优化器和Oracle一样依赖统计信息。统计信息不准,执行计划就不会准。这是数据库调优的地基,地基不稳什么都是白搭。
第四个坑:HINT用多了会"中毒"。 有一段时期,碰到慢查询就加HINT,加了当时确实跑快了。但过了两个月数据量翻了一倍,之前加的HINT反而成了性能瓶颈——强制走索引变成强制全表扫描了。不得不回头逐个检查、去掉不再适用的HINT。
教训:HINT是拐杖,不是腿。能用统计信息解决的问题不要用HINT。用了HINT的SQL要定期review,别以为加了一次就一劳永逸。
七、一些感慨
一年下来,项目最终通过了验收。系统的性能指标和Oracle时期的差距在5%以内,功能完全覆盖。甲方写了验收报告,评价是"达到了预期目标"。
但对我来说,最值得记下来的不是这个结论,而是过程中对"国产数据库"认识的转变。
说实话,在做这个项目之前,我对国产数据库的印象停留在"能用但不好用"的层面。一年之后,我的评价变了: 金仓对Oracle的兼容性比我想象的好,但迁移的难度不在于兼容性本身,而在于"换了引擎之后的重新调优"。
兼容性解决的是"能不能跑"的问题——这个是及格的。Oracle的CONNECT BY、MERGE INTO、DBMS_OUTPUT这些都能直接用,不需要改。31个Package里只有7个需要动代码,这个比例说明兼容度确实可以。
但"跑得快不快"是另一回事。Oracle在特定场景下有十几年的优化积累——某些SQL写法、索引策略、参数配置都是围绕Oracle的优化器特点形成的。换到金仓之后,同样的SQL写法、同样的索引策略,执行效果可能完全不同。这不是金仓的错,这是"换数据库必然要经历的阵痛"。
所以我对"从能用到好用"的理解,在项目前后发生了根本性的变化。
之前我以为:能用 = 语法兼容,好用 = 性能达标。
现在我认为:能用 = 语法兼容 + 功能覆盖 + 数据准确,好用 = 性能达标 + 运维顺手 + 团队会用。
最后一个"团队会用"是最容易被忽视但影响最深远的。系统上线之后,原来那帮Oracle DBA要转型管金仓。金仓的命令行、调优工具、监控手段和Oracle都有差异。我在项目最后两个月花了很多时间做"传帮带"——教他们怎么看KWR报告、怎么调参数、怎么分析执行计划。这部分工作不在项目计划书里,但我觉得可能是这个项目留下来最有价值的资产。
数据库换了,人的技能也得跟上。工具换得再快,人的经验是慢慢积累的,急不来。