简介:dbswitch工具是一款面向数据库开发与运维人员的批量迁移同步工具,主要用来解决源端数据库向目的端数据库的表结构转换与数据批量搬迁问题。它既能完成全量同步,也能针对有主键的表做增量的变更同步,同时在结构层面提供了字段类型、主键约束和建表语句的自动转换,以及基于正则表达式的表名与字段名映射规则,因此在跨库迁移、异构数据库互相同步、数据仓库初始化等真实场景中都能派上用场。压缩包内一共包含五百零六个文件,其中有三百零六个核心源码文件、三十六个前端脚本、二十五个页面组件、二十个依赖包、十五个配置描述、十三个数据库脚本、九个命令脚本等,整体大小约为九十九点零九兆字节,工程结构规整,目录划分清楚,便于进行二次开发或直接部署。目前已有七百九十人学习下载,适合中高级数据库工程师、平台开发人员以及有数据同步需求的团队参考借鉴。通过阅读源码,不仅能深入理解分批读取、插入写入或复制写入以及增量数据计算的整体设计思路,还能获得可执行的构建脚本、启动命令与容器化部署描述等工程化配套内容,从而更快地应用到自己的项目中去。
1. 数据库迁移同步,别再靠导出导入硬扛:dbswitch干了什么
接手过迁移任务的人都有过这种经历:源库是MySQL,目标库是达梦或者人大金仓,两边字段类型对不上,主键索引要重排,几十张表靠Navicat导SQL脚本,导完对不上行数还要半夜爬起来排查。dbswitch就是冲着这个场景来的,它把结构迁移、全量数据同步、有主键表的增量变更同步三件事合并成一条流水线,用配置文件声明源端和目标端,跑一遍就能拿到建表SQL和导入数据的结果。适合做国产数据库替换、异构库迁库、测试环境数据刷新这类活的人,读过JDBC、见过迁库翻车现场的人上手最快。下面按结构迁移、全量同步、增量同步、常见坑、上生产前的验证这个顺序拆开讲。
2. 结构迁移:类型映射、正则改表名、建表SQL的生成逻辑
结构迁移是整条迁移链路的第一关,也是报错最集中的一关。dbswitch的处理思路是:先去源端抓元数据,再做类型映射,最后生成目标端建表语句。搞清楚这条链路,后面所有配置都好理解。
2.1 从元数据抓取到目标建表语句,一条链路是怎么走的
dbswitch基于JDBC做元数据抓取,支持的源端类型包括MySQL、Oracle、PostgreSQL、SQL Server、达梦、人大金仓、GBase这类常见库。它抓的不只是表清单,还包括字段名、字段类型、精度、默认值、注释、主键、唯一索引这些明细,抓完统一封装成内部结构,再用目标端的方言生成DDL。
它生成建表SQL时不是简单地字符串拼接,而是基于一套模板化的DDL生成逻辑。比如同样一个字符串字段,在MySQL里是varchar(255),到达梦里可能建议转成varchar(255),到PostgreSQL里是character varying(255)。类型映射表决定了这个转换关系,每个目标端都有独立映射配置。字段注释、主键约束、唯一索引这些DDL片段也是分开生成再拼起来。
提示:结构迁移不等于只能建表和索引,默认值、自增序列、字段注释这类元信息要不要带过来,取决于配置开关。默认情况下dbswitch会把常用约束一起带过去,但像触发器、视图、存储过程这类对象不在转换范围内,需要另想办法。
2.2 配置实例:从MySQL迁到达梦的映射规则与正则
实际配置一般走YAML文件或管理界面的连接配置。下面是一个从MySQL迁到达梦时的典型配置片段,标注了每个配置项的作用:
source: db-type: mysql jdbc-url: jdbc:mysql://192.168.1.10:3306/business_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: dbuser password: dbpass schema-name: business_db target: db-type: dm jdbc-url: jdbc:dm://192.168.1.20:5236/DMSERVER username: dbuser password: dbpass schema-name: TARGET_SCHEMA mapper: # 表名映射,正则一:只对源表名前缀为 t_ 的生效 table-name-regex: "^t_(.*)$" table-name-replacement: "biz_" # 字段名映射,把 src_ 开头的字段名改成 dst_ 开头 column-name-regex: "^src_(.*)$" column-name-replacement: "dst_"这个配置的逻辑是:所有源表名以t_开头的表,迁移到目标端时改成biz_前缀,同时把字段名从src_开头替换成dst_开头。所谓基于正则表达式转换,本质就是用Java的Pattern和Matcher做分组替换。比如^t_(.*)$匹配t_order后拿到order这个分组,拼接成biz_order。如果不想转换任何表名,regex写一个不可能匹配的表达式即可。
字段映射要注意作用范围,规则一旦配置就是全局生效的,不是只对个别表生效。我一般建议迁移前先在测试库上用小规模表集跑一遍DDL生成,看看有没有表被映射规则意外改掉名字。
2.3 类型转换兜底与字段长度带来的隐藏问题
类型映射不是全部一一对应的,总有映射表里不存在的类型。dbswitch对这种情况有一个兜底逻辑,映射不到的字段类型会走一个默认转换分支,通常转成目标端的varchar或text。这个兜底带来的风险是:字段语义变了但悄无声息。
最常见的实际问题是精度丢失。MySQL的decimal(10,2)迁到达梦没问题,但如果是decimal(38,10)这种超高精度,部分目标端的numeric精度上限不足,DDL生成就会直接失败或者被截断。另一个高频问题是无符号整数类型,MySQL的bigint unsigned在很多目标端没有对应类型,dbswitch会转成numeric(20)这一类带精度的类型,需要在映射配置里手动确认。
长度问题也在这一阶段暴露。MySQL的varchar(500)在有些目标端可能不支持这么长的varchar,需要转成clob/text。dbswitch在映射表里内置了这类规则,但规则只覆盖常见场景,碰到自定义类型比如MySQL的enum、set,以及PG的数组类型时,还是要提前列一张源端类型清单逐项核对。
3. 全量数据同步:分批读取与insert/copy双写入模式的参数调优
结构迁移完成后,数据同步才是真正耗时的环节。dbswitch采用JDBC分批次读取源端数据,再按目标端的写入方式分批提交。这里的核心参数是批次大小、读取方向、写入模式。
3.1 JDBC分批次读取的参数怎么设才能不卡不爆
分批次读取不是简单的一页一页翻,而是依赖源端的主键或唯一索引做范围切片。dbswitch的做法是:对每张表先查询出主键列,然后按主键范围分成多个批次,每个批次用独立的WHERE条件查询。这样每个批次的数据量可控,也便于失败后重跑指定批次。
批次大小的设置直接决定内存占用和查询性能。比如一张亿级订单表,主键id是bigint自增,如果把每批数据量设为5000行,一批次查询的内存压力就是5000行×行的平均宽度。行的平均宽度大(比如带text字段),内存峰值会翻倍。我一般给批次大小设置留一个动态判断,宽表用5000,窄表用10000到20000。
JDBC的fetchSize参数在这里很关键。默认情况下MySQL驱动会一次性把所有结果拉到客户端内存,设置fetchSize后才能让游标分批拉取。实际效果和驱动实现有关,MySQL需要配合useCursorFetch=true开启服务端游标,PostgreSQL则直接用游标机制。碰到大批量同步内存翻车时,优先检查fetchSize有没有在连接参数里生效。
3.2 insert和copy两种写入怎么选
dbswitch写入目标端时有两条路:逐行拼装insert语句,或者用目标端的批量导入工具走copy通道。insert模式通用性好,什么库都能跑,但速度上限受限于网络往返和SQL解析开销。copy模式是PostgreSQL、达梦这类数据库原生支持的高效导入方式,速度可以差出一个数量级。
-- copy模式底层走的语句形态(PostgreSQL目标端示意) COPY target_schema.t_order (id, order_no, buyer_name, amount, create_time) FROM STDIN WITH (FORMAT csv, DELIMITER ',', NULL 'NULL');注意:copy模式对字段顺序敏感,源端查询字段的顺序必须和目标端表结构一致。dbswitch在生成copy语句时是按字段映射顺序排列的,如果中途调整字段映射,目标端表结构没重建就直接同步数据,会出现字段错位。
insert模式的优势是有兼容性兜底,适合目标端不支持批量导入协议的场景。Oracle、SQL Server这类库一般走insert批量提交。提交频率由事务批次大小控制,比如每5000行提交一次事务,这个值和读取批次大小保持同步,避免提交过于频繁导致日志暴涨。
3.3 任务调度与断点续跑
全量同步的调度一般由外部任务编排驱动。dbswitch本身提供命令行入口和批处理脚本,比如项目包里常见的startup.cmd、datasync.cmd就是这类启动入口。怎么理解这几个脚本的分工:startup是启动后台服务或控制台,datasync是执行同步任务,version可以用于排查版本差异。
同步任务的幂等性很重要。同一张表重复跑全量同步,如果目标表已有数据,会先按主键做冲突判断。dbswitch的常见行为是:目标表存在主键时,重复数据会做更新或跳过,具体取决于配置的写入策略;没有主键的表重复跑,可能出现重复数据。所以批量迁移前,建议先把目标端表清空或者做一次truncate再全量跑。
断点续跑这块,dbswitch没有做内置的断点续传机制,而是通过批次粒度重跑来近似实现。跑挂了大不了从失败批次继续,已经完成的批次可以跳过。实际运维时我习惯在批处理外层套一层记录脚本,把每张表的完成状态写入一张状态表,重跑时按状态表决定跳过还是补跑。
4. 增量变更同步:Change Data Calculate的实现边界与配置要点
增量同步是dbswitch工具最有争议也最值得关注的部分。摘要里写清楚了:支持有主键表的增量变更同步,变化数据计算叫Change Data Calculate。这里要泼一盆冷水:它不是binlog级的实时同步,而是一种基于查询比对的准实时增量计算,理解了这个边界才能用对。
4.1 增量同步的前提:主键表与变更计算思路
Change Data Calculate的思路是在有主键的前提下,周期性比较源端和目标端的数据,计算新增、修改、删除三类变更,然后把这些变更应用到目标端。和DataX、Canal那类基于日志解析的方案不同,它不读取binlog,而是靠增量查询条件去源端捞变更数据。
对于新增和修改,常见做法是在源端表上找一个时间字段(如update_time)作为增量水位,同步时只抓取水位之后变更的数据。对于删除,如果有逻辑删除标志字段也可以靠条件过滤,但物理删除的检测就比较麻烦,通常需要目标端反向比对或者依赖额外的变更表。dbswitch在这个环节也是围绕主键做变更识别:以主键为准判断哪些是新增行,哪些是修改行。
-- 增量计算的一个典型处理逻辑:源端变更集合与目标端现有数据做对比 SELECT s.id, s.order_no, s.update_time FROM source_biz.t_order s WHERE s.update_time > ? AND s.update_time <= ? AND EXISTS (SELECT 1 FROM target_biz.t_order t WHERE t.id = s.id AND (t.order_no <> s.order_no OR t.update_time <> s.update_time));上面这条SQL表达的意思是:抓取一段时间内发生变更的源端记录,并且只捞那些和目标端当前数据不一致的行。这样一个批处理周期内,修改量是收敛的,不会每轮全表扫描。对应的参数是两个时间水位:批次开始时间和批次结束时间。批次的长度决定了同步延迟,比如每5分钟一个批次,延迟就是5分钟。
4.2 增量任务的周期调度与字段映射
增量同步在做配置时,有几个点必须设置清楚。一是增量时间字段,一般选择update_time或modify_time,选错字段会导致漏数据或者重复同步;二是主键列,必须在任务配置里显式声明,没有主键的表在增量模式下不会进入同步清单;三是时间字段的类型,datetime、timestamp、bigint三种类型在参数格式上不一样,dbswitch按源库方言解析时间条件。
周期调度的常见做法是配一个定时任务,每5到10分钟跑一次增量批次。批次区间用左开右闭原则避免重复,比如上次跑到了10:00,这次跑(10:00, 10:05]这个区间。由于应用代码里处理时间字段有时会少算毫秒,边界数据偶发重复是正常现象,目标端主键的存在会兜底,重复数据会转化为更新覆盖。
增量同步还有一种我常用的变体:第一次全量同步后,每天凌晨跑一次增量批次,把前一天变更数据同步过去。这个场景不需要很高的实时性,但能减轻源库压力。无论用哪种节奏,前提都是源端表确实存在可靠的时间字段,而且更新记录时该字段会被正确刷新。
4.3 千万级数据的性能边界与验证策略
摘要里特别提到千万级以上数据量的性能尚需在生产环境验证,这句话不是推诿,而是这类查询式增量的天然短板。增量计算依赖于在源端做时间范围+主键条件查询,源端表的时间字段如果没有索引,每轮批次都是一次全表扫描,数据量大了以后源库IO会被拖住。
面对千万级以上的表,我的建议是先验证三件事:时间字段有没有索引、一轮批次扫描的数据量、目标端批量更新的效率。在此基础上还要考虑增量批次与业务高峰的重叠,同步任务尽量调度到业务低谷期执行,或者把每个任务拆小,按表或按分片独立调度。
提示:如果业务对增量实时性要求是秒级,或者源表连可靠的时间字段都没有,dbswitch不是合适的选择。这种场景优先考虑基于日志解析的方案,或者让业务侧改造增加变更流水表。工具边界要先划清楚,再谈落地。
5. 实战避坑:迁移失败最常见的五类现场与处理办法
这一章内容来自我拆解工具时反复折腾的血泪经验。每条都是实际能撞上的问题,按现场现象、原因、解决办法三部分写。
5.1 类型映射缺失导致建表异常
现场现象:结构迁移跑到一半报错,日志里提示未知类型,或者生成的DDL有明显异常。比如MySQL的enum类型迁到某些目标端时报Unknown data type,或者mediumtext被错误映射成短文本类型。原因分析:dbswitch内置的类型映射表覆盖常见类型,但enum、set、bit、year这类MySQL特有类型或者PG的jsonb、数组类型,在部分目标端映射配置里缺失,走了兜底分支或者直接抛异常。解决办法:先在源端跑一遍元数据查询,把所有非通用类型列出来,手动补充或调整映射关系,把enum改成varchar、jsonb改成text、bit改成smallint。补充完映射后再跑结构迁移,不要在报错后盲目改目标端表结构。
5.2 正则表达式命中过广把表名改乱
现场现象:同步完成后目标端出现一批奇怪的表名,比如表名重复、前缀被错误替换、部分表名迁移后完全偏离预期。原因分析:正则规则是全局生效的,如果表名映射规则写得太宽,比如^(.*)$直接匹配所有表,那么每张表都被改一遍名字。另一个常见误点是对应的替换逻辑用了错误的分组引用,比如$1写成了$0,导致前缀替换错误。解决办法:先在源端把全部表名拉出来过一遍匹配测试,确认规则只命中预期范围内的表。我一般是在测试环境设置一个只包含两张表的小任务,先跑通再放全量任务。
5.3 大批量同步时OOM与锁等待
现场现象:同步任务跑了一段时间后进程崩溃,报OutOfMemoryError,或者目标端数据库的锁等待飙升,插入速度越来越慢直到超时。原因分析:批次大小设置过大,加上JDBC驱动把结果集一次性拉入内存,堆内存被打满;写入侧事务批次过大也会让目标库锁竞争加剧,大批量写入和业务查询互锁。解决办法:把读取批次调小,比如从20000降到5000,并在JDBC连接串上开启服务端游标;写入侧把事务提交频率调高,让每个事务只包含5000到8000行;同时错峰运行同步任务,避开业务高峰期。
5.4 没有主键的表在增量模式下静默跳过
现场现象:增量同步任务跑完,日志显示执行成功,但某些表的数据始终没有更新,或者运行日志里完全没有这些表的身影。原因分析:dbswitch的增量变更计算以主键为前提,没有主键的表在增量模式下直接不进入同步清单,而且这部分日志可能只打印在debug级别,不细看就发现不了。解决办法:配置增量任务前先核对表清单,把无主键表单独圈出来改走全量同步策略,或者给源表补充一个由业务字段组合成的唯一键。注意组合唯一键要确保不会出现null值,否则主键判断会失效。
5.5 字符集与字段长度引发的数据截断
现场现象:同步完成后目标端数据出现中文乱码,或者varchar字段内容被截断了尾部字符,行数比对正确但数据质量校验不通过。原因分析:源端连接串和JDBC驱动之间的字符集没有对齐,源库是utf8mb4但连接串只指定了utf8,一些生僻字会被替换成问号;另一个原因是源端varchar长度超长,目标端映射后的varchar长度不足,写入时在目标库被自动截断。解决办法:统一把源端连接串参数补全,比如characterEncoding=utf8改为characterEncoding=utf8mb4,同时在结构迁移完成后抽查几张表,对比字段长度和字符集设置,确认没有问题再跑数据同步。
6. 上生产前先做这三件事:结果校验与扩展自定义类型映射
结构调整完、数据也同步完了,最怕的是两边数据看着差不多、实际有些字段静默丢了。我每次拆这类工具,收尾都会强制走一遍校验流程。三件事:行数校验、抽样hash校验、自定义映射扩展。
行数校验是最基础的。对每张表分别统计源端和目标端的行数,两张表一组做对比。语句不复杂:
SELECT 'source_orders' AS tb_name, COUNT(*) AS row_cnt FROM source_biz.t_order UNION ALL SELECT 'target_orders', COUNT(*) FROM target_biz.t_order;这个校验对全量同步有效,对增量同步则要在批次跑完后归集统计。行数一致不代表数据一致,所以还要做抽样hash校验。常见做法是对每张表抽取若干主键范围内的行,把每个字段的值拼接后做hash,再对比两边的hash集合。如果某些字段类型在异构库间有精度差异,比如浮点数和小数的表示不同,hash对比会误报,处理方式是把这类字段先做格式化再拼接。
自定义类型映射的扩展我用过一次就离不开。dbswitch的核心映射表写在配置里,但碰到业务私有的自定义类型时,直接在映射文件中追加一条规则即可。比如源端有个自定义的phone_number类型,就把它映射成目标端的varchar(20),映射配置走的是优先级较高的用户配置路径,不会覆盖内置默认映射。扩展之后必须跑一次结构迁移验证,重点看生成的DDL是否满足目标库的语法要求。
最后放一个流量镜像的验证习惯:找一个业务低峰时段,把源端的一张核心表做一次全量重跑,和目标端做完整比对。我之前有过一次迁移后浮点字段精度差一位导致对账不平的事故,从那以后我每次上线前都强制走一遍“先结构、再全量、抽验hash、再放增量”的流程,字段类型差异也提前列清单核对。这个顺序救过我很多次,希望帮到你。
本文还有配套的精品资源,点击获取