news 2026/10/17 5:36:04

SQL Server迁移MySQL实战:结构转换、存储过程改写与性能调优指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SQL Server迁移MySQL实战:结构转换、存储过程改写与性能调优指南

1. 迁移前必须先想清楚的几件事

在拿到"SQL Server 迁移到 MySQL"这个任务时,先别急着开工具导数据。我见过太多人第一步就栽在评估上——误以为两边都是关系型数据库,表结构导过去、数据灌进去就算完事,结果上线当晚存储过程全部报错、自增主键错乱、中文乱码一片,最后通宵救火。所以要先把整个流程拆成几个阶段:评估盘点、环境准备、结构迁移、数据迁移、对象改写、验证回滚。每个阶段都有独立的验收标准,前一个没过关就别往后走。

先说你到底为什么要迁。常见的理由有三类:一是成本,SQL Server 的许可费在中小公司里确实是笔不小的开销,而 MySQL 开源免费;二是生态,业务上要接一些开源组件或云数据库服务,而它们对 MySQL 的兼容性更好;三是团队技能,招人时懂 MySQL 的候选人明显比懂 SQL Server 的多。但这三类理由对应的迁移难度完全不同。如果只是报表库,那迁移成本很低,大部分工作就是改连接串;如果业务核心库有几百个存储过程、几十个作业和一堆 SSIS 包,那这个项目就是个小型重构工程,得按项目制来排期。

在开始之前,最好先对源库做个摸底。用下面这几条 SQL 快速统计一下对象数量,能让你心里大概有个底:

-- 统计表和行数规模 SELECT t.name, p.rows FROM sys.tables t JOIN sys.partitions p ON t.object_id = p.object_id WHERE p.index_id IN (0,1) ORDER BY p.rows DESC; -- 统计存储过程、视图、函数数量 SELECT type, COUNT(*) AS cnt FROM sys.objects WHERE type IN ('P','V','FN','TF','IF') GROUP BY type; -- 统计索引数量 SELECT COUNT(*) FROM sys.indexes WHERE type > 0;

这些数字直接决定你要投入多少人力。我做过一个真实项目,源库有 300 多张表,存储过程 500 多个,迁移之前我按对象数量估了三周,实际干了一个半月——因为那些存储过程里用到的语法差异,远比文档里写的要复杂。所以第一课:永远给评估阶段留足时间,别拿"都是关系型数据库"当挡箭牌。

2. 数据库对象迁移:表结构、数据类型与索引的差异处理

确定要迁移之后,第一个硬骨头就是表结构。这里最容易踩的坑是数据类型映射。SQL Server 和 MySQL 虽然都是关系型数据库,但数据类型体系差异相当大,直接用默认映射工具处理,往往会留下后患。

2.1 数据类型映射规则

先给你一份我实际项目中沉淀下来的映射表,照着用基本不会出大问题:

SQL Server 类型MySQL 类型备注
INTINT直接对应
BIGINTBIGINT直接对应
SMALLINTSMALLINT直接对应
TINYINTTINYINTSQL Server 的 TINYINT 是 0-255,MySQL 也是 0-255,没问题
BITTINYINT(1)千万别用 BIT,MySQL 的 BIT 是位类型,行为不同
DECIMAL(p,s)DECIMAL(p,s)精度标度一致,直接搬
FLOATDOUBLESQL Server 的 FLOAT 默认 53 位,对应 MySQL 的 DOUBLE
DATETIMEDATETIMESQL Server 的 DATETIME 精度约 3.33ms,MySQL 的 DATETIME 精确到秒,数据本身没差别
DATETIME2DATETIME(3) / DATETIME(6)按实际需要的精度来,DATETIME2(3) 对应 DATETIME(3)
SMALLDATETIMEDATETIMESMALLDATETIME 只到分钟,DATETIME 精度更高,不丢数据
NVARCHAR(n)VARCHAR(n) CHARACTER SET utf8mb4关键点:SQL Server 的 NVARCHAR 是 UTF-16,MySQL 的 VARCHAR 在 utf8mb4 下是变长 UTF-8,n 的含义完全不同,要特别注意长度换算
VARCHAR(n)VARCHAR(n)单字节字符集下问题不大
NTEXTTEXT / MEDIUMTEXT根据最大长度选
TEXTTEXT / MEDIUMTEXT同理
IMAGELONGBLOB二进制大对象
UNIQUEIDENTIFIERCHAR(36)MySQL 8 支持原生 UUID 类型,但兼容性最好还是用 CHAR(36)
MONEYDECIMAL(19,4)MONEY 是定点数,别用 FLOAT 替代
XMLTEXT / LONGTEXTMySQL 没有原生的 XML 类型,只能存文本
GEOGRAPHYGEOMETRY(4326)空间数据,MySQL 8 支持,但函数体系完全不同
ROWVERSIONBINARY(8)用于乐观并发,迁移后要改造逻辑
SQL_VARIANT无对应必须手动拆分或改设计

这里最容易翻车的是 NVARCHAR。SQL Server 里 NVARCHAR(100) 表示能存 100 个 Unicode 字符,到了 MySQL 里 VARCHAR(100) 在 utf8mb4 下也是 100 个字符,这个倒没变。但要命的是 MySQL 的 VARCHAR 长度有个 65535 字节的行限制,utf8mb4 下一个字符最多占 4 字节,所以 VARCHAR(16384) 在 SQL Server 里没问题,搬到 MySQL 就超限了。处理办法是:把超长字符串列改成 TEXT 类型(TEXT 不走行内字节限制),或者缩小定义长度。注意,TEXT 类型在 MySQL 里不能有默认值,这个问题虽然在 MySQL 8.0.13 之后得到部分解决(允许默认值为表达式),但仍有坑——如果你的应用在插入时不显式给值,就会拿到 NULL 而不是期望的空字符串。

还有一个 MONEY 类型,SQL Server 的 MONEY 是十进制定点数,精度到万分之一。有人图省事直接映射成 DOUBLE,金额这种东西用浮点存,迟早要出事。老老实实映射成 DECIMAL(19,4)。如果业务上对金额精度有更高要求,改成 DECIMAL(20,6) 也行,但要同步改应用层的读取逻辑。

2.2 主键、自增列与索引迁移

SQL Server 的自增列(IDENTITY)对应 MySQL 的 AUTO_INCREMENT。结构迁移时两张表的主键定义看起来差不多,但有几个行为差异你必须知道:

SQL Server 的 IDENTITY 列插入显式值后,种子会自动跳到该值 + 1。MySQL 的 AUTO_INCREMENT 同样如此。但两者有个关键区别:SQL Server 支持SET IDENTITY_INSERT [table] ON来强制插入,MySQL 只需要在 INSERT 语句里显式列出自增列名即可。迁移数据时这是必须的操作,否则历史主键值对不上,后续应用逻辑全乱。

索引方面,SQL Server 的聚集索引和 MySQL 的 InnoDB 主键概念有对应关系,但非聚集索引的处理要小心。SQL Server 允许在视图上建索引(索引视图),MySQL 8 虽然支持功能索引和表达式索引,但视图片本身不支持索引。如果你源库里有索引视图,迁移后这些索引全部失效,得评估对查询性能的影响。

还有全文索引。SQL Server 的全文索引和 MySQL 的 FULLTEXT 索引在分词器、语法(CONTAINS vs MATCH...AGAINST)上完全不同。如果有全文检索需求,建议在迁移后考虑用专业的全文检索引擎(比如开源界常用的 Elasticsearch),而不是硬把 SQL Server 的全文索引搬到 MySQL 上。

3. SQL 语法差异与存储过程改写

结构迁完后,真正耗时间的是 SQL 语法层面的改写。T-SQL 和 MySQL 的 SQL 语法虽然同源,但差异点多到能单独写一本书。这里我把最常遇到的挑出来,每个都配上改写前后的对比,方便你直接抄作业。

3.1 基础语法差异对照表

功能SQL Server (T-SQL)MySQL说明
限制返回行数SELECT TOP 10 ...SELECT ... LIMIT 10最常用的差异,OGG/DataX 等工具不帮你改,得自己来
字符串拼接SELECT 'a' + 'b'SELECT CONCAT('a','b')T-SQL 里 + 也能拼,MySQL 里 + 是数字运算
获取当前时间GETDATE()NOW()/CURRENT_TIMESTAMP几乎每个存储过程都会用到
日期格式化CONVERT(VARCHAR, GETDATE(), 120)DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s')完全不同的语法体系
字符串长度LEN('abc')CHAR_LENGTH('abc')LEN 不含尾部空格,LENGTH 在 MySQL 里是字节数
子串截取SUBSTRING('abc', 1, 2)SUBSTRING('abc', 1, 2)这个居然一致,用起来最舒服
类型转换CAST(x AS INT)CAST(x AS SIGNED)MySQL 没有 INT 作为转换目标,必须用 SIGNED
判空函数ISNULL(x, 0)IFNULL(x, 0)拼写只差一个字母,但意思一样
NULLIFNULLIF(a, b)NULLIF(a, b)这个一致
逻辑判断IF ... ELSEIF ... ELSEIF ... ELSE注意是 ELSEIF 而不是 ELSE IF
分页OFFSET ... FETCHLIMIT offset, count写法区别明显
布尔值WHERE flag = 1WHERE flag = 1SQL Server 的 BIT 映射成 TINYINT(1) 后,查询条件不用变
取模a % bMOD(a, b)或a % b都支持,写法随意
字符串查找CHARINDEX('a', 'abc')LOCATE('a', 'abc')/INSTR('abc', 'a')参数顺序不同,极易搞反
去空格LTRIM(RTRIM(x))TRIM(x)MySQL 的 TRIM 默认去两端空格
时间加减DATEADD(DAY, 1, GETDATE())DATE_ADD(NOW(), INTERVAL 1 DAY)完全不同
求日期差DATEDIFF(DAY, d1, d2)DATEDIFF(d1, d2)参数顺序完全相反,大坑

DATEDIFF这个参数顺序问题是典型的隐蔽错误。SQL Server 的写法是DATEDIFF(单位, 开始, 结束),MySQL 的写法是DATEDIFF(结束, 开始),而且单位固定是"天"。迁移后你不会立刻看到报错,只会莫名其妙地发现日期差算成了负数或者差了一整天。这类问题靠跑一遍批量测试能抓出来,但更靠谱的是在迁移前写一个对比测试脚本,把每条 SQL 在两边各跑一遍,结果不一致的标记出来。

3.2 存储过程改写实战

存储过程的改写是整场迁移的核心战役。SQL Server 存储过程的功能密度相当高,里面充斥着批处理、临时表、游标、动态 SQL、错误处理。逐条改写工作量极大,我给你一套高效改写的步骤:

第一步,拿到源库的存储过程代码后,先做静态扫描。用正则搜索这些高频模式:WITH、CTE、TOP、GETDATE()、ISNULL、STRING_AGG、PIVOT、TRY...CATCH、@@ERROR等。特征匹配到的直接按对照表批量替换,这一步能解决 60% 的机械性改动。

第二步,处理临时表语义差异。SQL Server 里#temp是会话级临时表,存储过程结束时自动清理。MySQL 5.7 之前不支持会话级临时表在存储过程里反复创建(存储过程编译时临时表必须存在),MySQL 8.0 之后也仍然不建议依赖这个行为。最常见的改写方式是换成内存临时表,但要注意 MySQL 临时表的创建语句会被记录到 binlog,在复制环境下有主从数据不一致的风险。更加稳妥的做法是:把临时表改成语义等价的派生表(FROM 子查询)或者 CTE,虽然可读性差点,但彻底绕开了临时表生命周期的问题。如果业务逻辑确实离不开临时表(比如要建索引、多次引用),那 MySQL 里就用CREATE TEMPORARY TABLE,但每次调用存储过程时先DROP TEMPORARY TABLE IF EXISTS,避免残留数据。

有一个经验数据:如果源库存储过程里有超过 3 个临时表,改写成 CTE 的难度会急剧上升,因为 CTE 的作用域只在单个语句内。这时候我的建议是直接在 MySQL 存储过程里用临时表,但在开头加个清理动作。实测下来这种方案性能不差,而且改动量最小。

第三步,事务与错误处理。SQL Server 的TRY...CATCH结构对应 MySQL 的DECLARE ... HANDLER或者SIGNAL,但两者错误传播机制完全不同。MySQL 的默认行为是存过过程运行时遇到错误就中止,这点对应用层可能反而是好的(语义更简单),但如果你的存储过程里有"捕获错误、记录日志、继续执行"的模式,就必须用CONTINUE HANDLER。改写时注意,MySQL 存储过程里没有RAISERROR,新版用SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '...'来主动抛出业务错误。

第四步,游标处理。SQL Server 的游标语法和 MySQL 类似,但有个区别:MySQL 的游标必须在声明 handler 之前声明,否则会出现"declare handler order"的报错。实际改写经验:能不用游标就尽量不用游标,MySQL 8 支持窗口函数,很多在 SQL Server 里必须用游标做的行级操作,比如累加求和、取前 N 行、相邻行比较,现在都能用窗口函数一行搞定,性能提升十倍不止。

分享一个真实案例。某财务系统的存储过程,从 SQL Server 迁到 MySQL 后性能不升反降。排查后发现是游标嵌套:外层游标 200 行,内层对每行做一次子查询,导致执行次数暴涨到几十万次。改写方案是把嵌套逻辑改成一条 JOIN 加窗口函数,执行时间从 47 秒降到 1.2 秒。所以存储过程改写不只是翻译语法,更是一次重构的机会,任何能用集合运算解决的问题,都不要用逐行迭代去解决。

4. 数据迁移方案与工具选型

结构搞完,终于到了灌数据这一步。数据迁移方案的选择取决于数据量、停机窗口和可用工具。我按数据量给你三档方案,各自适配不同场景。

4.1 十万行以下:手工导入

表少、数据量小的情况下,直接基于 SSMS 右键导出数据,或者用bcp工具导出文本文件,再在 MySQL 侧用LOAD DATA INFILE导入。LOAD DATA INFILE是 MySQL 最快的导入方式,比逐条 INSERT 快几十倍。需要留意的是:

  • 导出时统一使用 UTF-8 编码;
  • 字段分隔符别用逗号,数据里可能有逗号,建议用竖线|或制表符;
  • 文本限定符要选好,不然数据里的引号会把行结构打崩;
  • 控制文件把空字符串和 NULL 的区别处理清楚。
-- SQL Server 侧导出 bcp "SELECT col1, col2, col3 FROM db.dbo.tbl" queryout data.csv -S server -U user -P pass -c -t "|" -r "\n" -- MySQL 侧导入 LOAD DATA INFILE '/path/to/data.csv' INTO TABLE tbl FIELDS TERMINATED BY '|' ENCLOSED BY '"' LINES TERMINATED BY '\n' (col1, col2, col3);

注意LOAD DATA INFILE在 MySQL 8 默认对文件路径有secure_file_priv限制,文件必须放在指定目录下,否则报The MySQL server is running with the --secure-file-priv option。这是被问得最多的问题之一。解决办法是把文件放到secure_file_priv指向的目录,或者用LOAD DATA LOCAL INFILE(允许客户端本地文件),但后者需要客户端和服务端都开启local_infile选项,云数据库实例通常默认关闭。

4.2 百万到千万行:DataX / Kettle

数据量中等的情况下,手工导出就不好使了,原因有两个:一是 bcp 导出的文件太大,传输和导入时间不可控;二是停写窗口可能只有几小时,手工操作容易超时。这时候上 ETL 工具比较合适。

我比较推荐阿里开源的数据同步工具 DataX,原因在于它的插件机制非常成熟,同时支持 SQL Server Reader 和 MySQL Writer,对数据类型的处理经过了大量生产环境的验证。写个 job.json 配置文件就能跑起来,逻辑简单,也适合在迁移过程中反复执行(比如先试迁,再全量)。

DataX 配置示例:

{ "job": { "content": [ { "reader": { "name": "sqlserverreader", "parameter": { "username": "sa", "password": "***", "connection": [ { "table": ["dbo.tbl"], "jdbcUrl": ["jdbc:sqlserver://host:1433;DatabaseName=db"] } ], "column": ["col1", "col2", "col3"] } }, "writer": { "name": "mysqlwriter", "parameter": { "username": "root", "password": "***", "connection": [ { "table": ["tbl"], "jdbcUrl": ["jdbc:mysql://host:3306/db"] } ], "column": ["col1", "col2", "col3"], "preSql": ["DELETE FROM tbl"], "batchSize": 2048 } } } ], "setting": { "speed": { "channel": 8 } } } }

有几个参数值得展开说:

  • channel是并发通道数,决定读取和写入的并发度。不要一上来就拉满,先在测试环境上跑一次,观察 SQL Server 的 CPU 压力和目标 MySQL 的写入吞吐。我通常先从 4 起步,最高加到 16,再高反而会遇到数据库自身的锁竞争。
  • batchSize是每次事务提交的行数。太大容易导致事务日志膨胀,太小则网络往返太频繁。2048 是我比较常用的值,你可以根据字段宽度调整:字段少的可以加大到 5000,字段多的大字段(有 TEXT/BLOB)就得降下来。
  • preSql里的 DELETE 是用来做幂等重跑的。迁移过程难免要试跑几次,没有这个配置,第二次跑的时候就全是主键冲突了。

4.3 千万行以上:分片迁移 + 增量同步

数据量上到千万甚至亿级,一次性全量同步就不现实了,因为全量搬运期间源库还在持续写入,搬完的那一刻数据已经不一致。这种场景标准的做法是"全量 + 增量"两步走:

第一步,在全量迁移开始前,记录一个一致性位点(可以简单记为某个时间点或某个大表的主键边界)。然后跑全量同步工具(DataX 或其它)。

第二步,全量跑完后,开启增量同步。增量同步通常依赖解析 SQL Server 的事务日志来捕获增量变更。这里我先给你交个底:SQL Server 和 MySQL 之间没有官方的实时同步工具,像 Oracle 的 GoldenGate 那样开箱即用的东西,在 SQL Server→MySQL 方向上不是免费的。实际项目中常见的解决路径是:在应用层做双写(改代码同时写 SQL Server 和 MySQL),或者基于日志解析工具(比如 Debezium 对 SQL Server CDC 的适配)做变更捕获,再通过消息队列写入 MySQL。

双写看上最省事,但它要求应用层把两个库的写入都当成事务来处理,一旦 MySQL 写入失败还容易导致业务整体回滚,对高可用来说是个不小的隐患。而配 Debezium 要定期清理日志读取位点,设计不好也会引发 SQL Server 事务日志无限膨胀。所以在项目启动阶段就必须明确增量同步实施方案,并且预留出至少两倍的调研和排错周期,否则大概率会拖到上线当天还在调同步延迟。

全量迁移过程中,几个容易忽略的点:

  • 临时关闭 MySQL 侧的外键约束检查。导入数据时如果子表先导、父表后导,会直接违反外键。最稳妥的办法是在导入前执行SET FOREIGN_KEY_CHECKS=0,导完再恢复。
  • 关闭唯一性检查(SET UNIQUE_CHECKS=0),数据先导进来再说。
  • InnoDB 引擎导入期间调大bulk_insert_buffer_size和innodb_buffer_pool_size,数据库配置好点的机器能明显提升导入速度。

5. 迁移后的应用适配与性能调优

数据搬过去了,结构也建好了,很多人以为这就完事了。实际上迁移后真正的战场是应用适配和性能调优。

5.1 连接层与应用代码适配

首先是连接串。SQL Server 的 JDBC 连接串长这样:

jdbc:sqlserver://host:1433;databaseName=mydb;user=sa;password=pass;

MySQL 的 JDBC 连接串长这样:

jdbc:mysql://host:3306/mydb?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai

注意几个参数:useSSL=false在 MySQL 8 里如果不开 SSL 必须显式标明,有些驱动版本不写会警告甚至报错;serverTimezone必须设,否则连接时报The server time zone value 'XXX' is unrecognized;高版本 MySQL 驱动要求useCursorFetch等参数的配置要合理,否则大数据量查询结果集默认全量加载到内存。

ORM 框架层面,Hibernate/MyBatis 都同时支持两种数据库,但方言(Dialect)配置不同。Hibernate 要改成org.hibernate.dialect.MySQL8Dialect,分页查询的自动生成语句从OFFSET变成LIMIT,其实反而是省事了。但如果项目里有人写了原生 SQL(比如在 Mapper XML 里写<select>的 SQL Server 特有语法),这些只能逐个改。

5.2 排序规则与大小写敏感

这块是迁移后最容易触发线上问题的地方。SQL Server 的数据库排序规则默认不区分大小写(取决于安装时选的 Collation,比如SQL_Latin1_General_CP1_CI_AS),而 MySQL 在 Linux 上默认表名和列名是区分大小写的,字符串比较区分大小写取决于字符集和排序规则。典型场景:代码里写select * from Users where name = 'admin',源库里匹配的是Admin,搬迁后查不到数据。排查方式是在 MySQL 里设置:

-- 数据库级别排序规则改为大小写不敏感(utf8mb4_general_ci 或 utf8mb4_0900_ai_ci) ALTER DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

如果需要逐表控制大小写敏感度,可以在建表时指定COLLATE。但必须记住一点:这个不一致会让应用埋雷,最好是做一次全局统一。如果没有特殊情况,迁移后一律用utf8mb4_0900_ai_ci(MySQL 8 默认)或utf8mb4_general_ci(MySQL 5.7 通用),统一为大小写不敏感和重音不敏感。遇到业务上真的有大小写敏感需求的列,单独在列级别指定utf8mb4_bin或者显式的_cs排序规则,不要整库混用。

5.3 常见性能问题排查

迁移后首轮压测通常会暴露几类典型问题:

第一,明明数据类型和数据量都一样,MySQL 就是比 SQL Server 慢。这类问题十有八九出在索引利用不充分。MySQL 的优化器能力相比 SQL Server 来说相对"朴素",复杂查询条件(比如 OR 多列、函数包裹索引列、隐式类型转换)很容易走全表扫描。

第二,查询WHERE date_col BETWEEN '2024-01-01' AND '2024-01-31'如果date_col是 DATETIME 且建了索引,两边性能都不错;但如果源库写的是WHERE CONVERT(VARCHAR, date_col, 112) = '20240101',迁移时顺手改成了WHERE DATE_FORMAT(date_col, '%Y%m%d') = '20240101',那索引就直接失效,全表扫描没跑。改写的时候要顺手把这类函数包裹索引列的模式改成范围查询。

第三,分页查询的性能差异大。SQL Server 的OFFSET...FETCH和 MySQL 的LIMIT offset, count在深分页(比如第 1000 页)时表现都差,但 MySQL 更明显。应对方案:记录上一页的最大 ID 做游标分页(WHERE id > last_id LIMIT 20),这个改写对前端分页逻辑几乎透明,但对数据库压力是数量级的下降。

第四,子查询与派生表的性能差异。SQL Server 对相关子查询的优化经验丰富,MySQL 在 5.7 之前对派生表会做物化(有derived_merge开关),8.0 之后优化器改善了不少。但如果迁移后碰到某个查询执行计划异常,优先手动改写为 JOIN 形式,实践里大多数情况下都更稳。

6. 容灾与回滚方案

迁移过程中"回滚"不是可选项,是必需品。我见过一个项目上线失败,因为没设计回滚路径,最后只能靠备份还原整库,业务中断了 4 个小时。

6.1 回滚预案设计

核心原则:在切换数据库之前,确保 SQL Server 源库一直保持"随时可写"的状态。具体操作建议:

  • 迁移期间源库保持在线,不动表结构、不加索引、不改应用代码。
  • 在正式切换前,做一次全量备份和差异备份,备份文件异地存放。
  • 切换时间点选在业务低峰期,切换后保留一个观察窗口(我通常留 3 天),观察期间 MySQL 和 SQL Server 双写。方法:应用层同时写两个库,读 MySQL,如果 MySQL 侧出现严重问题,直接把读切回 SQL Server。
  • 观察期结束后,再停掉 SQL Server 侧的业务写入,但库本身保留至少 30 天再下线。

"双轨运行"听起来不优雅,但它是在生产环境最稳妥的策略。很多团队嫌麻烦,直接一把梭切完就删源库,这是拿业务连续性赌博,一旦 MySQL 侧跑出逻辑 bug(比如存储过程改写时漏了个 ELSEIF),你就只能从一堆备份文件里碰运气了。

6.2 数据校验方法

切完后第一件事不是跑业务,而是验证数据一致性。我的习惯是做三层校验:

第一层,全表行数比对。用一条 SQL 同时查两边的行数,重点是有 WHERE 条件的分区子集,不能只看总行数。

-- SQL Server 侧 SELECT COUNT(*) FROM tbl WHERE create_date >= '2024-01-01'; -- MySQL 侧 SELECT COUNT(*) FROM tbl WHERE create_date >= '2024-01-01';

第二层,抽样字段校验。取 1% 的随机行(至少要覆盖所有大表),逐字段对比关键业务字段(金额、时间、状态码)。

第三层,业务前置测试。让 QA 团队跑一遍核心业务链路(下单、支付、改单、查询),盯着每个接口的返回值和响应时间。

三层校验全过了,才算迁移闭环。

7. 迁移项目管理的几条实战经验

最后分享几条我在多个迁移项目里总结出来的管理经验,重要性不亚于任何技术细节。

第一,所有改过的存储过程、视图、函数,必须做回归对比测试。我的做法是准备一套脱敏数据,在源库和目标库上同时执行同名存储过程,自动比对结果集。不要嫌麻烦,存储过程改写是 bug 高发区,靠人工翻代码查错效率太低——写一个脚本自动比对,既能快速发现差异,又能当迁移后的验收依据。

第二,迁移不要追求一步到位。建议先从一个最小的业务模块开始跑通全流程(评估、迁移、验证、切换),形成一套可复用的 SOP,再推广到其他模块。这个试点模块的数据量不用大,但必须覆盖所有类型的数据变更场景(增删改查、批量导入、定时任务)。我第一次做这种项目时就是用一个小模块试点,结果把存储过程改写的坑全踩了一遍,后面大模块反而顺利多了。

第三,找一个人专门负责"迁移后性能基准对比"。迁移不是一个点状事件,很多性能问题在上线前压测场景下根本暴露不出来。上线后前两周,要有人持续对比相同业务在两边数据库的响应时间、慢查询数量、锁等待时间等指标。一旦发现 MySQL 侧的新增慢查询,立刻记录并优化,别等用户投诉到运维部门才去救火。

这些经验不是我凭空想出来的,是真的在一个个订单表、一张张日志表、一条条存储过程里踩出来的。数据库迁移这条路,永远不会像文档里写的那样顺滑,但只要你把每个环节的验收标准定清楚、把回滚路径留好,它就是一个可控的工程,而不是一场豪赌。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 5:35:36

PostgreSQL分区表实战:破解单表亿级数据查询性能瓶颈

单表破亿&#xff0c;查询慢到报警&#xff0c;这场景我在不止一个项目里见过。最开始可能只是运营那边发一句“报表跑不出来了”&#xff0c;后来越来越频繁&#xff0c;慢查询日志里全是那张表&#xff0c;数据库CPU居高不下&#xff0c;磁盘IO一直打满&#xff0c;索引加了几…

作者头像 李华
网站建设 2026/10/17 5:35:16

88E6321交换机参考设计实战:从原理图到量产的关键设计要点

简介&#xff1a;这份资源是Marvell CoverLink Street 88E6321交换机的官方参考设计资料&#xff0c;面向从事千兆以太网交换机硬件设计、嵌入式网络开发及音视频传输系统集成的工程师。88E6321为7端口千兆AVB交换机&#xff0c;集成2个PHY并支持Energy Efficient Ethernet节能…

作者头像 李华
网站建设 2026/10/17 5:34:52

MCP协议:让数据库原生支持AI分析的范式革命

1. 这不是一次技术换岗&#xff0c;而是一次BI工作范式的迁移做了多年BI&#xff0c;我亲手搭过上百张Power BI仪表盘&#xff0c;写过几千行DAX&#xff0c;调过几十种数据源连接&#xff0c;也带过三届新人从SQL基础练到建模优化。但去年底&#xff0c;我主动把刚上线三个月的…

作者头像 李华
网站建设 2026/10/17 5:34:30

别只盯着EXPLAIN的key:索引失效的真正原因与排查方法

排查慢SQL的时候&#xff0c;我见过太多人盯着 EXPLAIN 结果里的 key 字段看一眼&#xff0c;发现不是 NULL&#xff0c;就长舒一口气&#xff1a;“索引走了&#xff0c;稳了。” 结果数据量一上来&#xff0c;接口照样超时&#xff0c;数据库 CPU 照样被打满。说实话&#xf…

作者头像 李华
网站建设 2026/10/17 5:33:56

UltraEdit查看文本编码类型:用TaoToken统一Key排查乱码与编码识别

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华