正文
在过去的二十年里,我亲历了无数次数据引擎的迭代。每一次引擎的跃迁,背后都是一场对性能、成本与未来可扩展性的豪赌。今天,我想以亲历者的身份,谈谈把Microsoft SQL Server(俗称MSSQL)迁移到MySQL这件事——我把它简称为“mssql2mysql”。这不是一次简单的SQL语法翻译,而是一次对数据生态、业务架构乃至团队心智模型的重构。
为什么要迁?
当年我们选择MSSQL,是因为它与企业级Windows生态的无缝集成,以及当年在OLTP场景下无可匹敌的优化器。然而,当云原生、容器化、开源治理成为主旋律,MySQL的MIT协议与社区活跃度开始释放出巨大的成本红利。更关键的是,MySQL 8.0在窗口函数、CTE、JSON支持上的补齐,让“功能缺口”这一顾虑被极大削弱。换句话说,迁移的ROI公式被重新计算,结果一目了然。
最难的不是语法,是心智
T-SQL与MySQL方言的差异,表面看是TOP与LIMIT、IDENTITY与AUTO_INCREMENT、@@ERROR与ROW_COUNT()的映射,实则是“批处理思维”到“连接池思维”的切换。MSSQL擅长在存储过程里一口气跑完业务闭环,而MySQL更倾向把复杂逻辑拆到应用层,用短连接+索引来换吞吐量。团队必须在两周内完成一次“算法思维”到“系统思维”的集体转身,这比写一千行兼容脚本更难。
迁移三板斧
第一斧,Schema翻译。我们写了一个Python脚本,调用sqlglot做AST级解析,把MSSQL的DATETIME2、MONEY、UNIQUEIDENTIFIER自动映射到MySQL的DATETIME(6)、DECIMAL(19,4)、BINARY(16)。这一步解决了90%的DDL不兼容。
第二斧,数据校验。利用pt-table-checksum在双写阶段做行级CRC32比对,每十分钟采样一次,差异率>0.001%即报警。
第三斧,回滚预案。在Kubernetes里同时跑两套StatefulSet:MSSQL主从+MySQL主从,流量权重按百分比分批切换,一旦P99延迟升高超过20%,15秒内自动切回原集群。
踩过的坑
· 隐式字符集:MSSQL的NVARCHAR到MySQL的utf8mb4,千万行数据出现“Emoji表情变问号”,原因是驱动层的编码握手不一致,需在连接串显式加charset=utf8mb4。
· 时区陷阱:MSSQL的GETDATE()返回服务器本地时区,而MySQL的NOW()默认跟随系统变量time_zone。跨地域部署时,必须把两者都钉在UTC,否则报表会对不齐。
· 锁粒度:MSSQL的页锁在并发更新时容易死锁,MySQL的InnoDB行锁虽轻,但如果忘记加索引,会瞬间退化成表锁。迁移后第一周,我们加了47个复合索引,才把慢查询日志压到原来的十分之一。
结果
六个月完成全量迁移,存储成本下降42%,写入QPS提升1.8倍。更关键的是,研发团队开始习惯用pt-query-digest做日常体检,而不是等DBA救火。这种“自驱优化”的文化迁移,才是真正的胜利。
结语
每一次技术路线的切换,都不是简单的“更好”与“更差”,而是“更适合”与“更不适合”。mssql2mysql不是终点,而是我们向云原生、向开源治理迈出的又一步。愿后来者少走弯路,把精力留给业务创新,而非历史包袱。
————————————————————
【教程】mssql2mysql超详细操作手册
目标:把MSSQL 2019生产库完整迁移到MySQL 8.0.34,零停机、可回滚。
环境:CentOS 7、Docker 24、Python 3.11、mssql-cli 1.6、MySQL Shell 8.0。
步骤1:前置检查
① 确认MSSQL版本≥2012,启用CDC(变更数据捕获)。
② 在MySQL侧创建同名schema,字符集统一utf8mb4_0900_ai_ci。
③ 安装依赖:
pip install sqlglot pymssql mysql-connector-python pt-table-sync
步骤2:Schema自动翻译
python migrate_ddl.py --src-dsn "mssql://user:pwd@host:1433/db" --dst-dsn "mysql://user:pwd@host:3306/db"
脚本逻辑:
· 用sqlglot解析MSSQL的sys.objects、sys.columns;
· 生成MySQL兼容的DDL,自动处理IDENTITY→AUTO_INCREMENT、DATETIME2→DATETIME(6);
· 输出review.sql,人工review后再执行。
步骤3:全量数据迁移
① 关闭MSSQL的外键约束:
EXEC sp_msforeachtable "ALTER TABLE ? NOCHECK CONSTRAINT ALL"
② 使用mydumper并行导出:
mydumper -h mssql_host -u user -p pwd -B db -t 16 -F 256 -o /backup/full
③ 使用myloader导入MySQL:
myloader -h mysql_host -u user -p pwd -d /backup/full -t 16 -o
步骤4:增量同步与校验
① 在MSSQL端启用CDC,捕获表级变更。
② 使用Debezium SQL Server Connector把变更流写到Kafka。
③ 下游用Kafka Connect JDBC Sink写入MySQL。
④ 每10分钟运行校验脚本:
pt-table-checksum --replicate percona.checksums --create-replicate-table --databases db --tables t1,t2 h=mysql_host,u=user,p=pwd
⑤ 若校验失败,用pt-table-sync自动修复:
pt-table-sync --execute --replicate percona.checksums h=mysql_host,D=db,t=t1
步骤5:流量切换
① 在Kubernetes里部署两套Deployment:
· mssql-proxy(只读)
· mysql-proxy(读写)
② 使用Istio按权重灰度:
0%→1%→5%→25%→50%→100%,每阶段观察P99延迟、错误率、binlog lag。
③ 切换完成后,保留MSSQL只读72小时,作为最终回滚窗口。
步骤6:收尾
① 打开MySQL的外键约束:
SET FOREIGN_KEY_CHECKS=1;
② 收集慢查询,跑pt-query-digest,补索引。
③ 关闭MSSQL的写权限,归档备份,完成迁移。
至此,mssql2mysql全链路闭环。祝你一路无坑,迁移愉快!