☰
PHP脚本实现Oracle到MySQL数据迁移的完整实践与避坑指南
2026/10/6 19:13:05 网站建设 项目流程

前阵子接了个数据库搬迁的活儿,要把一套Oracle 11g里的业务表搬到MySQL 8.0。甲方内部运维平台是PHP技术栈,我也不想为了一个一次性迁移任务专门部署Kettle或者DataX客户端,最后决定直接用PHP脚本两头连库,把表结构转换、数据批量搬运、结果校验一整套流程全部自动化跑完。整个过程说难不算难,但坑是真不少,从Oracle的ROWNUM分页陷阱到NUMBER精度丢失,再到CLOB字段在PHP里的读取方式不对导致内存溢出,每一类都踩过一遍。这篇就当复盘记录,把当时的完整操作过程、核心代码和避坑清单全部摊开,给同样要处理Oracle到MySQL数据迁移的朋友一个可以直接照抄的示范。

1. 场景与选型:为什么用PHP做Oracle到MySQL的迁移

1.1 这个需求是怎么冒出来的

这类需求最常见的来源是系统重构或平台整合。我这次遇到的情况是,甲方核心业务跑在Oracle上,但新上的报表分析和内部管理系统用的是MySQL,两边数据不打通,报表库只能靠人工导出再导入,天天有人抱怨数据对不上。领导拍板说做一次存量数据迁移,把Oracle里的业务主数据、订单流水、工单记录这些同步到新库,后续再做增量。

如果你也接到类似任务,先别急着写代码,建议先花半天时间把下面几件事问清楚:迁移的数据范围是全部表还是部分表、源端业务在迁移窗口内是否停写、目标MySQL版本是多少、迁移完成后有没有数据校验要求。尤其是“源端是否停写”这点,直接决定你能否用“导出-导入”这种离线方案,还是要走在线同步。我这次业务窗口是周六晚上8点到周日早上6点,源端报表库可以整体停写,所以方案就简单很多:离线全量搬一次,搬完后锁库校准。

1.2 PHP脚本相比ETL工具和Navicat的取舍

很多人第一反应是用Navicat Premium,图形界面点几下就能把Oracle表结构和数据迁到MySQL。这话对一半。Navicat做小表、少表迁移确实快,我平时搭测试环境也这么干。但一旦涉及几十张表、表里有CLOB大字段、有Oracle特有的NUMBER精度、有需要按业务逻辑清洗的数据,图形工具就露馅了:它不让你在搬的过程中插入自定义转换逻辑,遇到报错只能整表重来,而且整个过程对开发者不透明,日志也不够细。

那为什么不用Kettle、DataX这些专业ETL工具呢?我当时的判断基于三点。第一,团队现有的运维调度平台就是PHP 7.4写的,脚本可以直接挂上去跑,不需要额外部署Java环境或ETL服务;第二,这次迁移不是全库盲迁,而是有选择的业务表,并且要做字段清洗和字典映射;第三,单表数据量在百万到千万行级别,PHP在合理批处理策略下性能完全够用。当然我也得说实话,如果哪天要迁移几十TB的仓库数据、或者要长期做实时增量同步,那肯定还是得老老实实上DataX、Flink CDC这类专业组件,PHP不是万能的。

1.3 三阶段思路:建表、搬数、校验

整个迁移脚本我把它拆成三个阶段,拆开之后思路会非常清晰,后续排查问题也不用在整个脚本里大海捞针。

第一阶段是结构迁移。读取Oracle的元数据,把每张表的字段名、类型、长度、精度、可空性、默认值都摸清楚,然后按照映射规则生成MySQL的建表语句。第二阶段是数据搬运。从Oracle逐批读取数据,做类型转换和清洗,再批量写入MySQL。第三阶段是校验。不能搬完就算完事,必须做行数比对、主键范围比对、抽样字段比对,确保两边数据一致后才能交付。

这里的核心原则是:结构、数据、校验三个环节尽量解耦。结构迁移失败不影响数据脚本,数据脚本可以断点续传,校验脚本独立跑。后面我会把每一阶段的实现细节详细展开。

2. 环境准备与数据摸底:先把两头连起来

2.1 Oracle连接配置与字符集问题

PHP连Oracle需要oci8扩展,这玩意儿比PDO连MySQL要麻烦不少,但装好之后非常稳定。Linux环境下一般用pecl安装:

pecl install oci8

如果是PHP 8.3,编译参数里要指定Instant Client的路径,比如:

pecl install oci8 --with-oci8=shared,instantclient,/usr/lib/oracle/21/client64/lib

装完记得看扩展有没有加载成功:

php -m | grep oci8

连接Oracle的时候,我建议不依赖tnsnames.ora,直接用EZ Connect或完整描述串拼连接地址,这样脚本换台机器也能跑。代码示例如下:

$oraConfig = [ 'host' => '192.168.1.101', 'port' => 1521, 'service' => 'ORCL', 'user' => 'mig_user', 'pass' => 'mig_pass', ]; $dsn = sprintf( '(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=%s)(PORT=%d))(CONNECT_DATA=(SERVICE_NAME=%s)))', $oraConfig['host'], $oraConfig['port'], $oraConfig['service'] ); $ora = oci_connect($oraConfig['user'], $oraConfig['pass'], $dsn, 'AL32UTF8'); if (!$ora) { $e = oci_error(); exit('Oracle连接失败: ' . $e['message']); }

这里有个最容易踩坑的细节:oci_connect的第四个参数是客户端字符集。我建议先执行下面这条SQL确认服务端字符集,再决定传什么值。

SELECT USERENV('LANGUAGE') FROM dual;

如果返回的是AL32UTF8,那客户端也传AL32UTF8,PHP里拿到的就是UTF-8字符串,直接进MySQL没问题。如果返回的是ZHS16GBK,而你在oci_connect里传了AL32UTF8,Oracle虽然会尝试做字符集转换,但某些生僻字、特殊符号在转换路径缺失的情况下会变成问号。我在项目中遇到过一次,最后老老实实客户端也用ZHS16GBK,PHP拿到GBK字符串后统一用mb_convert_encoding转成UTF-8再写入MySQL,问题才彻底解决。

2.2 MySQL连接和迁移用的辅助表

MySQL这边用PDO连接,配置相对简单:

$pdo = new PDO( 'mysql:host=192.168.1.102;port=3306;dbname=migration_target;charset=utf8mb4', 'mig_user', 'mig_pass', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ] );

需要注意,连接串里的charset一定要用utf8mb4,不要用utf8,因为MySQL的utf8是utf8mb3,存不了emoji和部分四字节字符。Oracle数据里如果混有特殊符号,用utf8mb3大概率写入时报错或者变问号。

在做数据搬迁之前,我建议先在目标库建一张迁移进度表,用来支持断点续传和状态跟踪:

CREATE TABLE migrate_progress ( table_name VARCHAR(64) PRIMARY KEY, last_key_value VARCHAR(64) DEFAULT NULL COMMENT '已搬完的最大主键值', total_rows BIGINT DEFAULT 0, status ENUM('running', 'done', 'failed') DEFAULT 'running', updated_at DATETIME );

这张表虽然不起眼,但关键时刻能救命。后面第3.4节我会专门讲怎么用它接续中断的迁移任务。

2.3 通过Oracle元数据自动生成MySQL建表语句

结构迁移阶段的核心,是把Oracle的数据字典读出来,翻译成MySQL的DDL。先说查询表清单:

SELECT table_name FROM all_tables WHERE owner = 'MIG_USER' AND table_name NOT LIKE 'BIN$%' ORDER BY table_name;

WHERE里过滤掉BIN$开头的表很重要。Oracle在drop table之后,表会进回收站,实际还能查到,名字变成BIN$+一串乱码。如果不过滤,你会在迁移清单里看到一堆莫名其妙的历史垃圾表。另外,如果账号权限不够查all_tables,可以改用user_tables,但那样就只能看到当前账号拥有的表,需要根据实际情况调整。

再看单表的字段信息:

SELECT column_name, data_type, data_length, char_length, data_precision, data_scale, nullable, data_default FROM all_tab_columns WHERE owner = 'MIG_USER' AND table_name = 'ORDER_HEADER' ORDER BY column_id;

这里有一个极其关键的细节,Oracle的VARCHAR2(n)里的n默认是字节数还是字符数,取决于参数NLS_LENGTH_SEMANTICS。而all_tab_columns里有data_length和char_length两个字段,data_length是字节长度,char_length是字符长度。映射到MySQL的VARCHAR(n),单位是字符,所以必须用char_length,否则中文场景下会白白浪费一倍的字段空间,极端情况还会因为超长导致插入失败。

拿到字段元数据之后,按照映射规则生成建表语句,映射表我放在第3.2节详细讲。生成DDL时还有一些细节要注意:MySQL的标识符如果和保留字冲突,要用反引号包起来;排序规则建议用utf8mb4_bin而不是默认的utf8mb4_0900_ai_ci,因为Oracle的字符串比较默认是二进制排序,如果你在Oracle里建了大小写敏感的唯一索引,迁到MySQL后如果用ai_ci排序,唯一约束可能不生效或者行为不一致。

3. 数据搬运的核心逻辑:游标分批、类型转换与批量写入

3.1 游标流式读取,避免分页陷阱

数据搬运最忌讳的做法是把整张表一次性SELECT到PHP内存里再循环处理。千万行的表,就算每行只有几百字节,PHP进程内存也扛不住。更合理的方案是游标流式读取,配合oci_set_prefetch控制每次从Oracle抓取的行数。

$stmt = oci_parse($ora, "SELECT * FROM {$table}"); oci_set_prefetch($stmt, 500); oci_execute($stmt); while ($row = oci_fetch_array($stmt, OCI_ASSOC | OCI_RETURN_NULLS)) { // 处理单行 }

这里有两个点需要说明。第一,oci_set_prefetch的作用是让Oracle每次网络往返多返回一些行,减少PHP和Oracle之间的交互次数,默认值是100,调到500在普通宽表上是比较舒服的。但如果表里有大的CLOB字段,prefetch值不能调太大,否则每个缓冲区都要预留大对象空间,内存反而会涨。

第二,很多人习惯用ROWNUM做分页查询,像这样:

$sql = "SELECT * FROM ( SELECT a.*, ROWNUM rn FROM ( SELECT * FROM {$table} ORDER BY {$orderCol} ) a WHERE ROWNUM <= " . ($offset + $limit) . " ) WHERE rn > {$offset}";

这个小表、少表的时候没问题,但大表用这种方式分页有两个隐患。一是ORDER BY的列如果没有索引,Oracle要全表排序,越到后面越慢;二是如果排序键不唯一,分页查询的结果可能不稳定,出现重复或漏行。所以我的建议是:能游标流式读取就坚决不用ROWNUM分页。

但流式读取又带来另一个问题:如果脚本中途断了,怎么知道上次读到哪了?答案是用主键做续传标记,这在第3.4节展开。

3.2 字段类型映射与清洗规则

Oracle和MySQL的数据类型不是一一对等的,映射错了轻则报错,重则数据静默损坏。下面这张表是我实际项目中用的映射规则,可以直接抄:

Oracle类型MySQL类型迁移说明
NUMBER(1)TINYINT通常用来表示布尔或标志位
NUMBER(5)SMALLINT
NUMBER(10)INT
NUMBER(18)BIGINT
NUMBER(19)及以上DECIMAL(p,0)超过BIGINT范围,必须用DECIMAL
NUMBER(p,s)DECIMAL(p,s)保留精度,金额字段尤其重要
VARCHAR2(n)VARCHAR(n)用char_length做长度映射
CHAR(n)CHAR(n)保留原语义
DATEDATETIMEOracle的DATE包含时分秒,MySQL的DATE只到天
TIMESTAMPDATETIME(6)保留微秒精度
CLOBLONGTEXT注意MySQL单行上限是64MB
BLOBLONGBLOB
RAW(n)BINARY(n)/VARBINARY(n)

这里我重点说三个坑。

第一个坑是NUMBER精度丢失。Oracle的NUMBER最高能表示38位十进制数字,而MySQL的BIGINT只有19位。更阴险的是,PHP的oci8扩展在读取NUMBER列时,超过一定位数的值会直接转成float,而float只有53位二进制精度,换算下来大约15到16位十进制数字。也就是说,NUMBER(20,0)的值如果超过2^53,PHP端读到的就是精度丢失过的近似值,写进MySQL就晚了。

我的处理方案是:元数据阶段扫描所有NUMBER列,凡是data_precision大于等于16的,在查询SQL里直接用TO_CHAR包一层,让PHP收到的是字符串,再原样写入MySQL的DECIMAL列。具体生成SELECT语句时动态拼一下就行:

$selectParts = []; foreach ($columns as $col) { if ($col['data_type'] === 'NUMBER' && $col['data_precision'] >= 16) { $selectParts[] = "TO_CHAR({$col['column_name']}) AS {$col['column_name']}"; } else { $selectParts[] = $col['column_name']; } } $sql = "SELECT " . implode(', ', $selectParts) . " FROM {$table}";

第二个坑是空字符串。Oracle 11g里,空字符串和NULL在存储上是等价的,你用WHERE col = ''查不到任何东西,因为空字符串会被自动转成NULL。而MySQL里空字符串和NULL是两个完全不同的值。所以迁移时你要先问清楚业务语义:这个字段到底是“没有填”还是“填了空值”。我的做法是在清洗阶段统一处理,把Oracle传来的NULL和空串都保持原样,但在文档里明确标注,让业务方确认。

第三个坑是DATE类型。Oracle的DATE是包含时分秒的,MySQL的DATE只到天。如果映射成DATE,等于把所有记录的时间部分全部丢掉,而且这个错误是静默的,查COUNT不会报错,但业务跑起来才发现时间对不上。所以Oracle DATE必须映射成MySQL DATETIME,这个是硬规则。

CLOB字段也单独说一下。oci8扩展里,CLOB列fetch出来之后不是一个普通的PHP字符串,而是一个OCI-Lob对象,不能直接当字符串用。我在脚本里统一判断,如果是对象就调用load()方法取内容:

case 'CLOB': $row[$col] = is_object($val) ? $val->load() : $val; break;

如果你的CLOB值超过几十MB,load()一次性把整个对象读进内存也可能爆,那就得改用oci_lob_read分段读,但那种超大CLOB在实际业务表里非常少见,遇到一个处理一个就好。

3.3 批量写入MySQL与事务粒度控制

从Oracle读出来的数据逐行处理完,不能逐行insert到MySQL,那样不仅慢,而且会给InnoDB造成巨大的提交压力。我的做法是攒一批,拼成一条多VALUES的INSERT语句,一次性执行。

function batchInsert(PDO $pdo, string $table, array $rows): void { $cols = array_keys($rows[0]); $colStr = implode(',', array_map(function ($c) { return "`{$c}`"; }, $cols)); $placeholders = '(' . implode(',', array_fill(0, count($cols), '?')) . ')'; $sql = "INSERT INTO `{$table}` ({$colStr}) VALUES " . implode(',', array_fill(0, count($rows), $placeholders)); $stmt = $pdo->prepare($sql); $params = []; foreach ($rows as $row) { foreach ($cols as $c) { $params[] = $row[$c]; } } $stmt->execute($params); }

批量大小的选择,我实测下来500行左右比较合适。如果表有50列,500行就是25000个参数,PDO和MySQL处理起来都很轻松。如果列数更多比如100列,那建议降到200到300行,因为还要考虑max_allowed_packet的约束。行宽特别大、有多个大文本字段的表,我用的是100行一批,稳妥优先。

关于事务粒度,我自己做过对比测试。一开始我图省事,整个表只开一个事务,搬完再commit,结果跑到一半Oracle那边网络抖了一下,脚本中断,MySQL回滚了快二十分钟才缓过来。后来改成每500行一个事务,500行数据量很小,回滚代价完全可以接受,而且InnoDB的锁释放更平滑,总体耗时反而比大事务还少一些。所以我的建议是:不要迷信大事务更快,分批提交在这个场景下既安全又高效。

写入之前,还有两个MySQL侧的开关建议在迁移会话里设置:

SET foreign_key_checks = 0; SET unique_checks = 0;

外键和唯一约束在校验阶段做一次全量扫描就够了,迁移过程中关掉能省下大量索引维护开销。跑完记得恢复,不然业务上线会出大问题。

3.4 断点续传与运行日志

迁移脚本跑几个小时,中间不中断的概率其实很低。可能是网络抖动,可能是源库有Query被杀掉,也可能就是PHP脚本自身触发了内存上限。如果没有断点续传能力,每次都要从头跑,十万行的表还好,千万行的表会让人崩溃。

我的方案是前面建的那张migrate_progress表。每张表开始迁移前先检查一下状态和目标端当前的最大主键值,如果已经跑过一部分,就从这个主键值接着拉,跳过已经搬完的数据。

具体实现思路不复杂。假设表的主键是ID,那么每搬完一批,就取这一批里最大的ID,更新到progress表:

function updateProgress(PDO $pdo, string $table, array $rows): void { $lastKey = max(array_column($rows, 'ID')); $stmt = $pdo->prepare( "INSERT INTO migrate_progress (table_name, last_key_value, updated_at, status) VALUES (?, ?, NOW(), 'running') ON DUPLICATE KEY UPDATE last_key_value = VALUES(last_key_value), updated_at = NOW()" ); $stmt->execute([$table, $lastKey]); }

然后Oracle侧读取数据时,加上主键过滤条件:

$lastKey = getLastKey($pdo, $table); if ($lastKey !== null) { $sql = "SELECT * FROM {$table} WHERE ID > " . intval($lastKey); } else { $sql = "SELECT * FROM {$table}"; }

这里有一个隐藏前提:主键列的数据在迁移窗口内必须是单调递增的,或者说源端在迁移期间不再修改已存在行的主键。我这次迁移窗口内源表是停写的,所以这个方案完全够用。如果源库还在持续写入,那要处理的就是增量同步问题,不是简单的主键续传能覆盖的。

运行日志方面,我强烈建议每张表处理完都打一条汇总日志,包含表名、行数、耗时、最后主键值。格式不用花哨,CLI里echo一行就行:

[2025-01-18 22:31:05] ORDER_HEADER | rows=1843720 | elapsed=17m32s | max_id=1843720

这些日志一方面是给人看的,另一方面后续写校验脚本也要用到。

3.5 迁移后的三层校验

数据搬完不等于活干完,校验不过关上线之后出了事,那才是真的灾难。我在这个项目里做了三层校验,每一层对应不同粒度的风险。

第一层是行数比对,也是最基本的。Oracle执行SELECT COUNT(*) FROM 表,MySQL执行同样的SQL,两边结果必须一致。如果对不上,说明这一张表就有问题,后面两层不用做了,优先排查断点续传逻辑和主键边界。

第二层是主键范围比对。分别查出两边的MIN(ID)和MAX(ID),如果源端是连续的1到N,目标端也是连续的1到N,勉强算OK。不连续不能说明丢了数据,但至少要能解释得通。

第三层是抽样比对。随机抽20到50个主键,分别从两边查出完整记录,逐字段比较。我通常选主键模10等于某个余数的策略,伪随机但可复现,比程序里的mt_rand更利于排查。比对脚本会输出差异字段、两边各自的值,以及主键ID,这样业务方拿到差异清单可以直接去对。

这三层校验跑完,我才会把migrate_progress里的状态改成done,并通知业务方验收。

4. 常见问题与性能优化速查

4.1 字符集和空字符串:两个最容易埋雷的地方

字符集问题在外观上表现得很隐蔽,不是上来就报错,而是数据里偶尔出现问号、乱码、甚至字段截断。排查思路是从源端开始一层层看:Oracle服务端字符集是什么、oci_connect客户端字符集传的什么、PHP字符串当前是什么编码、MySQL连接charset是什么、MySQL表定义charset是什么。五个环节只要有一环不一致,结果就不可控。

我这次踩过的具体问题是:Oracle服务端是ZHS16GBK,客户端也用了ZHS16GBK,PHP得到GBK字符串,但写进MySQL时连接参数是utf8mb4,导致MySQL把GBK字节流当成UTF-8校验,遇到某些字节组合直接报错。后来的处理方案是在PHP里统一转码,只在读出来的那一刻做一次mb_convert_encoding,不要在写入前到处转,否则会漏。

空字符串的坑前面已经说过根源,这里补充一个我在排查时用过的SQL,可以快速找出“看起来是空但实际不是”的数据:

SELECT * FROM target_table WHERE col IS NULL OR col = '';

Oracle源端基本查不到col = ''的数据,因为都被存成NULL了,所以目标端出现空串反而说明数据被动过或者类型转换逻辑有问题,要查清楚。

4.2 大字段与特殊类型处理

CLOB、BLOB这类大对象字段,普通字符串处理方式在这里行不通。我自己写迁移脚本时,对CLOB的处理是先判断取出来的值是不是对象,再决定是否调用load()。这里要特别注意,load()是一次性把整个CLOB装进PHP内存,如果一个CLOB值有几百MB,PHP的memory_limit再高也会被吃穿。

实际项目中我遇到过一个极端表,里面有CLOB字段存了PDF的Base64文本,最长的一条超过120MB。对这种表,我把批量大小降到了20行一批,并且给PHP脚本单独设置了memory_limit=1G。即便如此,那个120MB的字符串还是让脚本进程占用接近300MB内存,跑了很久才搬完。事后复盘,更好的方案是用oci_lob_read按块读,再分段写入MySQL,但那样代码复杂度会明显上升,对于一次性迁移来说性价比不高。

TIMESTAMP类型如果是TIMESTAMP(6),微秒精度也不能丢。PHP的date函数格式化后最多到秒,超出部分的微秒会被截掉。如果业务对时间精度敏感,需要保留微秒,那在读取时就别用date格式化,直接把Oracle返回的字符串原样写入MySQL的DATETIME(6)列,让MySQL自己解析。

4.3 提速手段:关校验、调包大小、合理提交

迁移速度的瓶颈通常不在PHP代码本身,而在于数据库的写入路径。MySQL侧我实测有效的手段,按效果排序是:关外键和唯一校验、先搬数据后建索引、适当调大max_allowed_packet、批量语句复用。

“先搬数据后建索引”这条迁移铁律值得单独强调一下。如果目标表在搬之前就已经建好了好几个二级索引,每次INSERT都要同步更新所有索引页,速度会肉眼可见地下降。我的做法是数据全部搬完并校验通过后,再一次性执行建索引的DDL。MySQL 8.0重建索引的速度比边插边建快很多,而且不会造成碎片。

关于max_allowed_packet,默认配置下批量INSERT语句如果特别长(比如几百个字段、几千行),可能直接报Packet too large。我的建议是把它设为64MB,同时把一批的行数控制在合理范围内,不要让一条SQL真的逼近这个上限。毕竟网络传输、SQL解析、参数绑定都是有成本的。

PHP侧还有一个容易被忽略的点:如果你在读取循环里做大量的字符串替换或正则清洗,一定要保证这些操作是必要的,而不是“顺手加上的”。我见过不少同事写迁移脚本时喜欢给每个字段都过一遍htmlspecialchars,这种多余的处理在大表场景下会显著拖慢速度。

4.4 常见问题速查表

症状根本原因处理方式
中文乱码、问号占位Oracle服务端与客户端字符集不一致先查USERENV('LANGUAGE'),统一转码后写入UTF-8目标库
空字符串查不到/存不进去Oracle空字符串=NULL,MySQL两者区分迁移前确认业务语义,清洗阶段统一处理
大数变成科学计数法或精度丢失NUMBER(20+)超过BIGINT范围,PHP float精度不够SELECT时用TO_CHAR包列,目标用DECIMAL
CLOB取出来是Resource对象oci8返回OCI-Lob对象判断is_object后调用load()
目标端插入报主键冲突/唯一键冲突源端数据本身有重复,或唯一约束语义不一致先查源端重复数据,确认后再迁移
迁移中途断网,从头再来没有断点续传建migrate_progress表,按主键续传
PHP内存溢出prefetch过大+大字段批量过大调小prefetch和批量行数,提高memory_limit
MySQL报Packet too large单条INSERT太长,超过max_allowed_packet调大max_allowed_packet,或减小单批行数
迁移很慢目标表索引太多、外键/唯一校验开启先搬数据后建索引,迁移会话关掉外键唯一校验

这个表是我这次迁移过程中实际遇到过的所有问题,对应的处理方式都验证过,不是理论推演。如果朋友的场景里还遇到过其他奇怪现象,大概率也逃不开“类型映射、字符集、批量策略、约束冲突”这四个大方向,沿着这个思路去查基本都能定位。

回头复盘这次迁移,真正花时间的其实不是写PHP脚本本身,而是摸清源库的字段语义、边界值和类型边界。第一次拿Navicat导小表时看着挺顺,遇到NUMBER(20)和CLOB就原形毕露。脚本只能保证把数据原样搬过去,搬完之后找业务方一起抽查关键表、核对金额和时间字段,才是这个活儿真正收尾的标志。这些坑踩过一遍之后,后面再听到“把Oracle迁到MySQL”这种需求,心里就有数了。如果需要在此基础上继续做增量同步,那就要换一套思路去接数据库日志或者CDC组件,不过那就是另一个故事了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询