接手过Oracle替换工程的人都知道,真正难的从来不是"把数据倒过去",而是"让整个系统无感地搬过去"。刚接到任务时,你面对的往往是一个运行多年的Oracle库,后面挂着一堆应用、报表、定时任务、存储过程,还有各种年久失修的"祖传脚本"。"替换"这个词听起来只是一次数据搬迁,做起来才知道,这是一场涉及SQL方言、事务语义、字符集、排序规则、连接池、监控告警的全链路手术。
下面我想把几轮Oracle替换工程里从技术选型、资产盘点、SQL改造到最终割接的完整实践拆给你看,包括那些文档里不会写、但真实生产环境必然遇到的坑。如果你正打算做Oracle替换,或者刚被拉进一个迁移项目,这篇文章至少能帮你少走一轮弯路。
1. 为什么要动Oracle:替换工程的真实起因与决策链条
1.1 办公室里的那个"不得不换"的理由
跟很多团队聊起Oracle替换,最初的动因往往不是技术,而是现实压力。我梳理下来,排在前三位的理由基本固定。
- 授权与成本:Oracle的License按CPU核数算,随着服务器从小型机转向x86,核数翻倍,账单一涨再涨。很多企业发现这笔钱已经养不起第二套灾备了。
- 服务响应不可控:Oracle的排障高度依赖原厂,遇到一个诡异的等待事件,社区资料少,工单排队时间长,一拖就是好几天,业务方等不起。
- 技术栈割裂:新项目都在用PostgreSQL、MySQL或国内数据库,老系统用Oracle,团队两头都要养,招聘、培训、工具链全都要双份开销,日子过得很别扭。
这三个理由放在一起,替换就从一个技术命题变成了经营命题。而你作为执行层,真正要做的不是争论换不换,而是把"换得起、换得稳"这件事做实。这里顺带说一句,替换的启动信号经常是"领导换了"或者"预算砍了",但落到具体执行时,技术方案永远要自己拿得出手,不能指望外部驱动替你做判断。
1.2 决策前必须回答的问题清单
替换工程最忌讳上来就查语法差异,先把边界和约束谈清楚。我建议无论谁拍板,下面五类问题必须有明确答案,否则做出来的方案必然是空中楼阁。
- 替换范围:是整个核心库一把梭,还是先挑几个非核心业务库试点?范围决定风险,也决定你要盘点的对象数量级。
- 停机窗口:业务方能给多长的停写窗口?是半夜两小时,还是周末四小时?这直接决定增量同步和数据校验方案。
- 数据体量:单表最大多少行、总体积多少T、日增多少?别凭感觉估算,去查dba_segments和dba_tables拿真实统计数字。
- 改造责任边界:应用方愿意投入多少人力改SQL?DBA团队是否负责所有存储过程重写?权责不划清,后面互相扯皮一定会发生。
- 回退条件:什么情况下必须回退?回退的SLA是多少?没有明确回退线的割接,本质上是在赌运气。
这些问题看着基础,但我在实际项目里见过太多次因为"先干起来再说"导致的返工。有一回,接手方以为只迁一个报表库,结果那个库下面挂了七个上游应用的直连账号,数据链路图一画,所有人都沉默了。所以,决策清单这一步永远值得花时间。
1.3 敢于说"不换"的边界
当然,替换不是所有场景的最优解。我见过一些小团队,Oracle总共就三个库、几十张表、没有存储过程,这种规模硬要折腾开源或国内数据库替换,迁移成本反而高于几年的License费用,纯粹是给自己找事。
我判断一个系统是否值得替换,会先看三件事:生命周期预期、复杂度、团队的长期投入意愿。如果系统本来就在技术债里挣扎,上面也没有资源投入改造,那替换工程大概率会变成一场漫长的消耗战。这时候,最专业的声音恰恰是"现在不换"。把决策依据写清楚,等业务方真正有动力时再启动,才是对所有人负责。
2. 迁移前的资产盘点:把隐形的Oracle依赖全部翻出来
2.1 从元数据开始:拿到完整的对象清单
资产盘点第一件事,是登录Oracle把家底摸清楚。不要只盯表和数据量,那只是冰山一角。我习惯先把dba_objects按类型拉一个全量清单,筛出自己负责的schema下面的表、索引、视图、物化视图、同义词、序列、包、存储过程、函数、触发器、DBLINK、JOB。
SELECT object_type, COUNT(*) cnt FROM dba_objects WHERE owner = 'APP_SCHEMA' GROUP BY object_type ORDER BY cnt DESC;这一步会非常直观地告诉你改造工作量分布在哪儿。有的库几百个存储过程,有的库几十个物化视图,有的库重度依赖DBLINK做跨库查询——这些都会在后续方案里变成不同的处理策略。
然后是明细清单。每类对象都要导出定义文本;存储过程、函数、包、触发器用dba_source,视图和物化视图用dba_views,表结构用dba_tab_columns配合dba_constraints、dba_indexes。把这些导出成文本文件放进版本管理仓库,这就是后续所有改造工作的基线。数据库侧配置基线也要一并记录:监听端口、初始化参数、补丁版本、字符集、排序规则,这些看似跟数据无关,后面排查兼容性时全都用得上。
2.2 SQL文本与应用依赖:别漏了藏在代码里的SQL
光有数据库侧的对象清单还不够,很大一部分SQL不在数据库里,而在应用代码、报表工具、ETL脚本里。Oracle的v$sql只能看到运行过的SQL,如果有些SQL半年没跑过一次,它就不会出现在v$sql里,但替换之后一旦被触发,就是一颗定时炸弹。
我的做法是四路并查。第一路,从AWR和v$sql里拉最近半年执行频率高、消耗大的TOP SQL。第二路,在应用代码仓库做字符串全局搜索,凡是出现SELECT、UPDATE、DELETE、MERGE、FROM、JOIN关键字的文件全部列出来。第三路,把报表工具的后台SQL脚本也搜一遍,很多报表工具的连接串和SQL都写在配置文件或数据源定义里,非常容易漏。第四路,跟应用方团队做一轮"灵魂访谈",让他们回忆有没有夜间批量、年终处理、历史数据归档这种低频任务。
这四路并查做完,得到一份完整的应用SQL清单。拿这份清单去和目标库做语法兼容性扫描,能提前发现大部分改造点。需要说明的是,高频SQL适合用自动化工具批量扫描,低频批量任务则更依赖人工梳理,两边的比重要根据系统特点调整,不能一个模板套到底。
2.3 依赖关系与风险分级
盘点完成之后,把所有对象和SQL按改造成本和风险分四档。
| 风险等级 | 判定依据 | 典型示例 | 处理策略 |
|---|---|---|---|
| 低 | 标准SQL,工具可自动转换 | 简单SELECT、INSERT、UPDATE | 自动改写+回归测试 |
| 中 | 涉及函数、类型映射差异 | NVL、TO_CHAR、日期运算 | 人工改写+针对性测试 |
| 高 | 依赖Oracle专有特性或PL/SQL复杂逻辑 | 存储过程大批量游标、自治事务、物化视图刷新 | 重写为主,安排专项评审 |
| 极高 | 紧耦合Oracle运行时行为 | 依赖序列预分配、依赖空字符串等于NULL、依赖DBLINK级联查询 | 需要业务方参与确认语义 |
这个矩阵的好处是让团队知道精力往哪里投。大部分项目里,真正难啃的是高和极高这两档,它们往往只占20%的数量,却要吃掉80%的工作量。把低风险那部分先跑通,能快速建立信心,但不要因为低风险改起来顺就放松警惕,后面的大头在存储过程和复杂SQL上。
3. 目标库选型与全量增量同步方案
3.1 选型不是找"最好的数据库",而是找"最能接住Oracle的"
选型时的评估维度我一般看五个:SQL方言兼容度、迁移工具成熟度、增量同步能力、周边生态和团队熟悉度、长期维护成本。
| 维度 | PostgreSQL | MySQL | 达梦DM8 | openGauss | OceanBase |
|---|---|---|---|---|---|
| SQL兼容度 | 中等,需改分页/函数/过程 | 较低,迁移工作量大 | Oracle兼容模式较好,支持包/游标改写 | 兼容部分Oracle语法,对plpgsql不熟需适应 | 兼容部分MySQL/Oracle语法 |
| 迁移工具 | pgloader、ora2pg、自研 | 手动为主,需大量改写 | DM数据迁移工具,支持Oracle直连 | 自带工具相对较新 | OMS提供迁移链路 |
| 增量同步 | 逻辑复制、Debezium | Binlog CDC成熟 | DM工具支持增量 | 尚在完善 | OMS成熟 |
| 社区资料 | 丰富 | 非常丰富 | 国内技术社区增长较快 | 社区和文档增长快 | 文档完善,大厂案例多 |
| 许可证 | 开源免费 | 开源免费 | 商业授权 | 开源 | 有社区版/商业版 |
这个表不是标准答案,每个项目都要按自己的数据特征打分。我的经验是:如果Oracle里存储过程、包特别多,达梦的兼容模式能省掉不少PL/SQL重写的体力活;如果团队本来就熟MySQL,用MySQL也不是不行,但SQL改造量和存储过程重构会非常大。PostgreSQL是一个折中方案,语法接近、开源生态好,工具链复用度高。
还有一点很重要:选型时要让目标库厂商或开源社区的技术支持离你足够近。生产级割接不是写完SQL就结束,后面还有半年的稳定期,碰到性能问题和诡异的兼容性坑,一个能快速响应的人比什么宣传材料都有价值。
3.2 结构迁移:建表语句的差异和常用映射
把Oracle对象搬到新库,结构迁移是最先做的。数据类型映射是第一个坎,我列一个常用对照表。
| Oracle | PostgreSQL | MySQL | 注意点 |
|---|---|---|---|
| NUMBER(p,s) | NUMERIC(p,s) | DECIMAL(p,s) | 无精度约束的NUMBER,MySQL要评估是否用DECIMAL(65,30)或拆成BIGINT |
| VARCHAR2(n) | VARCHAR(n) | VARCHAR(n) | Oracle的n是字节数,MySQL的n是字符数,长度规则不同 |
| DATE | TIMESTAMP | DATETIME | Oracle DATE包含时分秒,MySQL DATE只有日期,容易踩坑 |
| TIMESTAMP | TIMESTAMP | DATETIME(6) | 精度差异影响比较运算 |
| CLOB | TEXT | LONGTEXT | 注意索引限制和排序行为 |
| BLOB | BYTEA | LONGBLOB | 应用读取方式可能变化 |
| 序列 | SEQUENCE / IDENTITY | AUTO_INCREMENT / SEQUENCE | 需要显式处理nextval语义 |
还有一个容易忽略的点是DDL语句本身。Oracle的CREATE TABLE在目标库可能需要调整表空间、分区策略、约束命名规则。分区表尤其麻烦,区间分区、哈希分区、列表分区的语法与PostgreSQL/MySQL差异都不小,手工一个个建容易出错。我建议对分区表单独评估,生成一套目标库风格的分区DDL,并提前做数据分布测试,不要等到割接当天才发现某个大表的分区定义有问题。
3.3 数据迁移:全量导入导出与并发调优
结构迁完,进入数据迁移。全量导出阶段,我推荐优先用目标库迁移工具或Oracle自带工具导成中间文件,而不是写JDBC程序慢慢抽。比如用expdp导出,再在目标端用load/copy命令导入,既快又稳。如果目标库是达梦,DM数据迁移工具直接支持从Oracle实例拉数据,配置好源库连接和映射关系就能跑起来,省去中间文件这一步。
导入性能的关键参数包括:并行度根据源库和目标库的IO能力设置,一般在4到16之间;大表不要单条INSERT,用批量insert或者copy协议;先建表、导数据、后建索引和约束,能大幅节省时间;数据导入期间先禁用业务触发器,导完再开启。
校验这步千万不要只统计行数。行数一致但内容不一致的场景太常见了。我会做三层校验:第一层行数,第二层关键字段的checksum或哈希,第三层抽样对比,对每张表按主键取5%-10%的行做全字段比对。时间允许的话,还要执行一遍应用层校验脚本,用实际业务查询验证关键业务的返回结果。这里多说一句,checksum算法要选择值域大的,否则碰撞概率会让校验失去意义,实际项目中用CRC64或MD5效果都还行。
3.4 增量同步:日志解析与CDC方案
如果停机窗口不够做全量,就必须上增量。Oracle侧增量迁移的传统选择是GoldenGate,现在也有不少自研办法:从归档日志解析redo,或者通过触发器/时间戳轮询抽取增量。
需要提醒的是,用触发器做增量会有性能开销,生产大表慎用;用时间戳轮询要求业务表本身有可靠的更新字段,没有的话很容易漏数据。目标库侧接收增量,PostgreSQL可以用逻辑复制,MySQL用Binlog复制,达梦和OceanBase都有自己的同步工具。整个增量链路最好在切换前持续跑两到三周,每天对比源库与目标库的差异,确保延迟长期处于秒级,再考虑割接。
4. SQL与存储过程改造:替换工程里最容易翻车的主战场
4.1 分页改写:ROWNUM和ROW_NUMBER的两种典型场景
Oracle经典分页写法是WHERE ROWNUM <= ?配合排序子查询,还有ROW_NUMBER() OVER (ORDER BY ...)的分析函数写法。这两种在目标库的写法很不一样。
比如Oracle常见写法:
SELECT * FROM ( SELECT t.*, ROWNUM rn FROM ( SELECT id, name, created_time FROM app_user ORDER BY created_time DESC ) t WHERE ROWNUM <= ? ) WHERE rn > ?;在PostgreSQL/MySQL里可以直接用:
SELECT id, name, created_time FROM app_user ORDER BY created_time DESC LIMIT ? OFFSET ?;机械替换看起来很简单,但有个隐蔽坑:ORDER BY的排序规则。Oracle默认排序对大小写、空格和null的排序顺序与PostgreSQL/MySQL不同,分页如果依赖排序稳定,改完可能出现同一页数据重复或漏页。这个我在坑3里会具体展开。
如果原SQL大量使用ROW_NUMBER() OVER做分组取TopN,比如"取每个用户最近一条订单",改写时要小心窗口函数的语法差异。尽管现代数据库基本都支持,但partition by子句后面的order by字段解析顺序偶尔会有差异。我的建议是每改一条复杂SQL,都拿一套代表性数据去两边跑,肉眼对比结果集,不要只看执行成功就急着收工。
4.2 字符串处理和隐式转换差异
Oracle里NVL是最常用的默认值函数,在PostgreSQL里对应COALESCE,MySQL里也可以直接用COALESCE或IFNULL。看起来像是一个函数替换,但语义有细节:NVL只接受两个参数,COALESCE接受多个;参数类型不同时,Oracle和PostgreSQL的隐式转换规则也不一样。
另一个经典差异是空字符串。Oracle里空字符串会被当成NULL处理,比如WHERE name = ''实际上查的是name IS NULL。PostgreSQL和MySQL则严格区分空字符串和NULL。这就导致同一个应用在不同库上查询结果不一样,原来返回空字符串的字段,迁到新库可能返回真实空串,应用逻辑里如果判断的是NULL,就会走错分支。这类问题靠自动化工具很难发现,只能靠业务测试覆盖。
字符串替换类的函数,比如REPLACE(str, old, new)和REGEXP_REPLACE,在PostgreSQL和MySQL里也有,参数顺序基本一致。但MySQL的REGEXP_REPLACE在高版本才支持,低版本得用其他函数绕。还有substr和substring的索引起点差异:Oracle从1开始,MySQL的substring从1开始,但有些程序员习惯写substr(str, 0, n),在Oracle里会返回空串,在MySQL里会正常返回,这种代码迁移后行为反而"变正常",进而暴露出之前的业务bug,也是挺折磨人的事。
4.3 存储过程、包与触发器的逐项改造
如果Oracle侧有大量PL/SQL,这是整个替换工程里工作量最大的部分。PL/SQL的包(Package)机制在PostgreSQL里没有直接对应物,通常要把包拆成普通函数并加前缀命名;达梦兼容模式做得好一些,包可以直接迁移,但仍需逐段测试。
改造中常见的差异点包括:%TYPE和%ROWTYPE锚定类型在PostgreSQL里的支持有差异;隐式游标FOR循环写法类似但变量作用域和异常处理有差别;自治事务在PostgreSQL里没有原生机制,需要用dblink开新连接模拟或改写业务逻辑;异常处理的WHEN OTHERS和SQLCODE/SQLERRM都要重新映射错误码。
还有一个值得抓住的机会:Oracle里常见的逐行UPDATE在大数据量下性能很差,迁移到新库时正好可以改写成集合操作,一次UPDATE加JOIN的写法往往比逐行循环快一两个数量级。触发器也是重头戏,PostgreSQL的触发器支持度不错,但OLD/NEW记录的引用方式、触发函数必须返回TRIGGER等细节都不同;MySQL触发器没有Oracle那么灵活,复杂规则可能要挪到应用层。
我的策略是提前把PL/SQL所有对象导出,做静态扫描,把不兼容特性标红,再按业务模块分批重写。重写过程中保持一个原则:不改业务逻辑,只改语法和语义等价的写法,所有改动都要有前后对比测试记录。
4.4 序列、同义词、物化视图等周边设施的迁移
序列是替换工程里特别容易翻车的地方。Oracle的序列NEXTVAL和CURRVAL语义在目标库里不一定完全一致。PostgreSQL的序列也支持nextval,MySQL的AUTO_INCREMENT则会在批量导入时自动取max+1,如果业务代码显式处理过序列值,迁移后主键冲突分分钟发生。我见过一次事故就是因为批量导入时没把序列重置到正确位置,第二天凌晨批量任务一跑,主键撞车直接把割接后第一个业务高峰打崩了。这个案例我在第6章详细拆。
同义词在Oracle里用来隐藏schema或远程表,PostgreSQL没有同义词对象,一般通过视图、外部表或直接改SQL里的schema前缀来替代。DBLINK在PostgreSQL里由FDW承担角色,在MySQL里没有直接等价物,如果业务重度依赖跨库查询,方案设计时就要提前规划。
物化视图是另一个硬骨头。Oracle的物化视图刷新机制成熟,可以快速刷新、完全刷新、复杂刷新。新库的物化视图能力参差不齐,PostgreSQL原生物化视图只支持全量刷新,增量刷新要靠第三方或自研触发器维护。如果原库有大量秒级或分钟级刷新的物化视图,尽量给业务方提供改造选项:要么放松刷新频率,要么把物化视图的逻辑下沉到定时任务加增量表。
5. 生产级切换:从灰度割接到回退兜底
5.1 割接窗口设计:把停机窗口切成五段
割接那天的时间窗口是稀缺资源。我习惯把整个割接过程切成五段:预检、停写、全量补差、增量追平、应用切换。
- 预检:切换前一两小时,检查目标库状态、主备延迟、磁盘空间、连接数等指标,全部绿灯才动手。
- 停写:通知应用侧停止写操作,数据库侧必要时设置读写分离或直接拒写,避免增量数据继续变化。
- 全量补差:把从第一次全量之后新产生的数据再导一遍,通常比第一次全量小很多。
- 增量追平:停写后继续消费最后一小段增量日志,直到源库和目标库的行数、checksum完全一致。
- 应用切换:修改连接配置,把流量从Oracle切到新库,业务验证通过后,割接窗口结束。
每一步都要提前设计验证标准。拿"增量追平"来说,不能只等延迟为0,还要对比每张表的行数和关键表的校验值。这里有一个血泪教训:当年第一次割接,延迟数归零,但实际还有半分钟的归档日志没解析完,切换后一查,少了几百条记录。后来我们改成"延迟为0后再观察5分钟,且连续两次采样一致才允许切换"。
5.2 应用切换的细节:连接池、配置中心与DNS
应用切换不是简单改一个jdbc.url。生产环境会有很多台应用服务器,连接池、配置中心、DNS、负载均衡都有可能成为切换点。我的建议是提前准备一份切换清单,列清楚每台服务器上连接串在哪个文件、密钥怎么轮换、配置中心的哪个key要更新,由专人逐项打钩。
连接池参数也要调整。Oracle连接池和RDBMS连接池的行为差异经常被忽略,比如最大连接数、连接空闲超时、验证查询。有一类经典问题:目标库的默认最大连接数远小于Oracle,应用用同一套配置连上来,瞬间打满连接池,直接雪崩。这个我在坑2里细说。总之,切换前要按新库的规格重算连接池上限和超时参数,别拿老配置硬套。
5.3 回退方案:老库不拆,至少保留N天只读
割接完成后,回退能力要保留一段时间。我的经验是,老库不要急着下线,至少保留7天,并保持只读验证状态。保留期间不是闲着,要做两件事:一是定期从新库反向抽取少量关键数据回写老库的比对表,验证两侧数据一致性持续成立;二是把老库的备份完整留档,一旦切换后的一周内出现重大问题,还能用LUN快照或回放日志的方式恢复。
回退的决策点也要提前定:什么时候必须回退?比如"核心业务成功率低于99%持续30分钟"或者"数据一致性校验出现不可修复差异"。到了那个点,不要犹豫,直接按预案回退。最怕的是团队在割接后发现一堆小问题,又不舍得放弃已经投入的大量工作,一边修一边顶着,最后越陷越深。
5.4 演练的频次与验收标准
生产级平稳迁移没有侥幸,全是演练堆出来的。正式割接之前,至少要完整演练两次。演练不是走流程,要真刀真枪做数据切换和回退,记录每步耗时,识别瓶颈。第一次演练通常是灾难现场,你会发现各种意外:某个数据导入脚本在2T的表上跑了5小时还没结束,某个应用连新库时因为SSL配置报错。
每次演练后更新割接手册。第一版手册可能写了大几十页,第二次演练时就能缩减一半,因为很多步骤已经脚本化、自动化。我的验收标准是:连续两次演练在预定窗口内完成,且回退演练成功,才允许申请正式的割接窗口。
6. 踩坑实录:四次真实故障的完整排查链路
6.1 坑一:增量同步延迟归零后又冒出的数据差异
这个坑是在一次割接演练中遇到的。当时增量任务显示延迟为0,源库和目标库的行数一致,我们正准备宣布演练成功,一致性校验脚本却报出某个核心表差300条记录。
排查链路是这样的:先看增量任务日志,发现最后一段归档日志在延迟显示归零后的3分钟才解析完成;再看数据时序,发现那300条记录集中在停写前1分钟内提交,属于大事务在日志中的提交记录晚于业务提交时间。也就是说,延迟为0只代表日志消费到某个位点,不代表所有已提交事务都已经被解析。
解决方案很简单:把"延迟为0"的判定改成"延迟为0并持续观察5分钟,且连续两次校验一致"才允许切换。这个规则后来写进了割接手册,排除了好几轮演练里的假绿灯。
6.2 坑二:应用连接池在割接后瞬间雪崩
这个坑发生在一次正式割接后的10分钟。应用切换到新库后,首页突然大面积报错,数据库连接数飙到上限,CPU被打满。
一开始所有人盯着数据库怀疑是SQL有问题,但看慢查询,没有明显的慢SQL,连接数却一直在涨。后来抓应用线程栈才发现,应用用的是Oracle时代的连接池配置,最大连接数2000,而目标库实例规格只有500的连接上限。应用启动后大量空闲连接被创建,又因为目标库的wait_timeout比Oracle短,连接被服务端断开,应用连接池不感知,继续把失效连接给业务线程,业务线程阻塞后重连,又制造更多连接,形成了雪崩。
排查链路是:数据库CPU高 -> 看连接状态发现大量TCP连接处于CLOSE_WAIT -> 对应应用线程栈发现大量等待获取连接 -> 回溯连接池配置发现最大连接数远大于库端上限 -> 重新按新库规格和QPS预估设置连接池参数 -> 加一层连接池初始化预热脚本 -> 故障恢复。事后复盘,其实切换前的演练里就该暴露这个问题,但演练时用的连接数较小,没触发。教训是:演练环境必须和生产环境规格一致,否则很多参数类问题根本测不出来。
6.3 坑三:字符集排序不一致导致的分页错乱
这是迁移后一次晚间业务反馈的问题:一个列表翻页到第3页,连续出现和第2页重复的数据,而且有的数据不见踪影。
一开始怀疑是分页SQL改写有误,拿SQL到两边各跑一遍,结果集数量一样,但取出来的行就是不一样。后来逐步比对,发现问题出在ORDER BY的排序规则上。原应用用的是ORDER BY user_name,Oracle默认按二进制或语言排序,PostgreSQL的默认排序则基于locale,大小写、重音、空格的处理都不一样。排序顺序不一致,分页按偏移量取数,自然出现重复和漏行。
解决方式是明确排序规则:给排序列加上COLLATE或转成一致的比较基准。比如PostgreSQL里可以显式使用COLLATE,或者在SQL改写时统一先做TRIM、LOWER处理再排序。这里特别要提醒的是,DISTINCT配合分页也会受排序影响,如果SQL里既有DISTINCT又有聚合函数,排序字段和投影字段的关系要仔细审。
6.4 坑四:批量导入漏了序列步长,第二天主键冲突
这个坑来自一位朋友带队的迁移项目,事故完整链条是:数据导入完成后,开发同事用insert做了冒烟测试,一切正常;第二天凌晨批量任务跑起来,大量报表生成任务直接报主键冲突。
排查发现,导入工具把全量数据导入目标库后,目标库的自增序列仍然停留在建表时的初始值。Oracle侧的序列在上线前早就被业务消耗到了几十万,导入数据本身不占序列位点,导入也没重置序列。当晚冒烟测试的少量insert恰好没触发冲突,等批量任务一上来,序列值从1重新开始,撞上了已存在的数据。
修复办法是导入后立刻按每张表的最大主键值调整序列:PostgreSQL用setval,MySQL手动更新AUTO_INCREMENT的值,DM也有对应的重置方法。我后来把这个步骤固化到数据迁移脚本里,作为全量导入后的必做项,再也没出过问题。这个坑的教训是:别把序列当小事,它跟数据迁移是同一件事。
最后再分享一个小技巧:割接前把所有关键脚本和手册放在一个离线文件夹里,打印一份纸质版放在机房和办公室各一份。真到了割接现场网络抖动、平台连不上的时候,一份离线文档能救全场的命。替换工程的本质是跟不确定性做对抗,你能做的是把每一层风险都提前拆开、演练、加固,剩下的交给纪律。祝所有正在做替换工程的朋友都能平稳落地。