ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Oracle迁移PostgreSQL实战:低业务中断、可校验、可回退的完整方案

Oracle迁移PostgreSQL实战:低业务中断、可校验、可回退的完整方案 搞Oracle到PostgreSQL迁移这件事圈子里聊得最多的不是工具选哪个而是怎么让业务别停太久、数据到底搬对没有、出了问题能不能跑回去这三个灵魂拷问。我最近完整跟完了一个核心交易系统的迁移项目正好把整套打法梳理一遍给准备上车的朋友当个参考。先说结论Oracle迁PostgreSQL难点从来不在怎么导数据而在迁移链条的整体设计。你只有把低业务中断、可校验、可回退当成一个整体目标来拆解才能避免那种数据导完了、业务起不来、回也回不去的尴尬局面。1. 迁移项目启动前先想清楚怎么切再动手1.1 迁移背景与目标拆解我们这个项目是从Oracle 11g迁到PostgreSQL 14源库大概有3TB数据核心业务表最大的一张超过8亿行还有不少用到了CLOB、分区、存储过程的老业务。业务侧给的要求很明确停机窗口最多4小时切换后一个月内如果发现重大问题必须能回退并且迁移完成后要拿出让业务信服的数据一致性证明。这三条要求翻译成技术语言就是低业务中断不能搞先停库全量导出再全量导入的老套路必须走全量增量同步的双轨方案把真正的停机时间压缩到只需要处理增量追赶和切换验证这两个环节。可校验迁移完成后要用行数、校验和、抽样比对等手段证明数据没丢、没多、没错。不是拿两条SQL对一下数量就完事而是要能经得起业务和审计的追问。可回退切换后保留完整的回退通道包括源库持续运行、反向同步机制、回退演练预案。一旦业务验证不通过能在一个小时内切回Oracle且不丢新增数据。这三个目标不是并列关系而是互相牵制的。比如为了可回退你可能需要在一段时间内同时维护两套库的写入为了低中断你的增量同步工具必须足够稳定否则追赶不上就会无限拉长停机窗口为了可校验又必须在迁移过程中保留足够的审计痕迹方便回退时对账。1.2 迁移前的源库调研与风险清单很多项目死在第一步没搞清楚源库里到底有什么就开始动手。我建议迁移前至少花一到两周做一次彻底的源库体检输出一份风险清单。我们当时重点排查了以下几类东西对象清单表、索引、约束、序列、视图、物化视图、存储过程、函数、包、触发器、同义词、DB Link。每一项都要统计数量并评估改造工作量。千万别小看同义词和DB Link业务SQL里如果大量使用迁移后全都要改。SQL使用情况抓取AWR报告和近一段时间的慢SQL重点看用了哪些Oracle独有语法比如CONNECT BY层次查询、()外连接、ROWNUM分页、NVL、SYSDATE、MERGE、LISTAGG等。这些是SQL改造的重灾区。数据类型分布NUMBER、VARCHAR2、DATE、CLOB、BLOB、LONG、RAW、ROWID等类型的字段数量和最大长度。尤其是CLOB和LONG迁移后要用TEXT替代但业务代码里对CLOB的操作方式可能需要调整。存储过程复杂度统计有多少存储过程、总代码行数、用到了哪些Oracle独有包DBMS_SQL、DBMS_JOB、DBMS_ALERT、UTL_FILE等。这部分改造成本往往被严重低估。我们项目里有1300多个存储过程最终花了整个项目40%的工时在改写上面。依赖关系应用与数据库的交互方式包括连接方式JDBC、OCI、ODBC、事务隔离级别、会话级参数设置、临时表使用情况。应用层的连接串、驱动、SQL写法都要纳入改造范围。体检报告出来后要让业务方、DBA、开发三方一起评审明确哪些东西可以原样迁哪些必须改造迁哪些是僵尸对象可以果断抛弃。这一步做扎实了后面所有的方案才有依据。2. 低业务中断的核心打法全量增量双轨迁移2.1 迁移工具选型与取舍工具选型没有标准答案核心看你的源库版本、目标库版本、数据总量、同步实时性要求和团队熟悉度。我们当时对比了三条路线Ora2Pg开源免费Perl写的能把表结构、数据、序列、视图、存储过程整体转换支持直接连Oracle导出再导入PostgreSQL。优点是自动化程度高缺点是大表导出用COPY方式对源库有一定压力而且存储过程转换质量一般只能转个骨架复杂逻辑基本还得人工改。pgloader同样开源从Oracle读数据到PostgreSQL非常快用的也是COPY协议支持并行加载。但DDL迁移能力弱主要用来搬数据。自研抽取同步脚本用Python写一套基于JDBC的全量导出和增量抽取脚本配合调度平台实现断点续传和数据校验。灵活性最高但开发量不小。我们最终采用了Ora2Pg转结构pgloader搬全量自研增量同步脚本Debezium做变更捕获的组合方案。结构转换用Ora2Pg生成初始DDL再人工修全量数据用pgloader并行加载增量部分考虑到源库是Oracle 11gDebezium的Oracle插件要装额外组件最后是自己写了一套基于日志的增量抽取程序。这里给个建议如果是Oracle 12c及以上Debezium是较好的增量同步方案社区活跃且支持断点续传11g就得掂量掂量了。2.2 全量迁移的并行与限速策略全量数据迁移最容易翻车的地方是导太猛把源库IO打满业务直接告警导太慢又赶不上停机窗口。所以并行度和限速必须提前设计。我们按表维度拆分任务用了一个简单的规则小于100万行的表单表单任务顺序执行100万到1000万行的表按主键范围拆成4到8个分片并行超过1000万行的表按主键范围拆成16到32个分片每个分片独立连接、独立事务。同时设置了全局并发上限保证源库的活跃会话数不超过20。pgloader本身有个batch concurrency参数可以控制并行度但实测下来对Oracle源库支持一般所以我更推荐自己写一个简单的调度器用一张任务表记录每个分片的状态调度线程从里面捞待执行的任务分发给工作线程工作线程处理完更新状态出错自动重试三次还是失败就进入人工处理队列。这里有个非常关键的细节全量迁移必须在增量同步启动之前完成并且要记录全量导出的数据快照时间点。我们的做法是全量开始前先在源库开启归档日志和补充日志然后启动增量抽取程序实时解析日志写入Kafka全量导出的每张表在导出前记录一个SCNSystem Change Number快照导出完成后用这个SCN到增量同步里做数据拼接。这样才能保证全量导出期间产生的增量变更不会丢。2.3 增量同步与业务双写设计增量同步的目标是让PostgreSQL和Oracle的数据差距保持在分钟级以内这样停机切换时只需要把最后几分钟的增量追上就能关窗口。我们的增量链路是Oracle归档日志 → LogMiner解析 → Kafka → 自研消费者程序 → 应用到PostgreSQL。为什么用LogMiner而不是直接用OGG主要是因为OGG授权成本和部署复杂度在当时的环境下不划算。LogMiner有几个坑要提前踩必须提前开启最小补充日志Supplemental Log否则LogMiner拿不到更新前的字段值UPDATE语句没法正确重放。LogMiner解析出来的DDL语句不能直接拿到PostgreSQL执行需要做语法转换比如ALTER TABLE xxx ADD (col NUMBER)要改成ALTER TABLE xxx ADD COLUMN col NUMERIC。大事务解析会产生大量Redo记录LogMiner内存可能扛不住要设置合理的UTL_FILE落地目录分段读取。增量应用端的冲突处理也需要设计。双轨运行期间如果应用还在继续写Oracle我们的增量程序会把这些变更实时投递到PostgreSQL两边数据保持一致。但如果某个时刻增量应用出现问题、落后太多或者业务在验证阶段直接在PostgreSQL上面做了修改就会产生回环冲突。我们的策略是切换之前所有写操作只走OraclePostgreSQL只接受增量投递的历史数据切换之后应用层切到PostgreSQLOracle那边不再接收业务写流量。这套单向写、双向读的策略在切换前把冲突可能性降到最低。2.4 停机窗口内的切换步骤清单就算增量同步再稳定切换当天也必须有SOP每一步到什么状态、由谁确认都要提前定义好。我们的切换清单大概是这样的业务侧发布维护通知应用层开启只读模式或直接停止写流量等待增量同步追上确认Oracle和PostgreSQL的数据延迟为0停止Oracle侧的写操作记录停止时间点增量程序消费完Kafka里所有消息应用完最后一笔变更执行数据校验脚本下一节细说确认两边数据一致关闭增量同步程序防止PostgreSQL继续接收Oracle的变更应用层修改数据库连接配置切换到PostgreSQL执行一批关键的冒烟测试SQL确认核心业务功能正常观察5到10分钟确认无异常后对外宣布切换完成。整个切换过程耗时大概1.5小时算上全量数据校验、应用重连、缓存预热最终停机窗口控制在3小时左右满足4小时的要求。这里要注意第5步的数据校验不能等全部跑完才判断我们在切换前就做过多次全量预校验在增量持续运行的情况下切换当天只做增量部分的差异性校验和随机抽表比对。3. 数据校验方案让业务信服的三个层次3.1 第一层行数与总量校验最基础的校验是行数比对。对每张表执行SELECT COUNT(*)两边对不上就说明有问题。但COUNT在大表上很慢动辄几分钟甚至更久。我们当时是用pg_comparison这类工具直接对比但更通用的做法是按主键范围分段统计行数每段一个任务并行跑最后汇总。如果源表和目标表都没有主键Oracle里确实存在这种烂表那校验就只能退而求其次按所有字段做GROUP BY后再COUNT或者用COUNT(*)配合SUM(HASH)来粗查。这类表数量不多还好如果很多强烈建议在建表阶段就帮它们补上主键或唯一约束否则后续的数据一致性验证基本没法做。行数校验是必要条件但不是充分条件行数一样不代表数据一样所以还要做第二层。3.2 第二层字段级CRC校验与哈希比对第二层是对每一行的所有字段拼起来做哈希两边比对哈希值是否一致。具体做法是在源库Oracle写一段SQL把每行需要比对的字段用DBMS_CRYPTO.HASH或STANDARD_HASH算出一个哈希值SELECT id, STANDARD_HASH(col1 || | || col2 || | || col3, SHA256) AS row_hash FROM my_table;在目标库PostgreSQL用同样的拼接规则算哈希SELECT id, encode(sha256(convert_to(col1 || | || col2 || | || col3, UTF8)), hex) AS row_hash FROM my_table;两边按主键关联对比row_hash是否一致把所有不一致的主键ID捞出来交给业务确认。这里有几个坑必须注意拼接字符串里的分隔符不能和字段值本身冲突。我一般用两个特殊字符比如||~|~||并提前检查字段值里是否包含这些字符。字段值里的NULL要统一处理。Oracle里空字符串就是NULL而PostgreSQL里空字符串和NULL是不同的。我们统一约定NULL或空串都替换成一个特殊标记比如NULL。浮点类型BINARY_DOUBLE、NUMBER带小数在Oracle和PostgreSQL二进制存储上有差异直接拼接字符串做哈希可能出现看起来一样、哈希不一样的误报。处理方法是对数值类型先做格式化统一保留到小数点后6位或更多确保两边字符串一致。大数据量下表字段特别多时哈希计算会非常消耗源库CPU。我们当时在源库用只读快照或者备库上跑哈希校验避免影响生产。3.3 第三层业务规则抽样比对哈希校验能保证技术层面的数据一致但业务方更关心他们用起来对不对。所以我们额外设计了一套业务规则的抽样比对从核心业务表里随机抽取若干个ID把该ID关联的订单、明细、流水全查出来业务方人工核对把一批统计报表SQL分别在Oracle和PostgreSQL上跑一遍比对聚合结果是否一致把几个典型的复杂查询多表关联、子查询、窗口函数在新库上执行业务方确认执行结果和口径符合预期。这个环节的意义不止于查数据还在于让业务方建立对迁移结果的信任。技术团队说一百句校验过了不如业务方自己抽几个单子确认来得踏实。3.4 校验工具与自动化我们整个校验体系是跑在一个自研的校验平台上的支持配置表清单、校验类型、阈值和通知渠道。技术上不复杂核心就是一个任务调度器加一堆校验脚本。不过如果你没有自研条件有几个现成工具可以参考pg_comparison专门做PostgreSQL与PostgreSQL/Oracle等多库数据对比支持行数、全量、抽样对比能输出差异报告。DataGrip/DBeaver的数据对比功能适合小表、交互式核对不适合大批量自动化。xtc校验工具一些开源实现里有基于CRC或MD5的文件级校验能力但直接用在数据库迁移上还是得改造。我的建议是工具只是辅助校验方案和口径才是核心。先把口径和字段映射规则定义清楚工具只是帮你把规则跑起来。4. 可回退机制给业务一颗定心丸4.1 回退方案设计的两种路线回退最理想的方案是双活也就是切换后Oracle和PostgreSQL都保持可写应用层按比例把流量分摊到两边某一侧出问题可以随时把流量全部切到另一侧。但双活对数据一致性的要求极高写入冲突处理的复杂度也很大普通项目很难真正做到。我们实际采用的是切换后保留源库只读反向同步增量的半双活方案。具体来说切换时Oracle侧停止业务写入但数据库实例不关闭归档日志继续保留切换后增量同步程序反向工作监听PostgreSQL的变更用PostgreSQL的逻辑复制功能把变更同步回Oracle如果业务验证期间发现重大问题需要回退我们先把PostgreSQL写入停止等最后一批变更同步到Oracle然后应用连接切回Oracle就能在较短时间内恢复服务。这个方案的关键在于反向同步的可靠性和时延。PostgreSQL的逻辑复制Logical Replication机制比较成熟但把逻辑复制的变更重放到Oracle端还是得靠自研程序或中间件。我们当时是写了一个消费者从PostgreSQL的WAL里解析出变更再合成SQL语句应用到Oracle。这里有个现实问题PostgreSQL逻辑复制默认只适合PostgreSQL到PostgreSQL跨库重放得自己处理类型映射和SQL方言差异工作量不小。4.2 回退窗口与观察期设计回退不是无限期的业务方也不能想什么时候退就什么时候退。我们在项目计划里明确了一个回退观察期切换后一个月内业务如果发现重大数据或功能问题启动回退流程超过一个月认为迁移已经稳定不再支持自动回退Oracle源库进入只读归档状态。这个观察期不是随便定的是根据业务体量和风险容忍度协商出来的。观察期越长反向同步的维护成本和资源成本越高太短业务不放心。我见过有些项目观察期只有一周结果第四天暴雷回退的时候反同步数据量太大差点没退干净。一般来说业务量大的核心系统观察期建议不低于两周最好是一个月非核心系统可以适当缩短。4.3 回退演练不能等到出事才练回退方案写了几页纸但真正出事的时候能不能按流程执行只有练过才知道。我们在正式切换之前做了两次完整的回退演练每次都能暴露问题。第一次演练暴露出一个经典问题切换后业务在PostgreSQL上生成了大量新数据回退时要把这些数据反向同步回Oracle结果因为表结构里有个字段类型映射不一致导致批量INSERT失败。这类问题如果不演练真到回退的时候就是灾难。第二次演练我们提前修正了映射又测了流量切换的脚本确认应用重启后能读到Oracle的最新数据整个回退耗时控制在40分钟内。所以我的建议是回退演练必须包含数据层和应用层两层验证。数据层验证反向同步是否完整、一致应用层验证连接切换、缓存清理、配置修改是否顺利。只测数据不测应用回退后业务还是起不来。5. 常见问题与排查技巧实录整个迁移过程中我们踩过的坑和排查过的问题不少挑几个有代表性的写在这里供大家参考。5.1 Oracle的CONNECT BY在PostgreSQL里怎么改Oracle的层次查询CONNECT BY PRIOR是迁移的高频改造点。PostgreSQL没有直接对应的语法要用递归CTEWITH RECURSIVE改写。比如Oracle里SELECT emp_id, mgr_id, LEVEL FROM emp START WITH mgr_id IS NULL CONNECT BY PRIOR emp_id mgr_id;改成PostgreSQLWITH RECURSIVE emp_tree AS ( SELECT emp_id, mgr_id, 1 AS level FROM emp WHERE mgr_id IS NULL UNION ALL SELECT e.emp_id, e.mgr_id, t.level 1 FROM emp e JOIN emp_tree t ON e.mgr_id t.emp_id ) SELECT emp_id, mgr_id, level FROM emp_tree;这里注意递归CTE的性能调优空间比较大如果树的深度和宽度都很大比如超过10层、几十万节点建议提前用真实数据量压测必要时加search_depth之类的优化手段。5.2ROWNUM和ROW_NUMBER()的等价改写Oracle里用ROWNUM做分页很常见SELECT * FROM ( SELECT t.*, ROWNUM rn FROM big_table t ) WHERE rn BETWEEN 101 AND 200;PostgreSQL里直接用LIMIT/OFFSETSELECT * FROM big_table ORDER BY id LIMIT 100 OFFSET 100;这里有个容易踩的隐形坑Oracle的ROWNUM是在排序之前生成的所以如果原SQL里写了ORDER BY再套ROWNUM语义上是先取前N行再排序和PostgreSQL的LIMIT语义先排序再取前N行不一样。改写时必须先确认原SQL的业务意图不然数据就错了。5.3 CLOB字段迁移到TEXT后的性能问题Oracle的CLOB在PostgreSQL里一般对应TEXT类型。看起来简单但实测发现一个现象某些业务对CLOB字段做频繁的UPDATE和SELECT在Oracle里有专门的LOB段和空间管理性能还行到PostgreSQL的TEXT上如果写入的文本很大几十KB到几MBTOAST机制会触发压缩和外部存储性能会有波动。我们的处理办法把大文本字段单独拆到一张扩展表里和主表用外键关联需要时才JOIN出来。如果业务确实要频繁读写大文本还可以把external存储打开避免每次查询都加载整个大字段。5.4 空字符串与NULL的语义差异Oracle里和NULL是等价的SELECT NVL(, x) FROM dual返回x。PostgreSQL里不是NULL。这一条差异在数据迁移时特别容易产生脏数据。我们在校验阶段发现不少表的某些字段两边值看起来不一致查了半天才发现是源库存在空字符串迁移时我们统一转换成了NULL但业务代码里有用判断的逻辑导致行为变了。后来我们定了规范Oracle到PostgreSQL迁移时空字符串按照Oracle语义统一转NULL同时业务代码里所有 的判断改为IS NULL。这个规范在改造阶段就要同步给开发团队。5.5 分区表迁移的正确姿势Oracle的分区表迁移到PostgreSQL有几种方案用PostgreSQL原生分区表基于声明式分区或者用继承分区或者用pg_partman这类扩展。我们的经验如果分区数量不大几十到几百个用原生声明式分区就够了如果分区数量上千原生分区的元数据管理会有压力可以考虑用pg_partman管理自动建分区。迁移时的一个常见坑Oracle分区表的全局索引和分区索引在PostgreSQL里没有完全对应的概念需要根据业务SQL的过滤条件重新设计索引策略。不要照搬Oracle的索引结构否则查询性能会很差。5.6 序列迁移的断档问题Oracle的SEQUENCE不保证连续PostgreSQL的SEQUENCE也一样。但两边序列的CACHE和INCREMENT配置不同步可能导致ID冲突或断档。我们迁移时把Oracle序列的LAST_NUMBER和INCREMENT_BY记录到PostgreSQL序列的同名参数上还要考虑增量同步阶段两边应用各拿各的序列号等切换后PostgreSQL序列起始值必须比Oracle当前值大很多否则切过来后新生成的ID可能和同步回来的历史数据撞车。这个问题真的很隐蔽但如果爆了就是主键冲突直接拖垮切换。建议切换前用脚本检查所有序列的当前值确保PostgreSQL侧的值远高于Oracle侧。5.7 常见问题速查表问题现象常见原因排查/处理办法校验时哈希不一致字段拼接顺序不一致、NULL/空串没统一、浮点精度差异统一拼接规则字段值做标准化增量同步延迟持续增大Kafka消费能力不足、目标库应用能力差、大事务卡住增加消费者并行度排查大事务必要时手动补数切换后应用报驱动错误JDBC连接串或驱动版本不兼容Postgres JDBC驱动与Oracle JDBC驱动用法不同检查URL参数、schema设置存储过程执行失败PL/SQL和PL/pgSQL语法差异人工改写重点处理游标、动态SQL、异常处理查询性能严重下降统计信息未收集、索引缺失、优化器差异切换后立即执行ANALYZE根据执行计划补索引回退时反向同步失败类型映射不一致、主键冲突、循环依赖提前演练建立主键冲突处理规则类型映射表要用同一份我自己走完这个项目后最深的体会是数据库迁移拼的不是单点技术有多强而是流程管理和风险把控。这三个目标——低中断、可校验、可回退——每一个都对应着一整套方案和工具链而它们之间又互相影响。建议准备动手的朋友先把你现有的停机窗口、数据量、业务容忍度都摆出来用这些边界条件去倒推每一步该怎么做。方案设计得越细执行的时候才越稳。再补充一点迁移过程中所有脚本和命令一定要先在小环境里完整演练一遍带上真实数据量的1%到5%压测一下性能别等到正式切换才发现导出速度、校验耗时、增量延迟这些数字完全不可控。
返回列表