数据库迁移这事接手的人第一反应往往是找个工具把表和数据倒过去但真做起来才知道SQL Server 到 MySQL 的迁移更像是一次“两边翻译”不只是让数据换个地方存放。成本压力、开源技术栈改造、运维口径统一是很多团队从 SQL Server 迁到 MySQL 的常见理由而落地时最耗时间的却往往是对象定义、函数语法、排序规则、索引规则这些细节。这篇文章我会按自己实际跟过的迁移项目来写从方案选型、环境准备、表结构转换、数据导入到最后的完整性校验和踩坑记录尽量把可以直接照抄的步骤、命令和参数列出来。适合正在做迁移评估、或者已经接到迁移任务又不想踩坑的开发、DBA 和运维同学。1. 迁移方案选型为什么不能直接把数据搬过去很多第一次做 SQL Server 迁移的人会问两边不都是关系型数据库吗SQL 也差不多为什么不能直接导出 INSERT 脚本确实简单的单表数据拷过去没问题但一旦涉及多 schema、存储过程、触发器、默认值、索引约束直接搬就会出来各种不兼容。这就像搬家不是把物品塞进卡车就能顺利入住尺寸、接口、水电布局全部要重新核对。数据库迁移本质上是一个“翻译”过程把 SQL Servert 定义和技术实现逐句转换成 MySQL 能理解的对象。1.1 先摸清 SQL Server 和 MySQL 的核心差异先说最容易踩的差异点。SQL Server 里schema和database是两层概念一个库下面可以挂dbo、sales等多个 schemaMySQL 的database和schema基本是同一个东西没有独立的 schema 层。日常业务里大家习惯写SELECT * FROM dbo.Orders迁移到 MySQL 就得去掉dbo.前缀表名直接挂在库下面。这看起来简单但脚本一多漏改一个就报无效表名。类型差异是第二个高频问题。SQL Server 的NVARCHAR在 MySQL 里没有完全等价物我们用VARCHAR加上utf8mb4字符集来承载 UnicodeDATETIME2对时间精度有要求MySQL 用DATETIME(3)或DATETIME(6)MONEY和SMALLMONEY建议转成DECIMAL(19,4)避免浮点误差UNIQUEIDENTIFIER转成CHAR(36)最省事虽然占空间多一些BIT转成TINYINT(1)语义上不影响大多数应用。函数差异同样不可小觑。SQL Server 用GETDATE()取当前时间MySQL 用NOW()判断空值 SQL Server 用ISNULL(expr, val)MySQL 用IFNULL(expr, val)虽然COALESCE两边都支持但存量代码八成写的是ISNULL查找字符位置SQL Server 用CHARINDEX(x, col)MySQL 用LOCATE(x, col)。这些细节在存储过程和视图里出现频率极高忽略了就会生成一堆运行时报错的脚本。还有一个容易忽略的点是排序规则和大小写敏感。SQL Server 安装时默认排序规则通常是Chinese_PRC_CI_AS不区分大小写MySQL 在 Linux 上默认的表名和数据库名区分大小写lower_case_table_names0排序规则用utf8mb4_unicode_ci或utf8mb4_0900_ai_ci时也不区分大小写。如果你的 SQL 里存在依赖大小写敏感的库名或比较逻辑迁移后行为会变。建议迁移前把 MySQL 的lower_case_table_names统一设置为 1这样 Windows 和 Linux 上面的表现一致后续部署也不会被环境差异坑到。这个参数要在实例初始化之前配置好中途改会造成表文件路径混乱。1.2 三条主流迁移路线怎么选目前常用的迁移路线有三条微软官方的SSMA for MySQL、Navicat Premium的数据传输/结构同步、以及纯手写脚本改造。选型先看规模的复杂度几百张表和一个库只有几十张表的打法完全不同。SSMA 全称是SQL Server Migration Assistant for MySQL微软官方免费工具直接下载即可使用不需要额外激活码。它最大的优势是能批量读取 SQL Server 的元数据自动完成大部分类型映射和 T-SQL 到 MySQL 语法的转换同时生成评估报告哪个对象哪些语句转换不了会标注出来。缺点是生成的 DDL 有时过于保守比如会把VARCHAR长度放大三倍索引名称也有重复截断风险需要人工复查。适合迁移对象很多、需要快速建立基线脚本的场景。Navicat Premium 的操作体验最直观连接两边数据库后可以用“数据传输”功能迁移表结构和数据也可以用“结构同步”对比 DDL 差异。它适合中小型库、表结构相对简单的项目一两个小时内就能跑完。但遇到自定义类型、复杂存储过程的时候它的转换能力偏弱很多时候还是得从 SSMS 里导出脚本到 MySQL 里手动执行。实测下来Navicat 传输千万级大表时会长时间占用内存需要把每次传输的数据批量调小并在业务低峰执行。手写脚本是最后的选择适合表数量不多、业务逻辑特殊、必须严格管理 DDL 的场景。你可以从 SSMS 生成原始脚本再逐条改成 MySQL 语法。过程很琐碎但可控性最高每一条变更都清楚。实际项目里我通常用“组合拳”SSMA 生成第一版 DDL 和评估报告Navicat 做结构同步和数据初迁最后的存储过程、触发器和历史数据补录用手写脚本处理。这样比只用任何一个工具都稳。1.3 动工前的三个检查点动工之前不要让开发直接连数据库先花半天做盘点。第一个检查点是数据规模与对象清单库有多大、表多少张、哪几张表超过千万行、有没有大字段、有没有维护了很长时间的历史归档表。把结果整理成表格决定哪些能全量迁移、哪些需要分批抽数哪些历史数据可以只迁汇总结果。第二个检查点是字符集和排序规则。建议目标 MySQL 实例统一使用utf8mb4utf8mb4_unicode_ci不要为了省空间用latin1否则中文、生僻字、Emoji 都可能变成乱码。源库如果有大小写敏感的业务依赖要提前标注出来迁移后用具体列的COLLATE utf8mb4_bin来局部处理而不是全局改排序规则。第三个检查点是 SQL Mode。MySQL 默认的sql_mode在不同版本上差异不小8.0 通常带有ONLY_FULL_GROUP_BY、STRICT_TRANS_TABLES、NO_ZERO_DATE等很多从 SQL Server 迁来的 SQL 在严格模式下会直接无法执行。迁移初期建议把sql_mode调成接近宽松模式的状态先把业务跑起来再逐步修掉不合规的 SQL最后再把ONLY_FULL_GROUP_BY打开。千万不要一上来就开最严格的模式否则你会同时面对语法错误和数据格式错误两座大山。2. 环境准备与工具检查迁移最怕在操作过程中源库还在持续写入所以环境准备的核心是“把源库固定住把目标库准备好”。这个阶段不需要太多技术含量但漏掉一个环节就可能导致数据不一致。2.1 SQL Server 侧的准备迁移开始前对 SQL Server 实例做一次完整备份是底线操作。迁移过程中哪怕只读表也可能误执行一些脚本导致意外。备份可以让你在任何时刻回退不用从 0 开始。备份完成后给迁移账号最小权限读取所有表数据的SELECT、查看所有对象定义的VIEW DEFINITION如果涉及到存储过程还需要读取sys.sql_modules的权限。不要图省事直接给sysadmin这会放大误操作风险。然后要跟业务方确认维护窗口。常见做法是迁移期间把源库置为只读避免增量数据漂移。如果业务绝对不能停那就要规划增量同步方案通常的做法是全量迁移某一时间点的快照然后用 SQL Server 的变更跟踪或 CDC 把增量补到 MySQL。这个方案复杂度直接上一个台阶除非必要第一次迁移不建议做老老实实申请停机窗口最靠谱。版本差异也要注意。老实例如果是 SQL Server 2000/2005/2008生成的脚本里会出现WITH NOCOUNT ON、[dbo].[Table]这类旧语法SSMA 转换率会低一些。另外老版本实例上通常有一堆没人清理的扩展属性、用户定义类型、自定义错误消息这些对象在 MySQL 里没有对应概念迁移前要单独盘点别让它们在原始脚本里阻断建表流程。2.2 MySQL 实例安装与基础配置MySQL 安装本身不复杂Windows 用安装向导Linux 用包管理器或下载 tar 包。真正重要的是配置文件my.cnf里几个决定迁移成败的参数。这里给一个经过实际项目验证的初始配置片段[mysqld] character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci max_allowed_packet256M bulk_insert_buffer_size64M innodb_buffer_pool_size16G innodb_log_file_size1G innodb_flush_log_at_trx_commit2 innodb_flush_methodO_DIRECT transaction-isolationREAD-COMMITTED slow_query_logON slow_query_log_file/var/log/mysql/slow.log long_query_time1innodb_buffer_pool_size建议设置为物理内存的 60% 到 70%这不是乱拍脑袋InnoDB 的数据页、索引页、插入缓冲都要用它给得太小会导致导入时频繁直接写磁盘耗时翻倍。max_allowed_packet设置为 256M 是为了防止大字段或者大批量 INSERT 时出现Packet too large中断业务系统里TEXT、BLOB较多时尤其重要。innodb_flush_log_at_trx_commit2是降低每次提交的磁盘刷新频率导入阶段可以提高吞吐但请注意事务安全性会弱一些上线前要评估能不能接受。transaction-isolationREAD-COMMITTED是考虑到 SQL Server 默认的隔离级别就是 READ COMMITTED迁过去之后并发行为更接近原系统。参数配置完成后创建目标库和迁移账号CREATE DATABASE sales DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER mig_user% IDENTIFIED BY StrongPass123; GRANT ALL PRIVILEGES ON sales.* TO mig_user%; FLUSH PRIVILEGES;不要直接在 root 下做数据导入迁移账号权限足够即可后面排查问题有清晰边界。2.3 连接工具与常见的 SSL/驱动报错Navicat Premium 是我最喜欢的迁移终端同时支持 SQL Server 和 MySQL。新建连接时SQL Server 侧建议选择“使用 Windows 身份验证”或 SQL Server 身份验证注意实例名要写清楚比如127.0.0.1,1433不要漏端口。MySQL 侧重用刚才创建的mig_user测试连接通过后再继续。这里顺便聊聊一个高频报错[08001] [Microsoft][ODBC Driver 17 for SQL Server] SSL 提供程序: 证书链是由不受信任的颁发机构颁发的。这个报错经常出现在新版 ODBC 驱动连接 SQL Server 2019/2022 的时候因为驱动默认开启了强制加密。如果你在迁移准备阶段用其他工具连 SQL Server 遇到这个错解决办法是在连接字符串里把Encrypt设为Optional或者使用TrustServerCertificateTrueNavicat 里则把“使用 SSL”选项关掉即可。这个和 MySQL 迁移目标本身没关系但常常成为迁移团队环境准备的第一个拦路虎处理不好会以为源库禁止连接浪费时间排查。MySQL 8 还有个常见的连接坑默认认证插件是caching_sha2_password旧版客户端连接时报Authentication plugin caching_sha2_password cannot be loaded。要么升级客户端驱动要么在创建账号时指定IDENTIFIED WITH mysql_native_password BY ...。生产环境我更建议升级驱动因为mysql_native_password在 MySQL 8.4 里已经开始被进一步弱化以后还是要切到新认证插件。3. 核心实操从建表、到转换、再到导入到了这个阶段方案和准备工作都做完了开始真正碰元数据和数据。顺序很重要先把表结构建好再导数据最后处理对象逻辑。这个顺序能帮你把问题拆开建表报错只跟 DDL 有关导数据报错只跟数据质量有关排查起来不用两边猜。3.1 表结构导出的三种方法用 SSMA 生成表结构是最快的。打开 SSMA新建项目连接 SQL Server 和 MySQL选择要迁移的库点击“转换模式”后会生成一份转换评估报告。报告里会列出无法自动转换的对象和语句比如OPENQUERY、APPLY、PIVOT这类 MySQL 没有直接语法的内容。SSMA 生成的建表语句可以一键保存成 SQL 文件也可以直接在 MySQL 端执行。但评估报告说“成功”不代表万事大吉要抽查几张关键表的 DDL。Navicat 的结构同步适合不想引入新工具的情况。左边选择 SQL Server 库右边选择 MySQL 目标库点击“结构同步”Navicat 会扫描差异并生成目标库缺失的建表脚本。操作很直观但它对存储过程、视图的转换能力比 SSMA 弱而且遇到 MySQL 不支持的默认值会直接忽略不建议单独依赖它。如果要完全掌握 DDL最稳妥的方式还是从 SSMS 导出原始脚本再手动改。SSMS 里选择库→右键→任务→生成脚本选择所有表导出后你会看到类似这样的 SQL Server 原生定义CREATE TABLE [dbo].[Orders] ( [OrderID] INT IDENTITY(1,1) NOT NULL PRIMARY KEY, [OrderNo] NVARCHAR(20) NOT NULL, [CustomerID] INT NOT NULL, [CreatedAt] DATETIME2(3) NOT NULL, [TotalAmount] MONEY NOT NULL );手动改造后变成 MySQL DDLCREATE TABLE orders ( OrderID INT NOT NULL AUTO_INCREMENT, OrderNo VARCHAR(20) NOT NULL, CustomerID INT NOT NULL, CreatedAt DATETIME(3) NOT NULL, TotalAmount DECIMAL(19,4) NOT NULL, PRIMARY KEY (OrderID) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci;这里有几个选择背后的原因。NVARCHAR(20)直接对应VARCHAR(20)是合理的因为 MySQL 的VARCHAR(20)也代表 20 个字符utf8mb4只是影响存储字节数不影响字符数量定义。有些转换工具会把长度自动乘以 3生成VARCHAR(60)倒也不会错但会浪费一点索引空间建议按字符数保持一致遇到长列索引报错时再做前缀索引处理。IDENTITY(1,1)换成AUTO_INCREMENTMySQL 在建表时就会自动维护自增。MONEY转DECIMAL(19,4)是为了保留精确小数SQL Server 里 MONEY 也是精确类型不能转成DOUBLE。表名、列名建议统一小写避免在 Linux 环境下和lower_case_table_names1不一致引发问题。3.2 类型映射与常用函数改写清单类型映射是迁移中绕不开的硬核对。下面是我常用的对照表SQL ServerMySQL说明NVARCHAR / NCHARVARCHAR / CHAR依赖库表字符集为 utf8mb4VARCHAR / CHARVARCHAR / CHAR基本可直迁TEXT / NTEXTTEXT / LONGTEXT大文本按需选型INT / BIGINT / SMALLINTINT / BIGINT / SMALLINT注意显示宽度概念不同TINYINTTINYINT两边都是 0-255DECIMAL(p,s)DECIMAL(p,s)精度刻度保持一致MONEY / SMALLMONEYDECIMAL(19,4) / DECIMAL(10,4)避免浮点误差DATETIMEDATETIME范围不同注意零日期DATETIME2(n)DATETIME(n)精度对应DATETIMEOFFSETVARCHAR(45) / TIMESTAMPMySQL 无原生 offset 类型DATE / TIMEDATE / TIME直迁UNIQUEIDENTIFIERCHAR(36)UUID 字符串形式IMAGE / VARBINARY(MAX)LONGBLOB / BLOB二进制大对象BITTINYINT(1)0/1 语义ROWVERSION / TIMESTAMP无直接等价业务字段自行维护函数改写主要集中在存储过程和视图里。常用对照-- SQL Server 写法 SELECT ISNULL(ColumnA, ) FROM dbo.Orders; SELECT CHARINDEX(A, OrderNo) FROM dbo.Orders; -- MySQL 对应写法 SELECT IFNULL(ColumnA, ) FROM orders; SELECT LOCATE(A, OrderNo) FROM orders;GETDATE()要改为NOW()NEWID()改为UUID()GETUTCDATE()改为UTC_TIMESTAMP()CONVERT(VARCHAR(10), OrderDate, 120)在 MySQL 里写成DATE_FORMAT(OrderDate, %Y-%m-%d)。这些规则不复杂但量大人工改容易漏。实际执行时我习惯先把所有.sql文件跑一个字符串替换脚本把GETDATE()、ISNULL、CHARINDEX这些高频函数先统一替换然后再人工处理语义差异比较大的语句。存储过程改造是迁移中最大的工程。SQL Server 的存储过程通常大量使用SET NOCOUNT ON、临时表#temp、RETURN返回值这些在 MySQL 里没有完全对应的用法。举一个最简单的例子SQL Server 里CREATE PROCEDURE [dbo].[sp_GetOrder] OrderID INT AS BEGIN SET NOCOUNT ON; SELECT OrderID, OrderNo FROM dbo.Orders WHERE OrderID OrderID; END;MySQL 里等价写法DELIMITER // CREATE PROCEDURE sp_GetOrder(IN p_OrderID INT) BEGIN SELECT OrderID, OrderNo FROM orders WHERE OrderID p_OrderID; END // DELIMITER ;MySQL 存储过程不需要SET NOCOUNT ON因为默认就不会像 SQL Server 那样返回Rows affected计数。OrderID改成输入参数p_OrderID更清晰也避免和会话变量混淆。复杂存储过程中如果用到RAISERROR改成SIGNAL SQLSTATE 45000 SET MESSAGE_TEXT xxx用到尝试捕获时MySQL 的DECLARE EXIT HANDLER FOR SQLEXCEPTION放在BEGIN开头处语法上和 SQL Server 的BEGIN CATCH差别很大这块没法完全自动转换SSMA 转换完之后几乎都要人工过一遍。3.3 数据导出导入实战方案数据导入方式取决于数据量级和源库导出的中间格式。如果源和客户机网络环境允许直接用 Navicat 的数据传输最省事。选中两个库勾选需要传输的表在高级设置里把“每次传输数据量”调大到 2000 行左右开启“事务”选项这样万一中断不会留下半截数据。实测下来千万级表在局域网跑大约需要十几分钟已经可接受。更快的方式是先导出 CSV 再用 MySQL 的LOAD DATA INFILE加载。SQL Server 导出 CSV 可以用 BCP 工具bcp select OrderID, OrderNo, CustomerID, CreatedAt, TotalAmount from sales.dbo.Orders queryout /data/orders.csv -c -t, -S localhost -U sa -P password然后 MySQL 端执行LOAD DATA LOCAL INFILE /data/orders.csv INTO TABLE orders FIELDS TERMINATED BY , ENCLOSED BY LINES TERMINATED BY \n IGNORE 1 LINES;LOAD DATA比逐条 INSERT 快一个数量级原因是它直接走 InnoDB 的批量接口减少了解析和日志刷新的开销。如果是几万行的中小表写一段简单的 INSERT 脚本跑也行没必要引入复杂的导出逻辑。大规模迁移时我会把多个 CSV 按表分组分批LOAD DATA避免一次加载所有数据导致磁盘和内存打满。导入顺序是一个很关键但容易被忽略的细节。有外键依赖的表必须先导主表再导从表否则关联键还没建就插入子表记录会报外键失败。更省心的做法是在导入前先关掉外键检查SET FOREIGN_KEY_CHECKS0; -- 所有表数据导入在这里执行 SET FOREIGN_KEY_CHECKS1;这个开关只影响当前会话导入完成后必须确认已经重新置为 1。执行完后再补外键约束比一边导数据一边校验快得多。对超大表最忌讳的是把所有 INSERT 放在一个长事务里一旦中间报错回滚代价极高。分批提交的节奏通常每 5000 行 commit 一次或按 CSV 文件每 20 万行做一个事务边界能显著降低 undo 和 redo 的膨胀压力。3.4 数据完整性校验的几种办法数据导完不等于迁移完成不校验就等于没做。最简单的校验是全表行数对比。SQL Server 端执行SELECT Orders, COUNT(*) FROM dbo.Orders;MySQL 端执行SELECT orders, COUNT(*) FROM orders;如果表数量多靠手写这些 SQL 效率太低。可以写一个小脚本从两边information_schema.tables里把表名和行数读出来生成对比结果。我习惯把中间结果落到一张migration_check表里最后对table_name关联对比一秒找出缺失和行数不一致的表。行数一致还不够还要做数据抽样校验。对带主键的表按主键取模抽样比如每 100 条抽一条SELECT OrderID, OrderNo, CreatedAt, TotalAmount FROM orders WHERE OrderID % 100 0 ORDER BY OrderID;两边执行后对结果做 diff能快速发现列错位、时间时区偏移、精度丢失等问题。抽样比例不需要很高大表抽 1% 到 5% 就足够发现问题如果是金融、订单这类关键业务建议先把核心表做全量。对象层面的校验同样重要。比较两边视图、存储过程、触发器数量数量对不上就说明有对象没迁完。MySQL 端没有内置的CHECKSUM TABLE只能做表级校验但它不支持和 SQL Server 直接比较所以还是以行数加抽样为主。最后别忘了校验自增种子ALTER TABLE orders AUTO_INCREMENT 100000;这个值应该等于源库 IDENTITY 当前值加 1否则后续插入新记录可能主键冲突。4. 踩坑实录与迁移后的调优就算按照完整流程走迁移过程中还是会出现各种意料之外的问题。这个章节把我在实际项目里遇到的高频故障整理成一份速查表遇到对应现象能直接对照处理。同时说一下迁移完成后 MySQL 实例怎么做初始调优以及从 SQL Server 迁过来的团队在开发习惯上要注意什么。4.1 高频故障速查表现象原因解决办法导入后中文乱码连接字符集、库表字符集不统一库表统一utf8mb4连接串加characterEncodingutf8已乱数据用ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4恢复自增列继续插入时主键冲突源库 IDENTITY 当前值没有迁移用ALTER TABLE t AUTO_INCREMENT当前值重设种子建表时报Specified key was too longutf8mb4 下索引列字节数超限长列改用前缀索引KEY idx_name (col(191))或缩短列长度默认值执行报错SQL Server 的DEFAULT GETDATE()写法在 MySQL 中不适用MySQL 使用DEFAULT CURRENT_TIMESTAMP注意不能加括号CURRENT_TIMESTAMP()表名或者查询报语法错误使用了order、desc、group、user等保留字统一用反引号包裹字段或表名最好直接改字段名查询报ONLY_FULL_GROUP_BY错误MySQL 8.0 默认 SQL Mode 严格先改sql_mode去掉该规则业务验证后再逐步修正 SQL导入时报0000-00-00日期无效MySQL 默认不允许零日期SQL Mode 中加ALLOW_INVALID_DATES或者把零日期转NULLWindows 下服务启动失败数据目录权限异常或初始化未完成查看 data 目录下的.err日志执行mysqld --initialize-insecure后重启大批量导入后磁盘飙升、事务日志膨胀大量 INSERT 集中在长事务分批提交每 5000 行一个事务边界适当调大innodb_log_file_size中文乱码是出现率最高的问题。心法只有一个从 MySQL 实例、库、表、连接四个层级都看一遍只要有一层不是utf8mb4就可能出现乱码。连接串里尤其要检查是不是落了characterEncodingUTF-8。已经乱掉的数据不要反复改ALTER TABLE先导出一份数据文件确认字符集再按正确字符集重新导入。时间类问题也非常常见。SQL Server 的DATETIME最小值是 1753 年而 MySQL 的DATETIME范围是 1000 年到 9999 年理论上范围更大但 MySQL 在严格模式下不接受0000-00-00这个值。老业务系统经常用最小时间当“空值”迁移后就变成 SQL 报错。遇到这种情况要把零日期转成NULL或者调整 SQL Mode不建议为了兼容零日期长期改库级别配置。4.2 迁移后 MySQL 的初始配置数据导完、校验通过接下来 MySQL 要承载真实业务流量这时不要留着迁移期参数直接用。迁移阶段为了吞吐量我会把innodb_flush_log_at_trx_commit保持为 2但业务上线后如果对数据安全要求高要改回 1否则异常断电可能会丢失最多一秒的事务日志。max_connections根据应用的连接池规格来设通常 500 到 1000 已经够用不要盲目调高连接数过多反而会拖垮系统。慢查询日志建议一直开着。slow_query_logON、long_query_time1的设置不影响性能但迁移后经常有原先在 SQL Server 上跑得很快的语句到 MySQL 变慢没有慢日志就只能靠应用报错定位效率太低。上线第一周我会每天看一眼慢日志把高频语句用EXPLAIN分析索引执行计划找出全表扫描的语句逐条补索引。MySQL 8 的EXPLAIN输出比 5.7 更详细重点看rows估算值和possible_keys。如果一条查询预估扫描行数达到百万级别但key列为空基本就是缺索引。有些迁移后的 SQL 习惯写SELECT *建议顺手优化成按需列这在 SQL Server 上可能差异不大但在 MySQL 的大表上回表次数差距非常明显。4.3 迁移后开发习惯的注意从 SQL Server 转到 MySQL团队最容易踩的坑是事务隔离级别。SQL Server 默认是READ COMMITTEDMySQL 默认是REPEATABLE READ。REPEATABLE READ会在查询范围内加间隙锁Gap Lock高并发场景下更容易出现死锁。如果业务逻辑已经按照 SQL Server 的习惯编写建议在实例配置里把transaction-isolationREAD-COMMITTED固定下来并且让应用侧连接串明确指定隔离级别避免不同环境行为不一致。存储引擎和事务支持上也要确认。MySQL 的 MyISAM 不支持事务和外键如果是从老项目继承过来的建表语句务必统一改成 InnoDB。新写业务时也建议默认 InnoDB。定时作业方面SQL Server Agent 在 MySQL 里对应的是事件调度器迁移后要单独处理。SQL Server 的每个 JOB 都要重新写成CREATE EVENT ... ON SCHEDULE EVERY ...并且确保event_schedulerON。这个环节特别容易被忽略因为迁移文档通常只关注表和存储过程漏掉 JOB 会导致后续月结、统计脚本静默停摆。模型设计上还有一个小建议。如果团队后续打算做时序数据或者接入国产时序数据库比如把 MySQL 表结构自动映射成 TDengine 超级表和子表迁移阶段就要有意识地把字段类型规范起来时间字段统一TIMESTAMP或DATETIME测点数值用FLOAT或DOUBLE标签列用VARCHAR并且保证每张表都有明确的时间列。这样以后做数据同步、分区、降采样都会顺畅很多不然等数据量上来再改模型成本至少是现在的三倍。迁移完成后我习惯把整个映射关系文档留一份每次改字段都会回头对照。数据库迁移项目真正难的不是最后执行那一步而是把 SQL Server 里那些没人写文档的业务判断翻译成 MySQL 的写法。如果你手头正在做类似的迁移我的建议是先找一张不影响业务的小表完整走一遍确认生成、导入、校验都没有问题再铺开全量。这样你心里有底后面再大的库也不会慌。