☰
SQL迁移完整实战指南:从摸底审计到数据校验
2026/10/20 22:25:57 网站建设 项目流程

1. 从“改个库”到“动手术”:SQL迁移到底在迁什么

先抛一个我最近刚做完的活儿:一套跑了五年的业务系统,底层数据库要从老牌的商业数据库迁到开源的PostgreSQL。单看这句话,很多同学会觉得“这不就是把建表语句改一改,数据导过去,改改连接串嘛”。可真上手你才会发现,SQL迁移根本不是什么“搬数据”,它是在动一台正在飞行的飞机的发动机。

所谓SQL迁移,我把它定义为:在不破坏业务连续性的前提下,把数据库的表结构、数据、函数、存储过程、触发器、视图、权限等一套完整资产,从一个数据库平台迁移到另一个平台,同时保证应用的功能行为和性能表现不退化。

它和我们常说的“数据同步”“数据复制”有本质区别。数据同步关心的是“数据到了没有”,而SQL迁移关心的是“整个系统在换底座之后还是不是原来那个系统”。举个例子,一套报表系统每天凌晨跑一个存储过程,里面用了Oracle特有的层级查询语法CONNECT BY,迁到MySQL之后这个语法根本不认,这不是“数据搬过去”就能解决的问题。

结合我自己接手过的几个项目,SQL迁移的高频触发场景大致是这么几类:

**第一类是数据库版本升级。**很多系统还在用旧版本的MySQL或PostgreSQL,要么是因为安全补丁不再更新,要么是想用上官方支持窗口内的新特性。这种迁移相对温和,因为底层平台没变,受影响的更多是SQL语法兼容性、优化器行为变化和系统参数默认值差异。

**第二类是跨数据库平台迁移。**从Oracle迁到PostgreSQL、从SQL Server迁到MySQL、从MySQL迁到TDSQL或者OceanBase,这种属于硬仗。不同的SQL方言、不同的类型系统、不同的锁机制、不同的事务隔离级别,每一项都可能让应用“看起来没问题,跑起来全是坑”。

**第三类是自建库迁到云数据库。**很多企业为了减少运维成本,把自建的MySQL实例迁到云厂商的RDS、PolarDB或其他托管服务上。这类迁移的难点不在语法兼容,而在参数体系、内核版本差异、高可用切换机制和账号权限体系的重新适配。

**第四类是分库分表或单元化改造。**逻辑上是同一个数据库MySQL,但物理结构变了——从单库拆成多个分片,或者从集中式表结构改成分布式表结构。这种场景下,大量SQL语句需要重写,事务边界需要重新设计,迁移的复杂度不亚于换平台。

这篇文章主要是写给正在准备做SQL迁移的开发者、DBA和应用架构师看的。如果你是第一次扛这种活,下面的内容能帮你把整个流程的骨架搭起来,知道每一步要干什么、为什么要这么干、最容易在哪儿翻车。如果你已经做过一两次迁移,那重点看第4节和第5节,那里讲的是我在实际项目中踩过的坑和排查手法。

2. 迁移前的“摸底审计”:宁可多花两周,不要事后加班两个月

我见过太多团队,项目启动第一天就急着搭同步工具,结果同步链路刚跑起来就发现源库有一堆奇葩对象没盘点清楚,只能停机回滚重新来。SQL迁移的第一步根本不是“动手”,而是把源库的家底彻底摸清楚。

2.1 盘点对象:不只是“表”和“数据”这么简单

很多初次接触迁移的人会拿着information_schema的表清单看一眼,觉得“就这些表了,开干吧”。其实至少还有这么几类东西一定要单独列出来:

  • 视图(View):特别是带WITH CHECK OPTION、带递归CTE的视图,在不同数据库里的默认行为有差异。
  • 触发器(Trigger):触发器的执行时机、OLD/NEW伪记录的取值方式、触发器内SQL的方言,全都是风险点。
  • 存储过程与函数(Procedure/Function):这是SQL迁移最大的雷区,尤其是Oracle和SQL Server的存储过程,一来逻辑量大,二来大量使用专有内置函数。
  • 序列/自增列(Sequence/Auto Increment):Oracle的Sequence和MySQL的Auto_Increment行为差异很大,迁移后自增ID的连续性、Cache机制完全不同。
  • 主键与唯一约束:不仅要迁移“是否存在”,还要注意索引的可见性、NULL值的唯一性差异。PostgreSQL的Unique索引允许NULL,但SQL Server默认不允许。
  • 外键约束和检查约束:不同数据库对级联删除的行为、约束校验的时机不一样。
  • 权限与账号体系:Oracle的Role、SQL Server的Schema、MySQL的Grant,完全不是一个概念模型,很多团队把这部分漏掉,上线后才发现应用账号连库都连不上,或者只能连库但啥也查不了。

我自己的习惯是:在第一周内完成一份《对象盘点Excel清单》,一列列下来——对象类型、对象名、行数估算、创建语句、是否含特殊语法、迁移优先级、风险等级。这份表就是你后续所有工作的作战地图。

2.2 类型映射:别让“长整型”这种小事翻车

数据库类型看起来大同小异,但细节差异能让你“死”得悄无声息。举个例子,Oracle的VARCHAR2(4000)在UTF8字符集下实际能存的最大字节数有限,迁到MySQL的VARCHAR(4000)后,不仅占用空间变了,索引的长度限制也可能直接触发“索引超过767字节”之类的报错。

一张常见类型映射参考表:

源库类型目标库类型注意事项
Oracle NUMBERPostgreSQL NUMERIC / BIGINT如果原列只存整数就映射成BIGINT,否则用NUMERIC
Oracle DATEPostgreSQL TIMESTAMPDATE不带时区,应用端显示会受影响
MySQL DATETIMEPostgreSQL TIMESTAMP注意时区处理,分别使用TIMESTAMP WITH TIME ZONE或WITHOUT
SQL Server NVARCHARPostgreSQL VARCHAR确认UTF8处理,避免乱码
MySQL TINYINT(1)PostgreSQL SMALLINT或BOOLEAN应用代码是否依赖0/1需确认
Oracle CLOBPostgreSQL TEXT注意最大长度、排序和索引限制

我的建议是:建一个《列级映射表》,对每张表的每个字段都过一遍类型,不要只改表级。尤其是DECIMAL的精度、CHAR和VARCHAR的尾部空格行为,以及BLOB/CLOB的读法,这些在数据比对阶段都会还给你的。

2.3 存量SQL审计:应用代码里的“隐藏地雷”

很多人以为SQL迁移只是DBA的事,直到上线前应用联调才发现,代码里有几十条Oracle方言SQL在跑。所以迁移评估阶段必须做一件事——对应用的SQL日志或代码库做扫描审计。

你不需要一开始就全线扫描全部代码,优先级是这么排的:

  1. 先扫Mapper、DAO层、实体框架里写死的原生SQL。
  2. 再扫存储过程中引用的所有内置函数和专有语法。
  3. 最后扫运维脚本和定时任务里的SQL片段。

常见的不兼容高风险SQL特征,我列几个高频案例:

  • Oracle的(+)外连接写法,迁到PostgreSQL必须改写成LEFT JOIN或RIGHT JOIN。
  • SQL Server的TOP (N)和GETDATE(),在PostgreSQL里要改写为LIMIT N和CURRENT_TIMESTAMP。
  • MySQL的GROUP BY非聚合列宽松行为,在PostgreSQL里如果没关ONLY_FULL_GROUP_BY类似机制就会直接报错。其实PostgreSQL默认更严格。
  • 分页查询依赖ROW_NUMBER() OVER()还是LIMIT/OFFSET,两个平台的写法差异很大。
  • 隐式类型转换:MySQL里字符串和数字比较能自动转,PostgreSQL很多情况直接报“operator does not exist”。

这个阶段最忌讳“到时候再说”。代码审计一定要在迁移动作之前完成,因为它的产出直接决定你的改造成本和工作量预算。

3. 方案选型与迁移设计:一次性全量还是增量双写,先想清楚

摸清家底之后,就要决定迁移的总体技术方案。SQL迁移从来不是“执行一个脚本把数据搬完”这么简单,你真正要设计的是整个切换过程如何做到影响小、可回滚、能验证。

3.1 三种主流迁移模式

根据停机窗口的大小和业务容忍度,我把方案分成三类:

模式一:停机迁移(Offline Migration)

步骤:停应用 → 做全量备份 → 导出源库数据 → 导入目标库 → 切换连接串 → 启应用。

优点:实现最简单,数据一致性问题最少。缺点:停机窗口长,业务不可用。

我经历过的案例里,有凌晨两点到六点给了4小时窗口的金融报表系统,也有愿意周末停机36小时的内部管理系统。停机迁移适合三类场景——系统数据量不大、可以接受较长停机、对一致性要求极高且没有专业同步工具支持。

模式二:全量+增量同步迁移(Online Migration)

步骤:先在源库做一次全量快照导入目标库 → 通过日志解析或时间戳方式把增量变更同步到目标库 → 在某个时间点做最终校验 → 切换读写流量到目标库。

优点:停机窗口可以压缩到分钟级甚至秒级。缺点:要引入同步工具,要考虑断点续传、延迟、冲突处理。

这个模式是现在上云和跨平台迁移的主流做法。常见的开源工具包括基于日志解析的Debezium、DataX加定时增量、以及各云厂商自带的DTS服务。

模式三:双写过渡迁移(Dual-write)

步骤:应用同时写源库和目标库 → 逐步灰度流量 → 确认无问题后摘除源库写入。

优点:回滚最平滑,风险最低。缺点:应用改造成本最大,两套库数据一致性校验压力大,维护成本高。

双写一般是在切换到关键业务库、停机窗口基本不存在的大型系统上才用。绝大多数项目没必要一上来就双写,那意味着应用层要做大量适配,成本可能比迁移本身还高。

3.2 到底要不要用工具,用哪些工具

SQL迁移的“手工作业”和“工具辅助”之间的度,需要根据项目量级和技术储备来判断。我的建议:凡是能用工具的地方,坚决用工具;凡是要做决策和判断的地方,坚决自己来。

常用的开源和商业工具我简单分类一下:

  • 结构迁移工具:官方自带工具最稳,比如MySQL的mysqldump、PostgreSQL的pg_dump/pg_restore、Oracle的expdp/impdp。跨平台时,可以使用开源的Schema转换工具,或者自己写转换脚本。
  • 数据迁移工具:单次搬迁用DataX这类批量同步框架最灵活;持续性增量用基于日志解析的Debezium配套Kafka通道最典型。
  • 校验比对工具:一定要有专门的数据比对方案,不能用“随机抽几条看看”土办法。

这里必须提醒一点:任何自动转换工具的输出都要人工review,特别是存储过程、触发器这类逻辑对象。自动转换工具擅长处理标准SQL,一遇到专用方言或复杂逻辑,经常生成“能跑但不是那么回事”的代码。

3.3 停机窗口内的时间线设计

我每次做迁移演练,都要画一张精确到分钟的时间线表,举个例子:

时间点动作负责人
T-60min开始源库最后全量备份DBA
T-45min停止应用写入,确认源库无活跃写事务应用负责人
T-40min导出增量日志,记录binlog位置或redo日志序号DBA
T-30min对目标库执行最终增量回放DBA
T-20min跑数据校验脚本,比对行数和关键字段校验和测试负责人
T-10min更新连接配置,切换读写流量应用负责人
T+0min启动应用,冒烟测试全员
T+30min观察核心链路日志、慢查询、报错DBA+应用

这张表的价值在于:把“迁移执行”变成机械动作,而不是临场发挥。每个时间点都要提前规划Assignee,每次正式操作之前至少要做一次完整演练。

4. 实操过程实录:从结构转换到数据搬迁的完整链路

这一节我按实际操作流程来讲,所有的命令和配置都以常见的 MySQL 到 PostgreSQL 迁移为例。虽然具体语法会有差异,但流程对所有SQL迁移都适用。

4.1 第一步:结构转换,先搞定DDL

在目标库上重建结构之前,一定要先想清楚几个全局参数,它们直接影响建表语句的内容:

  • 字符集:源库是utf8mb4,目标库如果默认是SQL_ASCII,那建表时要显式指定UTF8。
  • 排序规则:不同collation会影响字符串比较、索引和ORDER BY结果,建议建库时统一,不然后期改库级collation非常麻烦。
  • 表空间/文件组:PostgreSQL没有“文件组”概念,一个schema就是一个逻辑命名空间。
  • 大小写敏感策略:MySQL的表名在Linux下大小写敏感,PostgreSQL默认会把未加引号的标识符折叠成小写,应用代码里的SELECT * FROM User和实际的user表不一定能对上。

示例:从MySQL导出建表语句后,在PostgreSQL里对应的基础建表写法是:

-- 源库MySQL CREATE TABLE `user_profile` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `nick_name` VARCHAR(64) NOT NULL DEFAULT '', `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_nick_name` (`nick_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 目标库PostgreSQL CREATE TABLE user_profile ( id BIGSERIAL PRIMARY KEY, nick_name VARCHAR(64) NOT NULL DEFAULT '', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uk_nick_name UNIQUE (nick_name) );

看起来差不多,但细节差异立刻体现:MySQL的AUTO_INCREMENT变成了BIGSERIAL;DATETIME变成了TIMESTAMP;无所谓的ENGINE和CHARSET被丢掉;唯一约束命名从源库搬了过来,避免查询information_schema时对不上。

我强烈建议结构转换环节一定要有一个“DDL Review Checklist”,逐项核对:主键、自增、默认值、注释、字符集、排序规则、唯一约束、检查约束、外键、索引名长度(Oracle索引名最多30字符,PostgreSQL没这个限制)。

4.2 第二步:数据全量搬运,选对导出导入姿势

数据量在三五GB以内时,直接用官方工具最省事:

# 从MySQL导出SQL文件 mysqldump -h源库IP -u用户名 -p --single-transaction --set-gtid-purged=OFF \ --default-character-set=utf8mb4 --no-tablespaces 业务库 > dump.sql
# 导入PostgreSQL,推荐用psql执行 # 先连到目标库,注意先建好目标schema psql -h目标库IP -U用户名 -d 业务库 -f dump.sql

如果数据量到了几百GB甚至TB级,直接mysqldump加psql -f就会慢到怀疑人生。这时候要换成并行导入方案:

  • 平铺文件+COPY:把数据从MySQL查询出来,生成CSV或文本格式,再通过PostgreSQL的COPY命令高速导入。
  • 分片并行导入:把大表按主键范围切分成多个分片,多个导入进程同时跑,充分利用目标库CPU。

示例,把一张大表导出为CSV并导入PostgreSQL:

# 导出:MySQL端用SELECT ... INTO OUTFILE,或者用客户端工具查出来重定向 mysql -h源库IP -u用户名 -p -e "SELECT id, nick_name, created_at FROM user_profile" > user_profile.csv
-- 导入:PostgreSQL的COPY命令,比逐条INSERT快几个量级 COPY user_profile(id, nick_name, created_at) FROM '/路径/user_profile.csv' WITH (FORMAT csv, HEADER true, NULL 'NULL');

COPY的原理是走服务端直读文件,不走SQL解析和事务逐条提交,所以能在分钟级完成上千万行的导入。初次用COPY的人常常忘记设置NULL的表示形式,导致空值导入失败,这是很典型的低级坑。

4.3 第三步:增量同步,让目标库追上源库的最新状态

全量数据搬完后,源库可能又产生了新写入。如果停机窗口足够长,可以忽略这些增量;如果窗口很短,就必须有一套增量追赶机制。

比较通用的方案是:

  1. 在全量导出的开始时刻,记录源库的日志位点(MySQL的binlog file+position,PostgreSQL的lsn,Oracle的SCN)。
  2. 全量导入完成后,启动一个基于日志的增量采集器,从这个位点开始解析并回放到目标库。
  3. 观察目标库和源库的延迟指标,目标库追平后进入切换窗口。

举个简单的Debezium配置思路(注意这里是MySQL作为源、PostgreSQL作为目标):

{ "name": "mysql-connector", "config": { "connector.class": "io.debezium.connector.mysql.MySqlConnector", "database.hostname": "源库IP", "database.port": "3306", "database.user": "repl_user", "database.password": "密码", "database.server.name": "my-app", "database.include.list": "业务库", "table.include.list": "业务库.user_profile", "database.history.kafka.bootstrap.servers": "kafka:9092", "database.history.kafka.topic": "schema-changes", "include.schema.changes": "true" } }

增量同步阶段的坑主要在这么几个地方:

  • DDL变更:源库执行ALTER TABLE后,采集器的schema变更事件没有同步到目标端,导致后续DML插入字段对不上。建议增量过程中锁死DDL,或者人工同步所有DDL。
  • 无主键表:日志采集器通常依赖主键定位变更行,无主键表要么在迁移前补主键,要么接受增量不同步风险。
  • 大事务:一个事务里更新了几百万行,采集器可能要解析很久才能commit,导致延迟突然飙升。

4.4 第四步:对象迁移,别把逻辑留在旧家

数据和表结构都过去了,但很多系统真正的“业务逻辑”还在存储过程里。跨平台迁移时,存储过程基本都要重写。

以Oracle迁到PostgreSQL为例,一个经典案例是Oracle的PIPELINED函数(管道函数),它在PostgreSQL里没有对等概念,通常要改成RETURNS SETOF函数或改写成视图。又比如Oracle的自治事务PRAGMA AUTONOMOUS_TRANSACTION,PostgreSQL要用dblink或新版本的部分特性模拟,否则整个事务提交/回滚边界就变了。

触发器也一样。MySQL的触发器只能触发行级FOR EACH ROW,PostgreSQL支持语句级FOR EACH STATEMENT。如果你原来写的是行级触发器,迁过去后要注意性能放大的问题——一次UPDATE 100万行的语句,在MySQL触发器里是逐行执行,如果等价迁移到行级触发器,效果相同;但如果你本意用语句级触发器实现批量操作,效果完全不同。

迁移对象逻辑时,我强烈建议做一个逻辑等价对比表,把源库每个存储过程/函数/触发器的输入参数、输出行为、边界条件、异常处理全部列出来,然后逐条编写目标库版本,再拿同一组测试数据在两个库上分别执行,比对结果集。

4.5 第五步:应用侧改造,连接串只是最外层

应用侧的改动不止是改数据库连接串。我曾经遇到过一个案例:应用在MySQL里通过SELECT LAST_INSERT_ID()拿自增主键,业务逻辑里到处在用,迁到PostgreSQL后同样的调用返回永远为0,因为PostgreSQL的等价物是RETURNING id,必须在INSERT语句里指定。

类似这种隐藏方言,我列一个常见的应用侧兼容问题清单:

  • 分页SQL中的LIMIT ? , ?参数顺序在不同驱动中的差异。
  • 布尔值返回:MySQL的TINYINT(1)在JDBC驱动里可能返回true/false,但PostgreSQL的BOOLEAN类型在不同驱动中的getter方法可能不同。
  • 日期格式化函数:DATE_FORMAT()、STR_TO_DATE()是MySQL专属,PostgreSQL要用TO_CHAR()和TO_DATE()。
  • 批处理提交:某些驱动默认rewriteBatchedStatements=true与否,直接决定批量插入的性能差一个数量级。

应用改造阶段必须让研发同学尽早介入,而不是等DBA把数据迁完之后才告诉他们“你们的SQL要改”。最理想的做法是:在迁移评估阶段,就把审计出来的不兼容SQL清单发给研发,让他们提前改造,然后在演练环境里联调测试。

5. 数据校验与切换演练:验证不到位,一切等于零

数据迁移完成不代表迁移成功。我见过太多项目,数据同步延迟归零了就觉得大功告成,结果一查业务数据对不上,白干。

5.1 怎么“证明”数据是一致的

真正靠谱的数据校验分三个层次:

**第一层:行数校验。**每个表都做SELECT COUNT(*)对比。这个校验最简单,但注意不同数据库的COUNT在大表上可能很慢,所以往往是抽样或依赖统计信息来估算,或者并行跑。

**第二层:关键字段的聚合指纹。**对每张表的关键字段做CHECKSUM/CRC32之类的聚合,比如SELECT COUNT(*), SUM(CRC32(CONCAT(id, nick_name))) FROM user_profile,分别算源和目标,对比是否一致。这种方式能抓住“行数一致但内容不一样”的问题。

**第三层:抽样明细比对。**按主键随机抽几百行,逐字段比对。对时间敏感字段,要注意时区换算后的值是否一致;对DECIMAL字段,要注意精度是否被隐形四舍五入。

这里分享一个我自己的自动化比对思路:把两张表各生成一行聚合签名,然后做全表哈希比对。虽然极端情况下会有哈希冲突,但在迁移验证这个场景已经足够,效率远高于逐行比对。

5.2 切换演练:你甚至要演练“回滚脚本”

每次迁移正式操作之前,至少要做一次完整的“彩排”。彩排不是走流程,而是真实地把应用停掉、数据迁一遍、应用指到新库、跑一轮冒烟测试,然后再切回旧库。

彩排的价值有两点:第一,暴露所有你没预料到的问题;第二,给你的回滚方案一次真实检验。

回滚方案不是一句“我们切回来就行了”,而是要能回答这些问题:

  • 切换之后,增量写入是只进了新库,还是新旧都被写?
  • 如果应用已经在新库上跑了2小时,这2小时的业务数据要不要回流到旧库?
  • 新库的AUTO_INCREMENT序列和旧库的当前值是否对齐?回滚后会不会ID冲突?
  • 账号权限、监控告警、定时任务、报表抽取等等外围系统,是否都跟着切了?

只要有一个问题答不上来,你的回滚方案就是空白。

5.3 灰度切换:先放大流量,不要瞬间全量

如果业务允许,建议流量灰度切换。比如先切5%的读流量到新库,观察应用日志、慢查询、报错,再逐步放大到50%,最后再切写流量。

写流量切换是整个迁移过程中最敏感的动作。写流量一过去,意味着“脏数据”开始在新库落地,这时候回滚的压力就大了。我有一个习惯:写流量切换前,强制做一次源库到目标库的增量延迟归零验证;写流量切换后15分钟内,密切盯住数据库的活跃会话数、锁等待、慢SQL、以及应用的错误率指标。

6. 常见问题与排查技巧实录

这一节列一下我这些年做SQL迁移时最常撞上的问题,以及对应的排查手法。有些问题小到让你抓狂,大到让你通宵回滚。

6.1 数据对不上:表行数一致但业务说缺数据

典型场景:迁移完行了,行数校验也过了,但业务同学说历史某个月的数据查不到。

排查思路:

  • 先看是不是时区问题。源库存的是CST时间,目标库应用连接用了UTC,查询时一转换就错位。
  • 再看是不是字符集处理。源库是utf8mb4,目标库是UTF8,某些特殊Emoji字符在导入时被静默替换成问号,虽然行数没变但内容错了。
  • 然后看分区表。Oracle的分区表导出时如果没带分区定义,目标库可能建成了普通表,数据全在一个大表里或部分遗漏。
  • 最后查触发器导致的隐式变更。源库有触发器在INSERT时改写字段,而目标库触发器没迁移或迁移不完全,导致同一行数据内容不同。

排查手法建议:拿出一个具体账号或业务ID,从源库查一行,再从目标库查同一主键的那一行,逐字段肉眼比对。这是最土但最有效的方法。

6.2 迁移后SQL变慢:同样的数据,换个数据库像换了台机器

这是SQL迁移中最让人头疼的问题。逻辑等价但性能差异巨大。

原因通常在几个方面:

  • 统计信息没更新或没导入。迁移后如果没跑ANALYZE,优化器拿不到数据分布信息,可能选了极差的执行计划。
  • 缺少直方图/统计柱状图。MySQL迁移到PostgreSQL时,源库若开了innodb_stats_persistent并维护了统计信息,目标库也要用ANALYZE建立直方图。
  • 索引类型不同。MySQL的BTREE索引在PostgreSQL里更常用btree,但对文本前缀查询、JSON字段查询,如果目标库少了GIN、BRIN等合适的索引类型,查询计划会完全跑偏。
  • JOIN顺序和HINT失效。很多Oracle迁移案例里,原SQL带了/*+ LEADING */或/*+ USE_HASH */的HINT,PostgreSQL不支持这些HINT,优化器选的计划可能和原来的天差地别。

排查建议:第一时间抓慢查询日志,把慢SQL拿去做EXPLAIN ANALYZE,对比源和目标执行计划里的扫描方式、JOIN方式、预估行数和实际行数。不要急着调内核参数,大多是执行计划的问题。

6.3 自增ID冲突:生产上一跑直接撞主键

源库的序列当前值没迁到目标库,或者迁移时用了不合适的初始值,生产一插入就撞主键。

排查方式:

  • 迁移前就要记录源库每个自增列的当前值。
  • 导入完成后,手动设置目标库序列的当前值:
-- 例如源库id当前最大值是15320,PostgreSQL设定下一个值 SELECT setval('user_profile_id_seq', 15320, true);

这里true表示下一个nextval从当前值+1继续,false表示从指定值重新开始。很多同学踩坑是因为忘记第三个参数。

6.4 写入冲突和锁等待暴增

这个现象在“双写期间”特别常见。两套库的行为差异会导致应用侧事务的行为变化。比如MySQL默认的事务隔离级别是REPEATABLE READ,PostgreSQL默认也是REPEATABLE READ,但真实锁行为不一样。PostgreSQL的更新冲突会直接抛could not serialize access due to concurrent update错误,而MySQL在同一隔离级别下可能只是等锁后继续。

如果碰到这类报错,优先看应用是否开启了重试机制。如果应用没有对这类异常做重试处理,就需要在迁移期间临时修改隔离级别,或者给应用层加重试逻辑。

6.5 字符集乱码:迁完像天书

字符集问题多发于导出导入时没有统一编码。命令行下mysqldump默认可能不是UTF8,pg_restore也可能因为客户端编码不一致把事情搞砸。

规避策略非常简单但很多人不重视:在导出和导入两侧都显式指定UTF8,在目标库连接时先执行SET NAMES 'UTF8'或者初始化连接参数。校验阶段再抽几条含中文、Emoji、生僻字的数据逐条比对。

6.6 外键顺序导入失败

全量导入时,如果先导子表、后导父表,或者导入过程中临时关掉了约束又忘了开回来,就会报外键冲突或校验失败。

实操建议:导入数据时先禁用外键约束(在PostgreSQL里可以SET session_replication_role = replica;),导入完成后再开启约束并执行一次全库约束校验。注意这个操作是会话级的,用完一定要改回来,不然后续DML都不会触发外键检查,这个问题在排查时极难发现。

7. 最后再聊几句经验体会

SQL迁移做得多了,我最大的感受是:迁移这事儿,真正复杂的不在“迁移”本身,而在于你对业务系统的理解深度。

工具能帮你搬结构、搬数据、同步增量,但没有任何工具能替你回答“这个存储过程为什么一定要这么写”“这个表的排序规则变了会影响哪个报表”“为什么生产上有个凌晨3点的任务依赖的是旧数据库的时区设置”。这些问题的答案只存在于熟悉业务的人和代码仓库里。

所以我特别建议团队在启动迁移项目时,就让应用研发、DBA、测试三方坐在一起,先花两周把“对象清单”和“SQL兼容性审计报告”拉出来,再谈后面的工具选型和方案设计。很多团队跳过这个阶段直接上工具,看起来省了时间,最后都在上线前加班还债。

如果你正准备做一次SQL迁移,我的建议是:先把本篇的第2节那一套摸底审计完整过一遍,把所有风险点都列出来,再决定用哪种迁移模式;正式操作前无论如何要做一轮完整的演练,演练中发现的所有问题都必须登记、修复、复测通过后才能上生产。这不是流程主义,而是我用很多个不眠之夜换来的教训。

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

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

立即咨询