1. 项目概述:为什么一个数据库迁移工具值得花一整天去拆解它
达梦数据库DTS工具MySQL→DM8,这串字符组合在最近三个月的某高校信创实验室、某省属政务云迁移项目组、以及几家中小银行核心系统国产化替代推进会上,出现频率高得有点反常。不是因为它多炫酷,而是因为——它真正在解决一个卡脖子级别的实操问题:把跑了十年的老MySQL业务库,不改一行代码、不中断关键服务、不丢一条订单数据,平滑切到DM8上。我去年参与过三个类似规模的迁移项目,最深的体会是:所谓“国产化替代”,90%的精力不在选型论证,而在DTS这一关能不能稳住。很多人以为DTS就是个图形界面点点点的工具,实则不然。它本质是一套带事务补偿机制的数据流管道,底层要同时理解MySQL的binlog协议和DM8的逻辑日志格式,中间还要处理字符集映射、自增ID冲突、时间精度截断、JSON字段兼容性等二十多个隐性雷区。比如MySQL里一个DATETIME(6)字段,在DM8里默认只存到毫秒级,差那三微秒,某些金融对账场景就会触发告警;再比如MySQL的utf8mb4和DM8的UTF-8看似一样,但DM8默认不开启utf8mb4兼容模式,一同步就变问号。这些细节,官方文档往往一笔带过,但实操中一个没注意,轻则数据乱码重则主从延迟飙升。所以这篇内容,不讲大道理,只聚焦你打开DTS控制台后,真正要面对的每一个下拉框、每一行配置、每一次校验失败背后的原理和解法。适合正在做信创迁移的DBA、需要交付国产化方案的集成商工程师,以及想搞懂数据库底层同步机制的后端开发者。如果你只是想查个命令怎么写,这里可能太细;但如果你正对着DTS界面上那个红色的“校验失败”弹窗发愁,那接下来的内容,就是你今天最该读完的。
2. 整体设计思路与方案选型逻辑:为什么非得用DTS而不是mysqldump+sqlldr
2.1 迁移不是“导出再导入”,而是“状态连续性保障”
很多刚接触DM8的开发者第一反应是:用mysqldump导出SQL,再用DM8的disql执行导入。这个方案在测试库跑通了,但一上生产就崩。原因很简单——它破坏了数据一致性窗口。假设你导出花了15分钟,这期间MySQL还在持续写入,导出的SQL文件只代表15分钟前某个瞬间的状态。而disql导入又耗时20分钟,导入完成时,原始MySQL已经比你导入的数据新了35分钟。这35分钟的增量数据,你根本没管。更麻烦的是,如果业务表有外键约束、触发器、存储过程,mysqldump导出的SQL在DM8上大概率报错,因为语法不兼容。DTS的设计哲学完全不同:它把整个迁移过程拆成全量同步+增量同步+平滑切换三阶段。全量阶段把现有数据搬过去,同时DTS会实时监听MySQL的binlog,把全量搬运期间产生的所有变更(INSERT/UPDATE/DELETE)记下来;全量完成后,立刻切入增量同步,把积压的变更追平;最后在业务低峰期,停写MySQL几秒钟,让DTS把最后一小段增量打完,再把应用连接字符串指向DM8。整个过程,业务感知不到中断,数据零丢失。这才是企业级迁移的底线。
2.2 DTS vs 其他同步方案的硬性对比
为什么不用Canal+自研适配器?或者用Debezium接Kafka再写DM8?我们做过横向测试,结论很明确:对于MySQL→DM8这种强耦合、需深度兼容的场景,DTS是唯一经过大规模验证的方案。下表是某省社保系统迁移时的真实对比数据(脱敏后):
| 方案 | 开发周期 | 稳定性(7×24h) | 字符集兼容性 | 时间精度支持 | 外键/约束处理 | 运维复杂度 |
|---|---|---|---|---|---|---|
| mysqldump+disql | 0.5人天 | ❌(导入失败率12%) | ❌(utf8mb4乱码) | ❌(微秒级丢失) | ❌(需手动删约束) | 低(但不可靠) |
| Canal+自研适配器 | 12人天 | ⚠️(需自建监控告警) | ✅(可定制) | ✅(需额外开发) | ⚠️(需解析DDL) | 高(依赖Java运维) |
| DTS工具 | 2人天 | ✅(官方SLA 99.99%) | ✅(内置映射规则) | ✅(自动精度对齐) | ✅(自动转换语法) | 低(图形化+日志清晰) |
关键点在于:DTS不是通用ETL工具,它是达梦为MySQL迁移专门打磨的“领域专用工具”。它内置了MySQL 5.7/8.0和DM8 21.03/22.01的所有协议解析器,连MySQL的ROW_FORMAT=COMPRESSED这种冷门参数都做了兼容处理。而Canal这类通用CDC工具,遇到DM8特有的SEQUENCE对象或DOMAIN类型,就得自己写转换逻辑——这恰恰是踩坑最多的地方。
2.3 架构分层:DTS不是黑盒子,它的三层结构决定了你能调什么
DTS的架构其实非常清晰,理解这三层,你就知道哪些参数能动、哪些必须按默认值来:
接入层(Agent):部署在MySQL服务器同机房的Linux机器上,负责抓取binlog。它不直接连MySQL,而是伪装成MySQL的一个从库(Slave),通过标准的
CHANGE MASTER TO命令建立复制关系。所以你必须给DTS Agent分配一个MySQL账号,该账号要有REPLICATION SLAVE权限。这是很多初学者卡住的第一步:账号没权限,Agent启动就报“Access denied”。传输层(Engine):这是DTS的大脑,运行在独立服务器上。它接收Agent传来的binlog事件,做三件事:① 解析成统一的中间事件格式(如
{op:"INSERT", table:"order", pk:123, data:{...}});② 根据预设的映射规则做字段转换(比如把MySQL的TINYINT(1)转成DM8的BOOLEAN);③ 将转换后的事件发往DM8。传输层的性能瓶颈通常在CPU,因为binlog解析是计算密集型任务。目标层(Writer):部署在DM8服务器上,负责把传输层发来的事件,用DM8原生协议写入。它会自动处理事务提交、批量插入优化(如
INSERT INTO ... VALUES (),(),())、以及错误重试。Writer最怕的是网络抖动,所以强烈建议DTS Engine和DM8部署在同一内网VLAN,延迟控制在1ms以内。
提示:DTS的“全量同步”和“增量同步”其实是两套并行流程。全量走的是JDBC直连MySQL抽取,增量走的是binlog监听。这意味着全量阶段可以和增量阶段重叠——你不需要等全量结束才开增量,DTS会自动把全量期间的变更缓存在内存队列里。这个设计极大缩短了总迁移时间。
3. 核心细节解析与实操要点:从创建任务到首次校验成功的全流程拆解
3.1 前置检查清单:90%的失败源于这五项没做
别急着点“新建任务”,先花10分钟做这几件事,能省下你后面3小时排查时间:
MySQL版本与模式检查:DTS明确要求MySQL 5.7.13+或8.0.11+,且必须开启
binlog_format=ROW(不能是STATEMENT或MIXED)。执行SHOW VARIABLES LIKE 'binlog_format';确认。另外,确保server_id已设置(SHOW VARIABLES LIKE 'server_id';),且不为0——这是MySQL复制的基础。字符集强制统一:MySQL库、表、字段的字符集必须全部是
utf8mb4。执行SELECT DEFAULT_CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME='your_db';检查库级字符集;再用SHOW CREATE TABLE your_table;看表级定义。如果发现latin1或utf8(注意不是utf8mb4),必须先执行ALTER DATABASE your_db CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;,否则DTS同步时会静默丢弃四字节emoji。DM8用户权限验证:DTS需要一个DM8账号,该账号必须拥有
CREATE ANY TABLE、INSERT ANY TABLE、UPDATE ANY TABLE、DELETE ANY TABLE、SELECT ANY TABLE权限。别用SYSDBA,用普通用户即可。创建语句示例:CREATE USER dts_user IDENTIFIED BY 'StrongPass123!'; GRANT CREATE ANY TABLE, INSERT ANY TABLE, UPDATE ANY TABLE, DELETE ANY TABLE, SELECT ANY TABLE TO dts_user;网络连通性实测:DTS Agent要能连MySQL的3306端口,DTS Engine要能连DM8的5236端口。别信“同一VPC肯定通”,一定要用
telnet mysql_ip 3306和telnet dm8_ip 5236实测。某次项目失败就是因为DM8防火墙策略只放行了应用服务器IP,没放DTS Engine的IP。时间同步校验:MySQL服务器、DTS Engine服务器、DM8服务器三者的时间差必须小于5秒。用
ntpdate -q pool.ntp.org检查。时间不同步会导致binlog位点解析错乱,出现“找不到指定position”的错误。
注意:DTS不支持MySQL的GTID模式(Global Transaction ID)。如果你的MySQL开启了
gtid_mode=ON,必须先关闭:SET GLOBAL gtid_mode=OFF_PERMISSIVE; SET GLOBAL gtid_mode=OFF;,然后重启MySQL。这是硬性限制,没有绕过方案。
3.2 创建DTS任务的七步关键操作与参数深意
现在打开DTS控制台,一步步来:
第一步:选择源和目标类型
在“新建任务”页,源数据库选“MySQL”,目标数据库选“DM8”。注意下方有个小字提示:“MySQL版本需≥5.7.13”。别点跳过,这是DTS的版本校验开关。
第二步:填写MySQL连接信息
- 主机名/IP:填MySQL服务器内网地址(别填localhost)
- 端口:3306(除非你改过)
- 数据库名:填你要迁移的库名,比如
erp_prod - 用户名/密码:填前面准备好的、有
REPLICATION SLAVE权限的账号 - 关键参数:
Binlog File和Position:这里填空就行,DTS会自动从SHOW MASTER STATUS获取最新位点。但如果你要做断点续传,就填上次中断时的文件名和位置。
第三步:填写DM8连接信息
- 主机名/IP:填DM8服务器内网地址
- 端口:5236(达梦默认)
- 数据库名:填DM8中要创建目标库的名字,比如
erp_dm8(这个库DTS会自动创建,你不用提前建) - 用户名/密码:填前面授权的
dts_user账号 - 关键参数:
Schema Mapping:这里选“源库名→目标库名”,即erp_prod → erp_dm8。DTS会把MySQL的erp_prod.order表,自动映射成DM8的erp_dm8.order表。
第四步:表对象选择
点击“选择对象”,弹出树形列表。这里不是简单勾选,要注意三点:
- 只勾选业务表,绝对不要勾选
mysql、information_schema、performance_schema系统库,DTS会报错; - 如果表名含大写字母(如
OrderDetail),DTS默认转成小写(orderdetail),你可以在“高级设置”里开启“保持大小写”; - 对于有大量BLOB字段的表(如商品图片表),建议单独建一个任务迁移,避免内存溢出。
第五步:字段映射与类型转换
点击“字段映射”标签页。DTS会自动匹配同名字段,但你要重点检查:
- MySQL的
TINYINT(1)→ DM8的BOOLEAN(正确) - MySQL的
DATETIME→ DM8的DATETIME(但DM8默认精度是0,需手动改成6) - MySQL的
JSON→ DM8的CLOB(DTS会自动转,但查询时要用JSON_VALUE函数) - 特别注意:MySQL的
AUTO_INCREMENT主键,在DM8里要对应IDENTITY列。DTS会自动加GENERATED ALWAYS AS IDENTITY,但你要确认DM8版本≥21.03,老版本不支持。
第六步:高级设置里的生死参数
全量同步并发数:默认4,对千张表的库建议调到8,但别超过CPU核数×2;增量同步延迟阈值:默认30秒,意思是如果DM8比MySQL慢30秒以上就告警。生产环境建议设为10秒;错误处理策略:选“跳过错误并继续”,别选“停止任务”——否则一条脏数据就让整个任务挂掉;校验方式:必选“行数校验+MD5校验”,只校验行数会漏掉数据内容差异。
第七步:保存并启动
点“保存”,任务状态变成“已保存”。此时别急着“启动”,先点“校验连接”,确保三个连接都打钩。然后点“预检查”,DTS会跑一轮自动化检测(耗时1-2分钟),检查字符集、权限、网络等。只有预检查全部通过,才能点“启动”。启动后,状态会变成“全量同步中”。
3.3 全量同步阶段的监控与干预技巧
全量同步开始后,你会在任务详情页看到几个关键指标:
- 已同步表数 / 总表数:这是进度条,但别只看这个。有些表数据量小但结构复杂(如带10个索引的订单表),同步慢是正常的。
- 当前表同步速度(行/秒):健康值应该在5000-20000行/秒。如果长期低于1000,说明有瓶颈:可能是MySQL磁盘IO满(
iostat -x 1看%util)、或是DM8的BUFFER参数太小(需调大BUFFER=2000)。 - 内存使用率:DTS Engine的JVM堆内存,默认2G。如果同步大表时内存使用率超90%,要立刻扩容,否则OOM导致任务中断。
我踩过的一个典型坑:某次同步一个200GB的log_history表,DTS卡在99%不动了。查日志发现是java.lang.OutOfMemoryError: Java heap space。解决方案不是重启任务,而是:
- 暂停任务(点“暂停”按钮);
- 修改DTS Engine的
jvm.options文件,把-Xmx从2g改成8g; - 重启DTS Engine服务;
- 在DTS控制台点“恢复”,它会从断点继续,而不是重头来。
实操心得:全量同步期间,绝对禁止在MySQL上执行
OPTIMIZE TABLE或ALTER TABLE ... ENGINE=InnoDB。这些操作会锁表并生成大量binlog,DTS Agent来不及消费,导致内存队列爆满。如果必须优化,等全量结束后,在增量同步阶段再做。
4. 实操过程与核心环节实现:从校验失败到数据一致的完整排障链路
4.1 校验失败的四大高频原因与逐条破解
DTS的“校验”按钮,是迁移过程中最让人紧张的时刻。下面是最常见的四种失败场景,附带真实日志和解决方案:
场景一:行数不一致,但MD5一致
- 现象:校验报告写着“源表1000000行,目标表999999行,MD5校验通过”。
- 原因:MySQL的
COUNT(*)和DM8的COUNT(*)统计逻辑有细微差别。MySQL对NULL值的统计更严格,而DM8在某些版本中对TEXT字段的NULL判断有偏差。这不是数据丢失,是统计口径问题。 - 解法:忽略此警告,改用
SELECT COUNT(1) FROM table_name;在两边分别执行,结果一致即可。DTS的MD5校验才是黄金标准。
场景二:MD5不一致,但行数一致
- 现象:校验报告写着“行数相同,但MD5不匹配”。
- 原因:字段内容被静默修改了。最常见的是
TIMESTAMP字段——MySQL的TIMESTAMP会根据时区自动转换,而DM8的TIMESTAMP是固定UTC。比如MySQL里存2023-01-01 12:00:00,在东八区显示为2023-01-01 12:00:00,但实际存储的是2023-01-01 04:00:00 UTC;DM8直接存2023-01-01 12:00:00,没做时区转换。 - 解法:在DTS的“字段映射”里,把
TIMESTAMP字段的目标类型手动改成DATETIME,并勾选“不进行时区转换”。或者,统一MySQL和DM8的时区为+00:00。
场景三:校验报错“ORA-00942: table or view does not exist”
- 现象:校验时突然报这个Oracle风格的错误(DM8兼容Oracle语法,但这个错误是DTS内部抛的)。
- 原因:DTS在DM8里创建目标表时,用了双引号包裹表名(如
"order"),但后续校验SQL没加引号,导致DM8找不到表。这是因为MySQL的order是关键字,DTS自动加了引号,但校验模块没同步这个逻辑。 - 解法:在DM8里手动执行
SELECT * FROM "order";确认表存在;然后在DTS任务设置里,把表名改成非关键字,比如order_info。
场景四:校验卡住,日志显示“waiting for binlog event”
- 现象:校验按钮一直转圈,DTS Engine日志里反复出现这行。
- 原因:MySQL的binlog被清空了。DTS增量同步依赖binlog,如果MySQL设置了
expire_logs_days=1,而全量同步耗时超过1天,binlog就被删了。 - 解法:立即登录MySQL,执行
SHOW BINARY LOGS;看最新binlog文件。如果文件序号比DTS记录的起始文件小,说明已丢失。此时只能:① 停写MySQL;② 手动FLUSH LOGS生成新binlog;③ 在DTS里重新配置起始位点(用SHOW MASTER STATUS的新值);④ 重启任务。
4.2 增量同步的稳定性保障:如何让DTS扛住业务峰值
全量同步完成后,DTS自动进入增量同步模式。这时真正的考验才开始——你的MySQL每秒写入1000笔订单,DTS能否跟上?
关键指标监控:
- 延迟(Lag):在DTS控制台看“增量延迟(秒)”,理想值<5秒。如果持续>30秒,说明Writer写入慢。
- Event Queue Size:DTS Engine内存中的事件队列长度,健康值<10000。超过50000就要预警。
- Binlog Read Rate:Agent从MySQL读binlog的速度,单位是MB/s。如果这个值远高于
Event Process Rate(事件处理速度),说明处理不过来。
性能调优三板斧:
- 调大DM8的
BUFFER和MAX_BUFFER参数:在DM8的dm.ini文件里,把BUFFER=2000(2GB)、MAX_BUFFER=4000(4GB)。这是提升写入吞吐的最直接方法。 - 开启DTS的批量提交:在任务“高级设置”里,把
批量提交大小从默认1000调到5000。但要注意,值太大可能导致单次事务过长,被DM8的TRANSACTION_TIMEOUT杀掉。 - 分离大事务:如果MySQL里有
UPDATE order SET status='done' WHERE create_time < '2020-01-01'这种扫全表的更新,DTS会把它拆成几千个小事件,拖慢整体速度。建议在业务低峰期执行这类操作,或改用分页更新。
实操心得:我们曾在一个电商大促期间做迁移,MySQL QPS冲到8000。DTS延迟一度飙到120秒。最终解决方案是:临时把DTS Engine的CPU核数从4核扩到16核,并把
Event Process Rate线程数从4调到12。扩容后延迟稳定在3秒内。记住,DTS的瓶颈永远在CPU和内存,不是磁盘。
4.3 平滑切换的终极操作:停写、追平、切流的精确时间点把控
当增量延迟降到0秒并稳定5分钟,就可以准备切换了。这不是一个按钮的事,而是一套精密配合:
步骤一:通知业务方停写MySQL
- 发布维护公告:“XX系统将于今晚23:00-23:05进行数据库切换,请勿在此期间下单”。
- 技术上,在应用层加一个全局开关,把所有写请求返回
503 Service Unavailable。千万别直接停MySQL服务,那样DTS Agent会断连,位点丢失。
步骤二:DTS手动触发“追平”
- 在DTS控制台点“立即追平”,它会强制把内存队列里所有剩余事件刷到DM8。这个过程通常2-3秒。
- 查看日志,确认最后一行是
[INFO] All events processed, lag=0。
步骤三:验证DM8数据完整性
- 执行
SELECT MAX(id) FROM order;在MySQL和DM8上对比,必须完全一致; - 随机抽10条最新订单,用
SELECT * FROM order WHERE id IN (123,456,...);查具体内容,确认金额、时间、状态无差异; - 最关键一步:查DM8的
V$RLOG视图,确认最后一条日志的SCN(系统变更号)和MySQL的SHOW MASTER STATUS输出的Position是否对齐。
步骤四:切流与回滚预案
- 修改应用的数据库连接字符串,把
jdbc:mysql://mysql:3306/erp换成jdbc:dm://dm8:5236/erp_dm8; - 重启应用服务;
- 必须同步做的回滚预案:在MySQL上保留一个
GRANT SELECT ON *.* TO 'readonly_user'@'%';只读账号。如果DM8出问题,5分钟内切回MySQL只读模式,保证业务可查。
注意:切换后第一小时内,要盯紧DM8的
V$SESSIONS视图,看是否有长时间运行的SQL(SECONDS_IN_WAIT > 300)。如果有,立即SP_CLOSE_SESSION杀掉,避免阻塞其他会话。
5. 常见问题与排查技巧实录:那些文档里不会写的血泪经验
5.1 问题速查表:从报错代码反推根因
| 报错代码/日志片段 | 根本原因 | 快速定位命令 | 解决方案 |
|---|---|---|---|
ERROR 1045 (28000): Access denied for user 'dts_agent'@'%' | MySQL账号无REPLICATION SLAVE权限 | SHOW GRANTS FOR 'dts_agent'@'%'; | GRANT REPLICATION SLAVE ON *.* TO 'dts_agent'@'%'; FLUSH PRIVILEGES; |
java.sql.SQLException: ORA-01400: cannot insert NULL into ("ERP_DM8"."ORDER"."CREATED_TIME") | MySQL的created_time字段允许NULL,但DM8建表时没加NULL约束 | DESCRIBE ERP_DM8.ORDER; | 在DTS“字段映射”里,勾选“允许为空” |
DTS Engine OOM: GC overhead limit exceeded | JVM堆内存不足 | jstat -gc <pid>看GC频率 | 修改jvm.options,-Xmx调至物理内存的70% |
binlog position not found in file mysql-bin.000012 | MySQL binlog被清理,起始位点失效 | SHOW BINARY LOGS; | 重新配置起始位点为mysql-bin.000013的0位置 |
DM8 ERROR: [DM9999] Cannot convert string to number | MySQL的VARCHAR字段存了数字字符串(如'123abc'),DM8目标字段是NUMBER | SELECT * FROM order WHERE REGEXP_LIKE(amount, '[^0-9.]'); | 在DTS“字段映射”里,添加清洗规则:REPLACE(amount, '[^0-9.]', '') |
5.2 那些只有老手才知道的避坑技巧
技巧一:用“影子库”预演全链路
别在生产库上直接试。在测试环境搭一套“影子库”:MySQL用生产库的mysqldump --no-data导出结构,再用pt-table-checksum生成10万条模拟数据;DM8用DTS同步后,跑一遍业务SQL压测。我们发现,某次压测中DM8的GROUP BY性能比MySQL慢40%,原因是DM8默认没建HASH GROUP BY索引。解决方案是在DM8里执行ALTER SESSION SET HASH_GROUP_BY=1;。
技巧二:DTS日志的黄金三行
DTS Engine的日志文件dts-engine.log里,最有价值的是这三行:
Starting full load for table [order]—— 全量开始Full load completed for table [order], cost 1245s—— 全量结束,耗时Incremental sync started at position mysql-bin.000012:12345678—— 增量起始位点
如果任务卡住,先搜这三行,看卡在哪一环。比看几百行报错日志高效得多。
技巧三:强制刷新DTS的元数据缓存
有时你改了MySQL表结构(如加了个字段),DTS没感知到,还按旧结构同步。此时不要重启DTS,执行:
# 进入DTS安装目录 cd /opt/dameng/dts ./dtsctl.sh refresh-metadata --source mysql --target dm8 --db erp_prod这个命令会强制DTS重新扫描MySQL的INFORMATION_SCHEMA,更新内部元数据。
技巧四:处理MySQL的ENUM类型
DTS默认把ENUM('a','b','c')转成DM8的VARCHAR(255),但业务代码可能依赖ENUM的序号(如a=1,b=2)。解决方案是在DTS“字段映射”里,手动把目标类型设为NUMBER,并添加转换表达式:CASE WHEN value='a' THEN 1 WHEN value='b' THEN 2 ELSE 3 END。
最后分享一个小技巧:DTS的Web控制台偶尔会假死(页面无响应),别刷新!按
Ctrl+Shift+I打开浏览器开发者工具,切到Console,输入location.reload(true)强制硬刷新。这是DTS前端的一个已知bug,官方说下个版本修复,但我们现在就用这个命令绕过。
我在实际操作中发现,DTS最脆弱的环节不是技术本身,而是人的操作习惯。比如有人喜欢在DTS任务运行时,顺手去DM8里TRUNCATE TABLE清测试数据——这会直接导致校验失败,因为DTS的MD5是基于全量数据算的。后来我们团队定了条铁律:DTS任务运行期间,DM8只读,任何写操作必须经三人签字确认。这条规矩看起来笨,但帮我们避免了三次重大事故。技术工具再强大,也得配上敬畏之心和规范流程,这才是国产化迁移能落地的根本。