尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SQL Server迁移Oracle实战:避坑指南与流程梳理

SQL Server迁移Oracle实战:避坑指南与流程梳理 去年接手了一个从 SQLServer 2016 迁移到 Oracle 19c 的项目数据库一千多张表两百多个存储过程还有一堆定时作业和视图整个迁移周期前后折腾了三个多月。中间踩的坑多得数不过来但也正因为踩得够多才整理出一套相对顺手的流程。这篇就当给后面要做同类数据库迁移的朋友一份“避坑参考”核心关键词就三个SQLServer、Oracle、数据库迁移。适合要把业务库从 SQLServer 迁到 Oracle 的 DBA也适合被公司要求改写数据访问层的后端开发以及做数仓集成的同学。1. 项目概述与迁移思路拆解1.1 为什么要把 SQLServer 迁到 Oracle这个问题很多人第一反应是“Oracle 是不是比 SQLServer 好”。其实做迁移核心从来不是“谁更强”而是业务和架构层面的诉求。我这次遇到的情况属于典型的集团型改造集团统一数据库标准所有子公司系统逐步收拢到 Oracle 体系方便后续做统一运维、统一备份和集中监控。还有一种高频场景是第三方系统只支持 Oracle比如一些大型 ERP、Primavera P6 之类的项目管理软件底层只能是 Oracle这时候你就不得不把周边系统的数据并过去。再一种是为了做跨库关联查询数据要往 Oracle 数仓里汇总SQLServer 作为源库定期同步。另外从数据库本身能力来看Oracle 在分区、并行执行、RAC 集群、事务一致性这些方面确实有它的优势某些重 OLTP 场景下迁移过去之后性能反而更稳。但我不建议单纯因为“听说 Oracle 更牛”就迁迁移的成本很高如果没有明确的业务驱动折腾半天可能只换来一堆兼容性问题。1.2 整体迁移方案与工具选型数据库迁移有三种主流路线我分别说下适用场景和优缺点。第一种是用官方迁移工具。SQLServer 这边有 SSMA for OracleSQL Server Migration AssistantOracle 这边有 SQL Developer 自带的 Migration Workbench。这类工具能把表结构、数据类型、约束、索引、视图、存储过程等大部分对象自动转换生成对应的 Oracle 脚本也能做表数据在线迁移。优点是自动化程度高适合表多、对象多的大库缺点是转换完之后语法层面的东西往往不能直接用尤其是存储过程几乎都要手工改一遍工具帮不了太多。第二种是用 ETL 工具比如 Kettle、DataX、Informatica。这类工具核心解决的是“数据搬运”问题不处理结构转换。适合异构表结构需要做映射、清洗、转换的场景。比如源表字段名叫 user_name目标库要求叫 username或者源库类型是 varchar(20)目标库想改成 NVARCHAR2(30)ETL 中间层可以做灵活的转换。缺点是对象定义视图、存储过程、作业你还是要单独处理。第三种是纯手工脚本。用 SSMS 生成建表脚本手工改成 Oracle 语法再用 DBLINK 或者数据导出导入来灌数据。这种方式最灵活每一步都知道在干什么出了问题也容易定位但工作量巨大只适合表数量少的小型系统。我做一千多张表的迁移时用的组合方案是SSMA 做结构转换和基础数据迁移存储过程和视图手工改写最后用脚本做全量校验。这里给你一个非常实在的建议别指望一个工具从头用到尾组合拳才是数据库迁移的正确打开方式。1.3 迁移范围和对象清单梳理动手之前先把迁移范围理清楚这是整个项目最容易忽略却又最重要的一步。我列一份当时整理的迁移对象清单你可以直接拿来当模板用。对象类型源库 SQLServer目标库 Oracle迁移方式表结构CREATE TABLECREATE TABLESSMA 转换 / 手工改写表数据全量导出SQL*Loader / DBLINK按表分批导出导入主键与唯一约束PRIMARY KEY / UNIQUEPRIMARY KEY / UNIQUE结构转换时一并生成外键FOREIGN KEYFOREIGN KEY建议数据迁移完再启用索引CREATE INDEXCREATE INDEX结构转换非唯一索引可延后视图CREATE VIEWCREATE VIEW手工改写验证列顺序和函数存储过程CREATE PROCEDURECREATE PROCEDURE手工改写为主触发器CREATE TRIGGERCREATE TRIGGER手工改写注意语法差异序列IDENTITY / 手动序列SEQUENCE手工创建并同步初始值作业 / 计划任务SQL Server Agent JobDBMS_SCHEDULER手工重建自定义函数标量函数 / 表值函数PL/SQL 函数手工改写注意不是所有对象都要迁。比如 SQLServer 的全文索引、变更数据捕获CDC这些如果业务上没用到或者迁移完之后有替代方案就可以暂时不迁。我在做清单的时候就果断砍掉了一批报表用的临时表这些表本来就是中间结果迁过去纯粹浪费空间。2. 迁移前的关键细节类型与语法差异2.1 数据类型映射对照表这一节可能是整个迁移里最枯燥但又最关键的部分。SQLServer 和 Oracle 的数据类型看着差不多实际用起来到处是坑。先看这张映射表是经过实战验证的版本。SQLServer 类型Oracle 类型说明与注意事项varchar(n)VARCHAR2(n)注意 Oracle 默认长度单位是字节需要确认 NLS_LENGTH_SEMANTICSvarchar(max)CLOB大文本字段长度不可控nvarchar(n)NVARCHAR2(n)长度单位为字符适合中文场景nvarchar(max)NCLOB大文本兼容 UnicodeintNUMBER(10)int 最大值约 21 亿NUMBER(10) 够用bigintNUMBER(19)对应 bigint 范围smallintNUMBER(5)2 字节整数tinyintNUMBER(3)0~255decimal(p,s)NUMBER(p,s)几乎一一对应直接映射numeric(p,s)NUMBER(p,s)同上bitNUMBER(1)SQLServer 的 bit 只有 0/1/NULLdatetimeTIMESTAMP(3) / DATE建议用 TIMESTAMP保留小数秒datetime2TIMESTAMP(6)精度更高需要保留到微秒的场景smalldatetimeDATE精度到分钟dateDATE一致timeINTERVAL DAY TO SECOND注意 Oracle 没有独立的 TIME 类型uniqueidentifierRAW(16)GUID 存储映射为 16 字节moneyNUMBER(19,4)金额类型注意是 4 位小数realBINARY_FLOAT单精度浮点floatBINARY_DOUBLE双精度浮点imageBLOB二进制大对象varbinary(max)BLOB同上xmlXMLTYPE可以用但性能一般这里面最大的坑是 varchar 的长度单位。SQLServer 的 varchar(n) 里的 n 是字符数Oracle 的 VARCHAR2(n) 默认 n 是字节数。如果你的库用的是 AL32UTF8 字符集一个中文汉字占 3 个字节那 SQLServer 里的 varchar(20) 在 Oracle 里如果直接建 VARCHAR2(20)只能存 6 个汉字超长直接报 ORA-12899。解决方法是建库时把 NLS_LENGTH_SEMANTICS 设为 CHAR或者 DDL 里写成 VARCHAR2(20 CHAR)。经验之谈迁移之前先把 Oracle 的 NLS_LENGTH_SEMANTICS 参数确认好否则等你迁到一半发现所有长度都差两倍改起来要命。2.2 SQL 语法与函数差异的改写要点数据类型只是第一关SQL 语法差异才是真正磨人的地方。先看几个高频差异点。分页查询SQLServer 最常见的写法是用 TOP或者 OFFSET FETCH。Oracle 11g 及以下常用 ROWNUM12c 以上支持 FETCH FIRST。-- SQLServer SELECT TOP 10 * FROM users WHERE status 1; SELECT * FROM users ORDER BY id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY; -- Oracle 12c SELECT * FROM users WHERE status 1 FETCH FIRST 10 ROWS ONLY; SELECT * FROM users ORDER BY id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY; -- Oracle 11g 及以下 SELECT * FROM ( SELECT u.*, ROWNUM rn FROM ( SELECT * FROM users ORDER BY id ) u WHERE ROWNUM 30 ) WHERE rn 20;注意 Oracle 的 ROWNUM 是在 WHERE 过滤之后、ORDER BY 排序之前分配的所以用 ROWNUM 做分页必须先排序再套一层子查询顺序不对结果就乱了。这个坑我见人踩过无数次。字符串拼接SQLServer 用 拼接字符串Oracle 用 ||。更坑的是如果拼接的字段里有 NULLSQLServer 会直接忽略 NULL 继续拼Oracle 则整个结果变成 NULL。所以很多 SQLServer 里写的好好的拼接语句迁到 Oracle 后得到一片空白需要加上 NVL 处理。-- SQLServer SELECT first_name last_name FROM employees; -- Oracle SELECT first_name || || NVL(last_name, ) FROM employees;日期时间处理SQLServer 的 GETDATE() 在 Oracle 里就是 SYSDATEGETUTCDATE() 对应 SYSTIMESTAMP AT TIME ZONE UTC。DATEADD、DATEDIFF 这类函数在 Oracle 里没有直接对应物需要改成 ADD_MONTHS、INTERVAL 或者 NUMTODSINTERVAL。-- SQLServer 获取当前时间 SELECT GETDATE(); -- Oracle SELECT SYSDATE FROM DUAL; SELECT SYSTIMESTAMP FROM DUAL; -- SQLServer 取当前时间加 1 天 SELECT DATEADD(DAY, 1, GETDATE()); -- Oracle SELECT SYSDATE 1 FROM DUAL; -- SQLServer 两个日期相差天数 SELECT DATEDIFF(DAY, start_date, end_date) FROM orders; -- Oracle SELECT end_date - start_date FROM orders;Oracle 里日期直接加减整数整数单位是“天”这点和 SQLServer 完全不同。如果你要算小时就得用 (end_date - start_date) * 24。NULL 处理函数SQLServer 的 ISNULL(expr, value) 对应 Oracle 的 NVL(expr, value)COALESCE 两个库都有但行为有细微差异。最需要注意的是 Oracle 里空字符串 会被当成 NULL 处理这个和 SQLServer 差异极大。比如 SQLServer 里 WHERE name 可以查到空字符串的数据Oracle 里这个条件等价于 WHERE name NULL永远查不到结果。迁移 SQL 后这类隐性 bug 非常难排查建议全库搜索一下这类写法。字符串转数字SQLServer 里直接 CAST(123 AS INT)Oracle 里用 TO_NUMBER(123)。如果字符串里有非数字字符TO_NUMBER 会直接报 ORA-01722。而 SQLServer 的 CONVERT 在某些情况下不会报错行为差异很明显后面我会在常见问题里细说。2.3 字符集与中文乱码隐患数据库迁移最让人头痛的问题不是 SQL 语法而是乱码。迁移前先确认两边字符集。Oracle 这边常见的有 AL32UTF8 和 ZHS16GBKSQLServer 有 Chinese_PRC_CI_AS 之类的排序规则。原则是目标库字符集最好能覆盖源库所有字符。如果源库是 ZHS16GBK、目标库是 AL32UTF8那么绝大部分中文可以无损迁移因为 UTF-8 是 GBK 的超集。但反过来就有风险比如源库里有生僻字或 emoji 字符GBK 存不下可能已经变成乱码。更麻烦的是 emojiUTF-8 编码需要 4 字节如果 Oracle 是 AL32UTF8插入 emoji 还可能报 ORA-12899。这类问题的排查方式是用一个脚本检测源库中是否存在目标字符集无法表示的字符提前发现问题比迁移完再去补救要省事得多。连接层也要注意。JDBC 连接 Oracle 时URL 里加上 oracle.net.ssl_server_dn_matchfalse 之类的不太实用真正有用的是设置 NLS_LANG 环境变量。Linux 下做数据导入NLS_LANG 设置不对SQL*Loader 导进去的数据可能全是问号。我一般设置为 AMERICAN_AMERICA.AL32UTF8 或者 SIMPLIFIED CHINESE_CHINA.ZHS16GBK取决于客户端字符集和目标库的对应关系。3. 实操过程从表结构到数据的一步步迁移3.1 环境准备与连接配置迁移之前先把两边环境配好。这里说的“环境”不只是软件能跑起来而是让你后面所有操作都顺畅的底层准备。Oracle 端准备Oracle 安装本身是个大工程网上教程一堆我不重复了只说两个容易出问题的地方。第一是监听服务。你可能会遇到 Oracle 监听服务无法启动的情况常见原因有三个监听配置文件 listener.ora 写错、端口被占用、Oracle 服务没起来。排查思路是先看服务列表里 OracleOraDB19Home1TNSListener 是否已启动没启动就手动拉起来再检查端口 1521 有没有被别的程序占用用 netstat -ano | findstr 1521 看一眼最后用 lsnrctl status 看监听状态status 能正常输出说明监听基本没问题。第二是 ORA-28547 这类连接错误。报错文本大概是 ORA-28547: connection to server failed, probable Oracle Net admin error。这个问题多半是客户端和服务器端 Oracle Net 配置不一致或者是 tnsnames.ora 里主机名解析不了。检查思路先 ping 主机名看通不通再用 tnsping 测试 Oracle 网络服务名如果 tnsping 能通而程序连不上重点检查 JDBC URL 里的 SID 或 SERVICE_NAME 是否正确。很多时候程序里写的是 SID而监听配的是 SERVICE_NAME对不上就报这个错。SQLServer 端准备SQLServer 这边重点是给迁移账号开权限以及做一次完整备份。迁移账号至少要 db_datareader 和 db_ddladmin 权限如果要用 SSMA 做结构转换还需要能读取系统视图的权限。另外建议迁移期间把目标表上的触发器、外键约束先禁用掉等数据导完再启用这样插入速度会快很多。SQLServer 表数据批量读取时可以加 WITH (TABLOCKX) 提示减少锁冲突和日志开销实测大表读取效率提升明显。连接字符串小工具联调阶段我习惯写一个简单的 Java 程序或者 Python 脚本测试两边数据库连通性也方便后续做数据抽样比对。这里分享一个用 Python 连接两个数据库的参考写法做数据量核对特别好用。import pyodbc import oracledb # SQLServer 连接 conn_sqlserver pyodbc.connect( DRIVER{ODBC Driver 17 for SQL Server}; SERVER192.168.1.100;DATABASEmydb;UIDsa;PWDyourpassword ) # Oracle 连接 conn_oracle oracledb.connect( usermig_user, passwordyourpassword, dsn192.168.1.200:1521/ORCLPDB1 ) # 分别执行查询统计行数 cursor_sqlserver conn_sqlserver.cursor() cursor_sqlserver.execute(SELECT COUNT(*) FROM users) print(SQLServer count:, cursor_sqlserver.fetchone()[0]) cursor_oracle conn_oracle.cursor() cursor_oracle.execute(SELECT COUNT(*) FROM users) print(Oracle count:, cursor_oracle.fetchone()[0])这种直连脚本在整个迁移过程中会反复用到建议提前准备好。3.2 表结构与数据迁移实操我用的是 SSMA 转换结构、DBLINK 灌数据、再手工校验的组合方案。下面把关键步骤拆开。用 SSMA 转换表结构SSMA for Oracle 安装好不好用关键在于流程要顺着它的思路来。顺序是新建迁移项目选择源数据库类型为 SQLServer选择目标版本为 Oracle连接源库和目标库选中需要迁移的表和视图右键选择“Convert Schema”查看转换报告重点看“Conversion Result”里标红、标黄的项。转换报告里标红的项基本都要手工处理。比如 uniqueidentifier 类型的字段SSMA 有概率转成 RAW(16)这个没问题但 varchar(max) 转成 CLOB 后如果源字段上建了索引Oracle 里 CLOB 不能直接建索引这时候需要改成 VARCHAR2(4000) 或者用函数索引。还有 bit 类型的字段SSMA 默认转成 NUMBER(1)但在 Oracle 里 NUMBER(1) 不能直接作为布尔值使用存储过程里如果对这类字段做判断需要手动改成 NUMBER(1) 加 CHECK 约束或者直接在代码里处理。结构转换完成后把生成的 DDL 脚本在目标库执行一遍。注意执行顺序先建表再建索引最后建约束和外键。顺序反了建外键时可能因为表不存在而报错。数据迁移的两种方式数据量在百万级以下我推荐直接用 DBLINK 加 INSERT 的方式简单可控。在 Oracle 里建立一个 DBLINK 指向 SQLServer需要一个 Oracle 网关或者透明网关配置相对繁琐。如果你们环境有 ODI 之类的工具也可以直接用。更通用的做法是分批导出再导入。先看一下目标 Oracle 表结构对不对然后从 SQLServer 端用 BCP 工具把数据导出为 CSV 文件再用 Oracle 的 SQLLoader 导入。SQLLoader 的控制文件大概长这样LOAD DATA INFILE users_data.csv INTO TABLE users FIELDS TERMINATED BY , OPTIONALLY ENCLOSED BY TRAILING NULLCOLS ( id, name, email, created_at DATE YYYY-MM-DD HH24:MI:SS )这里有两点要注意。第一CLOB 字段用 SQL*Loader 导入时默认 64KB 以内的文本能直接处理超过需要设置 LOBFILE不然长文本会被截断。第二CSV 里如果有换行符建议用 OPTIONALLY ENCLOSED BY 包裹并且导入前检查源数据是否包含分隔符或换行否则会出现列错位。禁用索引提升导入速度大批量数据导入时Oracle 维护索引的开销非常大。我当时导入一张两千万行的大表开着索引导了五个小时没导完后来把非唯一索引全部 drop只保留主键导入时间直接缩到四十分钟。导入完成后再重新创建索引建索引本身也花了二十分钟但总时间还是省了一大截。这就是实战里非常实用的策略数据加载阶段禁用索引和外键加载完成后再重建。3.3 存储过程与视图的迁移要点存储过程是数据库迁移里最耗人工的部分没有捷径。先看一个简单的 SQLServer 存储过程再看对应的 Oracle 写法。-- SQLServer CREATE PROCEDURE sp_get_user_orders user_id INT AS BEGIN SET NOCOUNT ON; SELECT u.name, o.order_no, o.amount, o.created_at FROM users u INNER JOIN orders o ON u.id o.user_id WHERE u.id user_id ORDER BY o.created_at DESC; END;-- Oracle CREATE OR REPLACE PROCEDURE sp_get_user_orders ( p_user_id IN NUMBER, p_cursor OUT SYS_REFCURSOR ) IS BEGIN OPEN p_cursor FOR SELECT u.name, o.order_no, o.amount, o.created_at FROM users u INNER JOIN orders o ON u.id o.user_id WHERE u.id p_user_id ORDER BY o.created_at DESC; END sp_get_user_orders;核心差异有三点。一是变量命名方式SQLServer 用 前缀Oracle 用 p_ 前缀或者 v_ 前缀变量名不能和字段名重名否则会有歧义。二是返回结果集的方式SQLServer 里 SELECT 直接返回结果集Java 用 JDBC 的 executeQuery 就能拿到Oracle 存储过程没有直接返回结果集的概念得用 OUT 参数定义 REF CURSOR。三是异常处理SQLServer 用 TRY...CATCHOracle 用 EXCEPTION WHEN OTHERS THEN并且建议记录 SQLERRM。另外存储过程里如果用了临时表SQLServer 的 #temp 表在 Oracle 里要换成 GLOBAL TEMPORARY TABLE而且事务结束后数据可能被清空行为逻辑不完全一致需要逐个确认。接下来是视图。视图的迁移相对简单但要注意列的顺序和类型因为很多报表程序写死了列的序号。做视图迁移后直接用 SELECT * FROM view_name 和源库对比一下行数和列数基本能保证不出大问题。3.4 序列与自增字段迁移SQLServer 的 IDENTITY 自增字段在 Oracle 里用 SEQUENCE 实现。这个转换本身不复杂但有个容易被忽略的问题序列的初始值。假设 SQLServer 的 users 表 id 列当前最大值是 10086你在 Oracle 里建序列时如果从 1 开始插入新数据就会主键冲突。正确做法是创建序列时把 START WITH 设为源表的最大值加 1。可以用 Oracle 的动态 SQL 先查出最大值再生成序列。CREATE SEQUENCE seq_users_id START WITH 10087 INCREMENT BY 1 NOCACHE;另外如果源库里有多个表共用同一套 IDENTITY 规律序列要建多个不要图省事一个序列打天下。判断依据是看源表 ID 的增长规律一个自增字段建立一个序列是基本原则。4. 常见问题与排查技巧实录4.1 连接类错误速查表迁移过程中最常见的错误基本都是连接层面的做一张速查表方便你对照排查。错误信息可能原因排查步骤ORA-28547: connection to server failed, probable Oracle Net admin errortnsnames.ora 配置错误、SID/SERVICE_NAME 不匹配、防火墙拦截用 tnsping 测试核对 JDBC 连接串检查监听配置Oracle 监听服务无法启动listener.ora 写错、端口被占用、Oracle 服务异常查看 lsnrctl status用 netstat 检查 1521 端口重启服务与 SQLServer 建立连接时出现与网络相关的或特定于实例的错误防火墙拦截、实例名没写对、SQLServer 未启动远程连接检查 1433 端口、SQLServer 配置管理器里启用 TCP/IP、确认实例名SQLServer 服务启动不了错误码 17051安装信息缺失、系统账户权限不足、注册表损坏检查 SQL Server 配置管理器用安装介质修复看错误日志这里单独说一下 ora-28547。这个错误在 JDBC 直连 Oracle 时容易出现关键点在 URL 中的 SERVICE_NAME 和 tnsnames.ora 里的别名要保持一致。我遇到过一种情况监听正常运行tnsping 也通但程序就是连接不上最后发现是 JDBC URL 里写的是 service_name而 Oracle 数据库实际注册的是 SID两边对不上。解决办法是在 URL 里改成相同格式或者把 tnsnames.ora 里的 SERVICE_NAME 改成目标库实际的服务名。4.2 数据与 SQL 改写类问题身份证号科学计数法这个热搜词“oracle 数据库sql导出的身份证信息是科学计数法”其实是典型的 Excel 和数据库工具链问题。身份证号是 18 位数字在 Excel 里默认会被当成数值类型超过 15 位自动转为科学计数法最后几位变成 0。这个不是 Oracle 本身的问题是导出到 Excel 时格式设置不对。解决办法有三个一是导出时把身份证列设置为文本格式二是 SQL 查询时在身份证号前加一个制表符或者空格比如 SELECT id_card || CHAR(9) FROM table这样 Excel 会把它当文本处理三是在 SQL 里直接拼接一个不可见字符导入后替换掉。我自己用第二种方法最顺手Excel 打开后不会自动转换后续处理也不受影响。字符串转数字失败SQLServer 的 CONVERT 和 CAST 遇到字符串里有空格或特殊字符时容忍度比 Oracle 高。Oracle 的 TO_NUMBER 非常严格字符串里只要有一个非数字字符就报错。比如源表有个字段存的是 1,200SQLServer 里可以隐式转为 1200Oracle 里直接报 ORA-01722。解决办法是在迁移前写个检查脚本把所有字符字段里的非数字内容都查出来看看能不能清洗。如果确实需要保留原始字符串目标表就继续用 VARCHAR2查询时再做转换。删除重复数据只保留一条SQLServer 里没有 ID 字段的重复数据删除网上的教程通常给的是加 ROW_NUMBER() 窗口函数的写法。Oracle 里同样可以用 ROW_NUMBER() 实现但是注意单条 DELETE 语句里不能直接对窗口函数的结果进行删除需要先把它作为子查询。-- Oracle 删除重复数据只保留 rowid 最大的一条 DELETE FROM users u WHERE u.rowid NOT IN ( SELECT MAX(rowid) FROM users GROUP BY id_card, name );这种写法效率不一定是最高的但胜在逻辑清晰容易理解。大表建议先建临时表再去重比 DELETE 快得多。按逗号拆分成多行源库有字段存的是逗号分隔的多个值迁移到 Oracle 后要拆成多行。SQLServer 可以用 STRING_SPLIT 函数2016Oracle 里没有内置的字符串拆分函数得用 CONNECT BY 加正则实现。-- Oracle 按逗号拆分为多行 SELECT TRIM(REGEXP_SUBSTR(vals, [^,], 1, LEVEL)) AS val FROM ( SELECT a,b,c,d AS vals FROM DUAL ) CONNECT BY LEVEL LENGTH(vals) - LENGTH(REPLACE(vals, ,, )) 1;这个方法在字符串不太长的时候好用超过 4000 字符就会出现问题因为 VARCHAR2 上限是 4000。长文本拆分建议用 PL/SQL 或者改用 CLOB 相关的处理方式。插入数据重复则不插入SQLServer 里可以用 MERGE 或者 IF NOT EXISTS 先判断再插入。Oracle 里最优雅的写法是 MERGE INTO逻辑更清晰性能也更好。MERGE INTO target_table t USING (SELECT 100 AS id, 张三 AS name FROM DUAL) s ON (t.id s.id) WHEN NOT MATCHED THEN INSERT (id, name) VALUES (s.id, s.name);这里有个性能注意点ON 条件字段上必须有唯一索引或主键否则每次都要全表扫描匹配大表上这个操作会非常慢。4.3 批量迁移的稳定性问题大数据量迁移时最怕的不是语法错误而是跑到一半连接断掉或者内存溢出。我在迁移一张日志大表时遇到过 SQLLoader 直接卡死的情况。后来排查发现是 SQLLoader 默认的 BUFFER 太小几千万行的 CSV 文件解析不过来。调整方式是给 SQL*Loader 增加 ROWS 参数指定每次提交的行数减少内存占用。DBLINK 方式迁移时如果一次性执行 INSERT INTO target SELECT * FROM sourcedblinkOracle 会尝试把所有数据都读到 PGA 里再写入典型的内存杀手。正确做法是分批处理用 ROWNUM 限制每批数据量比如每批五万行。还有一个小技巧插入之前先用 ALTER TABLE 把表的日志属性设为 NOLOGGING插入完成后再改回 LOGGING能大幅减少 redo log 生成量实测提速非常明显。ALTER TABLE target_table NOLOGGING; -- 执行插入 ALTER TABLE target_table LOGGING;要注意NOLOGGING 模式下如果数据库在导入过程中发生崩溃这些数据可能无法恢复所以导入完成后立即做一次备份是必须的操作。4.4 SQLServer 自身的常见坑跳转到 Oracle 之前SQLServer 侧也有一些问题容易卡住流程。比如 SQLServer 配置管理器找不到、SQL Server 服务启动不了错误码 17051、Management Studio 打开报错等。错误码 17051 我遇到过一次通常的原因是安装时系统账户权限不足或者 SQL Server 安装程序的部分组件损坏。解决方法是先用 SQL Server 安装中心里的“修复”功能尝试修复如果修复不了把 SQLServer 服务登录身份改成本地系统账户再启动很多时候就能绕过去。这类问题虽然和 Oracle 没有直接关系但一旦碰上整个迁移进度就被卡住了提前准备一份备用的 SQLServer 环境是值得的。另外SQLServer 的 Errorlog 文件能不能直接删除这个问的人很多。Errorlog 文件是 SQL Server 的日志文件理论上你可以删除但强烈不建议手动删正确做法是执行 sp_cycle_errorlog 来循环日志。直接删文件可能导致服务句柄异常下次启动时可能报错。5. 迁移完成后的校验与切换数据迁移完不算完事校验和业务切换才是真正的考验。5.1 全量校验三板斧第一板斧是行数校验。每个表分别在源库和目标库执行 SELECT COUNT(*)对比结果。注意动态生成对比脚本一千张表逐个手工执行不现实。第二板斧是抽样字段校验。行数一致不代表数据一致。对关键业务表抽几列数据做精确比对。可以用 MINUS 集合操作做差集查询对两边的表分别查出主键和关键字段用 MINUS 找差异行效率高且逻辑清晰。-- 找出源库有、目标库没有的数据 SELECT id, name, amount FROM temp_users_source MINUS SELECT id, name, amount FROM temp_users_target;这里有个坑两个 SELECT 的列数和顺序必须完全一致数据类型不一致时 MINUS 可能会因为隐式转换而误判。建议先把两边结果都转成统一格式的子查询再做 MINUS。第三板斧是业务接口冒烟测试。让开发人员跑一遍典型的业务接口比如用户登录、订单查询、报表导出确认核心功能正常。这一环节能发现很多 SQL 细节问题比如大小写敏感、时区差异、排序规则不同导致的查询结果不一致。5.2 切换策略与回滚方案数据库迁移不是一个“选个晚上一迁了之”的事情切换方案必须提前写好并且要演练至少两遍。我常用的切换策略是先做全量迁移然后做增量同步。SQLServer 和 Oracle 之间如果 DBLINK 能通可以定期同步增量数据如果不行就采用“双写”方案业务系统在切换窗口内先写源库再通过同步程序转发到目标库。切换当天先停业务做最后一次全量校验确认差异在可接受范围内然后更新连接串把流量切到 Oracle最后持续观察一小时无异常才算切换成功。回滚方案同样重要。我习惯在切换前对 Oracle 做一次冷备份万一切换后发现严重问题可以快速回到 SQLServer 继续跑业务。回滚演练也要做不能只写在文档里。6. 最后说几句实操体会数据库迁移这个事做得多了就会明白真正的难点从来不是工具怎么用而是你对源库和目标库的理解有多深。SQLServer 和 Oracle 都是很成熟的数据库但各自的体系、哲学、隐藏行为差异很大很多问题表面上看是语法错误实际是思维方式没转过来。比如空字符串和 NULL 的区别ROWNUM 的分配时机NUMBER 类型的精度陷阱这些不是看一遍文档就能体会到的必须亲手踩过坑才有感觉。我个人的建议是迁移之前先把需求方、开发方、运维方都拉到一个会议室把业务范围、停机窗口、回滚条件和验收标准对齐。技术问题其实都能解决但人对需求的理解不一致会在后期不断返工。这事比任何数据库优化都重要。最后再分享一个很实用的小习惯每次遇到问题不管是 ORA- 错误还是 SQLServer 的报错我都把错误信息、发生场景和解决办法记录在一个本地文档里。迁移结束后整理出来的问题清单就是团队后续做其他数据库迁移时最好的培训材料。数据库迁移不可怕怕的是不总结。
返回列表