
简介dbswitch 工具是一套面向数据库迁移与同步场景的数据库开发工具包适合需要完成异构数据库批量迁移、结构转换及增量同步的开发者与运维工程师。压缩包共506个文件约99.09MB主体为306个Java源码文件并包含20个jar依赖、Vue/JS前端页面、SQL脚本、XML配置、Shell与YAML部署脚本等附有启动脚本、Dockerfile与示例SQL便于快速搭建迁移环境和二次改造。功能上它支持字段类型、主键、建表语句的转换并生成对应SQL可基于正则表达式实现表名与字段名的映射数据同步侧基于JDBC分批读取源库数据以insert/copy方式写入目的库并针对有主键表提供增量变更同步Change Data Calculate能力满足全量与增量两类迁移需求。已有790人学习/下载适合中高级数据库开发者在真实迁移项目中借鉴其模块拆分、映射配置与同步实现思路。1. 数据库迁移同步为什么 dbswitch 值得放进你的工具箱做数据迁移的人早晚会遇到这个场景源端 Oracle 要迁到 PostgreSQL或者 MySQL 要搬到国产库几十张表、上亿行数据客户只给一个窗口期。手写脚本能跑但每张表都要调类型、处理字符集、断点续传折腾完能写一本小说。dbswitch 这个数据库迁移同步工具就是为批量迁移和常态化同步设计的把结构迁移、全量同步、增量同步收进一套配置和命令里。适合手里有迁移项目、或者正在做跨库同步的工程师——先花半天跑通一个最小任务再决定要不要接生产。2. 先搞懂 dbswitch 的同步机制结构迁移、数据迁移与日志表选同步工具之前我习惯先问三个问题它是基于日志解析实时捕获还是周期性拉取结构怎么过去增量靠什么识别这三个问题的答案决定它能顶住多大的数据量、能保证什么样的一致性。dbswitch 的定位属于批量同步这一派理解了这个前提后面调配置文件才不会懵。2.1 dbswitch 做的是“批量迁移同步”不是 CDC很多人一听到增量同步就想到 binlog 或 WAL认为 dbswitch 也是实时 CDC 工具。实际不是。dbswitch 走的是 JDBC 轮询加批处理到点就跑一次任务按配置把源端表的数据读出来写入目的端表。它的增量是靠源表里某个时间戳字段来判断哪些数据变了而不是靠解析数据库的变更日志。批量同步和 CDC 的差别直接决定使用场景。CDC 适合实时数仓、微服务事件流要求秒级延迟批量同步适合 T1 数仓、临时迁移窗口、定时同步在线业务库的只读副本。dbswitch 的优势不在延迟而在“全量加增量”统一配置、统一执行。同一个工具能完成迁移项目里最繁琐的结构同步和数据搬迁不用像手写方案那样在多个脚本之间来回切换。结构迁移部分dbswitch 会读源端的元数据包括字段名、类型、长度、精度、默认值、注释和索引再转换成目的端数据库的方言生成建表语句。这一步听起来简单实际非常容易翻车。比如 Oracle 的 NUMBER 到 PostgreSQL 应该映射成 numeric 还是 bigintMySQL 的 tinyint(1) 要不要转成 boolean每个数据库组合的标准答案都不一样。dbswitch 内置的映射规则能解决大部分默认情况但特殊场景仍然需要人工微调。还有一个容易被忽略的点dbswitch 的结构迁移默认是一次性的。如果源端表结构后续发生变化比如新增了字段同步任务不会像 CDC 工具那样自动捕获 DDL 并同步到目的端。生产环境里我一般会在每次全量任务前人工比对一次源端和目的端的表结构差异把新增字段手工补到目的表再继续跑数据同步。2.2 源端与目的端怎么建立映射配置文件的四个关键部分dbswitch 的配置文件是 YAML核心包含四块源端连接、目的端连接、表映射、同步参数。我把一个最小配置拆开解释source: url: jdbc:oracle:thin://127.0.0.1:1521/ORCL username: src_user password: src_pwd driver: oracle.jdbc.OracleDriver target: url: jdbc:postgresql://127.0.0.1:5432/migration_db username: dst_user password: dst_pwd driver: org.postgresql.Driver tables: - srcTable: SRC_SCHEMA.ORDERS dstTable: public.orders mode: full primaryKey: id sync: batchSize: 1000 fetchSize: 500 parallel: 4 createTable: true这里 source 和 target 就是两套 JDBC 连接参数。driver 一定要对应真实数据库版本Oracle 驱动放错 jar 会在启动时直接报连接失败。tables 列表可以写多张表每张表单独指定源表、目的表、同步模式和主键。sync 里的 batchSize 控制目的端每次批量写入的条数fetchSize 控制源端每次游标取回的行数parallel 控制表级并行度。配置里的主键不是必须的但全量同步建议写上。理由有两个一是在写入阶段可以用主键判断是否要做更新而不是插入二是在跑完核对数据时能按主键逐行比对。如果不填主键dbswitch 会退化成纯 insert源端更新过的数据在目的端不会被覆盖整个同步结果就是错的。参数别照抄要按表的数据特征调。几万行的小表 batchSize 设 500 都能跑完千万级的大表fetchSize 太大会让源库的 JDBC 驱动一次性物化大量结果集内存被瞬间占满。生产环境我一般先拿最大的三张表做基准测试再定参数不会拿一套参数套所有同步任务。2.3 增量同步的事实标准用时间戳字段圈出变更区间增量同步是 dbswitch 这类工具的重点也是最容易写出分歧的地方。它的玩法是给每张需要增量的表指定一个增量字段通常是 update_time、modified_time 这类能被业务更新的时间戳每次同步时把大于“上次同步位点”的行拉出来写进目的端然后更新位点。位点保存在哪里最简单的是每次任务结束后把同步时间写到一个状态表或者记录文件里。配置层面会有类似incrColumn、lastTime的选项原理都一样上一次任务跑完的时间就是下一次任务的起点。这里有个关键点增量条件要写成update_time lastTime AND update_time currentTime也就是左闭右开。原因很现实一个任务从启动到结束可能跨分钟如果直接取当前时间作为终点跑的过程中刚好更新的数据可能没读全下次又会因为起点时间没变而漏掉。如果业务表没有统一的变更时间字段也不要硬加增量。常见做法是在源端建一个触发器维护 update_time或者接受全量重刷。还有一种折中如果表量大但每天变化行数少可以给源表加一个针对变更时间的索引查询效率提升明显。增量同步不是银弹没有可靠的时间戳字段建议老老实实每天跑全量。这里要顺带提一下 DataX。很多人在选型时会用 DataX 和 dbswitch 做对比。DataX 在异构数据源间的全量吞吐表现不错但增量同步、表结构迁移、主键幂等这些能力需要自己额外开发。dbswitch 的价值在于把“迁移同步”这件事做成一个带配置和执行入口的整体尤其适合结构复杂、表数量多的批量迁移场景。如果你的核心诉求只是“把表原样搬走以后还要定期追增量”dbswitch 的思路更省事。3. 用 dbswitch 在本地跑通全量同步最小配置与三条命令机制讲清楚了下面进入能复现的部分。我会带你把一个最简单的 MySQL 到 PostgreSQL 的全量同步跑通。这个最小任务能验证驱动、配置、执行链路是否正常之后再往里面加增量字段和调度。3.1 准备好环境与驱动第一步先看 classpathdbswitch 发布包解压后一般包含 bin、lib、conf 三个目录。JDK 版本决定你能不能直接跑起来建议用确认过的长期支持版本不要用太新的版本去测老项目否则会遇到类加载兼容问题。启动前先确认两件事java -version ls -lh lib/ | grep -E mysql|postgresql如果 lib 下没有对应的 JDBC 驱动需要把对应驱动 jar 放进去。这一步看着简单实际是很多新手翻车的第一站。驱动版本要和数据库服务端匹配比如 MySQL 8 的认证插件是 caching_sha2_password老驱动连不上的现象就是登录成功但查询报错。版本匹配的另一个坑是时区参数。MySQL 连接串建议显式带serverTimezoneAsia/Shanghai否则驱动默认用服务器时区时间字段写进目的端可能差 8 小时。PostgreSQL 连接串没有这个问题但要注意currentSchema参数避免默认连到 public 之外的 schema。如果你是在一台没有图形界面的服务器上操作建议先跑一次不带配置的命令确认主程序能正常启动。启动日志里如果出现找不到主类或者配置解析失败多半是发布包解压不完整或者 JDK 版本不对。这一步排查成本低别省。3.2 编写全量同步的配置文件在第 2.2 节的基础上把 source 换成 MySQL、target 换成 PostgreSQLtables 里写一张真实的业务表。下面是我常用的最小配置source: url: jdbc:mysql://127.0.0.1:3306/shop_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: migrator password: migrator_pwd driver: com.mysql.cj.jdbc.Driver target: url: jdbc:postgresql://127.0.0.1:5432/warehouse?currentSchemaods username: etl_user password: etl_pwd driver: org.postgresql.Driver tables: - srcTable: shop_db.orders dstTable: ods.orders mode: full primaryKey: order_id sync: batchSize: 1000 fetchSize: 500 parallel: 2 createTable: true这里源表是shop_db.orders目的表是ods.orders。createTable: true表示目的端表不存在时自动按源表结构建表如果目的表已经存在并且结构完全一致也可以设为 false。建议第一次跑保留 true让工具生成结构随后人工检查一遍字段类型映射是否符合预期。mode: full是全量模式表示每次把源表整表数据同步到目的端。对于存量数据迁移这个模式最直接。执行前先统计源表行数方便跑完核对。source 连接串里的useUnicode和characterEncoding是 MySQL 写入中文数据时的关键参数缺了容易乱码。这里还有一个容易被忽略的细节primaryKey: order_id不只是用来做数据更新的它还会影响同步失败后的重跑行为。如果目的端表已经有主键建议在配置里保持同一列名。这样任务重复执行时同一行数据只会被更新覆盖不会产生重复记录。3.3 执行同步并核对行数配置文件保存成sync-full.yml放到 conf 目录下。执行命令一般是这样cd /opt/dbswitch ./bin/dbswitch -c conf/sync-full.yml启动后观察日志确认连接成功、表结构创建成功、每一批数据写入完成。跑完后在目的端核对数据select count(*) from ods.orders;再把源端行数列出来对比select count(*) from shop_db.orders;行数一致只是第一关还应该抽查几列内容尤其是时间字段和金额字段。时间字段要确认时区没有偏移金额字段要确认精度没有丢。MySQL 的 decimal(10,2) 到了 PostgreSQL 应该是 numeric(10,2)如果被映射成 double precision累计求和会出现微小误差这在财务场景是不能接受的。全量同步跑通后这个最小任务就是你的基线。后续加表、加增量、加调度都在这套配置上扩展不会推翻重来。很多项目的迁移之所以失败不是工具不行而是第一次全量没做核对就急着扩表最后数据对不上又要从头再来。4. 把增量同步接进来时间戳字段、任务编排与断点续跑全量解决了存量增量解决的是接下来怎么办。把增量接进来需要做三件事选对时间戳字段、让任务按固定周期跑、把同步位点管好避免重复和丢失。这一章对应到生产里最常见的数据库同步软件诉求——期望它像主从复制那样自动追数据但批量同步工具的逻辑和主从复制完全不同位点管理要靠自己落地。4.1 时间戳字段怎么选配置里怎么写不是所有时间字段都适合做增量。创建时间是死值只能代表插入更新时间才能代表修改。在订单表里常用的增量字段是update_time如果源表没有就要推动业务侧加上。配置时在 tables 里增加增量字段tables: - srcTable: shop_db.orders dstTable: ods.orders mode: incr primaryKey: order_id incrColumn: update_time lastTime: 2024-05-01 00:00:00mode: incr表示增量同步incrColumn是增量依据字段lastTime是首次同步的起点。第一次跑增量前要确认 lastTime 设置在存量数据迁移完成的时间点否则会造成历史数据重复同步或缺失。我一般把存量全量跑完后记录一下当时的数据库时间再把它填进增量配置。增量字段的选择有几个硬性要求字段类型必须是 datetime/timestamp 这类可比较的时间类型字段值必须能反映业务变更且字段上最好有索引。如果源表有几十万行且 update_time 没索引每次增量同步都会全表扫描数据库压力反而比全量更大。还有一个常见误解以为增量字段只要有时间类型就行。实际上如果这个时间戳是应用侧写入的网络时间不回拨还好一旦源数据库和应用服务器时间不一致增量区间可能永远不触发。所以选字段时我还会做一次抽样看字段的最大值是不是接近当前时间确认它是真的“活的”时间戳。4.2 用 shell 脚本做定时增量dbswitch 本身负责同步定时任务交给 cron。写一个简单的 shell 脚本包住执行命令并把每次运行时间记录下来作为下次增量的起点。先把逻辑跑起来再谈复杂编排。#!/bin/bash # 增量同步启动脚本 export JAVA_HOME/opt/java/jdk17 LAST_FILE/opt/dbswitch/run/last_time.txt if [ -f $LAST_FILE ]; then LAST_TIME$(cat $LAST_FILE) else LAST_TIME2024-05-01 00:00:00 fi # 把本次起点写进配置然后执行同步 sed -i s/lastTime:.*/lastTime: \$LAST_TIME\/ /opt/dbswitch/conf/sync-incr.yml /opt/dbswitch/bin/dbswitch -c /opt/dbswitch/conf/sync-incr.yml # 同步成功后再更新时间位点这一步不能提前 echo $(date %Y-%m-%d %H:%M:%S) $LAST_FILE这个脚本的关键在于位点更新放在同步成功之后。如果在同步之前就更新时间位点一旦执行中途失败下次会漏掉整个失败区间的数据。反过来同步成功却没有更新位点下次会把窗口内的数据再拉一遍靠主键去重还能接受但目的端表有主键才扛得住重复写入。cron 表达式按业务需求定。T1 场景通常凌晨跑一次近似实时的场景可以每 5 分钟跑一次。每次运行的时长为必须考虑的因素——如果一轮同步要跑 10 分钟而周期是 5 分钟任务会堆积这时候要么缩短批量大小要么接受延迟大于周期。这里要提醒一句sed 改配置的做法在单机单任务下没问题但如果同一份配置文件被多个同步任务共用多个进程同时改文件会造成配置串位。多任务场景最好给每个任务单独一份配置文件脚本里通过传参指定不同的配置路径。4.3 断点续跑的落地记录位点与重放上面脚本里用文件记录位点适合单机部署。如果 dbswitch 跑在容器或者多节点环境文件就不靠谱了我建议把位点挪到数据库里。简化的实现是建一张同步状态表任务启动时读状态任务结束时写回create table sync_state ( job_name varchar(100) primary key, last_time timestamp not null );每次任务开始时执行select last_time from sync_state where job_nameorders_incr结束时执行update sync_state set last_time:current_time where job_nameorders_incr。相比文件方案数据库位点能支撑多个执行节点通过分布式锁竞争同一个任务避免两个节点同时跑同一张表。断点续跑还有一个容易忽略的点如果源端表有删除操作时间戳增量永远发现不了删除。这也是批量同步工具和主从复制的本质区别之一。生产上遇到需要同步删除的场景常见做法是在源表加一个删除标记列业务删除时改成逻辑删除并更新时间戳这样增量拉取时能看到那行并同步删除标记。位点保存方式再往下想一层就是事务边界问题。dbswitch 按批提交时一批数据写到一半失败重跑时会从同一个位点开始已经写进去的那批会被再次覆盖更新。这种重复写入能不能接受要看目的端表有没有主键。没有主键的话建议把 batchSize 调小让单批失败影响的行数更可控。5. 避坑指南dbswitch 迁移同步中常见的 5 个翻车现场跑通最小任务不难难的是让任务在真实数据和生产环境里稳定跑。下面这五条是我在多次数据库迁移同步项目中积累的踩坑记录现象、原因、解决一条一条写清楚每条都能省你半天排查时间。5.1 现象目的端表结构建好了数据却一行没进有一次同步任务日志里明确显示表结构创建成功但跑完后目的端表是空的。先怀疑数据源查询直接在源端数了一下行数没问题怀疑驱动手动用 JDBC 查也能查到。问题出在配置里的表名源表名写的是 shop_db.orders但 MySQL 里 schema 名和表名的大小写敏感性受操作系统影响。在 Windows 上 MySQL 表名不区分大小写Linux 上区分结果配置里的表名和实际表名大小写不一致查询返回空结果。解决方法是保持源表名与数据库实际定义完全一致先用show tables查清真实大小写再填进配置。这个坑在数据库同步软件里非常隐蔽因为日志不会把查到的表名列出来排错时容易被误认为是权限问题。5.2 现象增量同步重复导入主键冲突增量任务跑第二次时日志刷出大量主键冲突现象是目的端表里有重复数据或者任务直接失败。原因通常是位点没有正确落库或者增量条件写成了闭区间update_time lastTime AND update_time currentTime。同一个时间戳上的多条数据会被连续两次任务重复拉取因为没有主键的更新逻辑直接走 insert 必然冲突。解决办法是两件一是把增量区间改成左闭右开终点严格小于当前任务时间二是给目的端表建主键并确认同步逻辑对主键冲突走更新而不是报错。我还会给同步任务加一个检查每次执行前对比源端最后一条增量时间防止位点回拨导致大范围重放。5.3 现象全量同步跑到一半连接断开千万级大表全量同步到一半目的端连接断开源端游标失效。原因有几个一是 fetchSize 设置太大源端数据库临时占用大量内存触发内存溢出二是目的端批量写入的 batchSize 太大网络抖动时单次事务重放时间太长三是源端数据库长时间查询触发空闲超时。解决方向是调小 fetchSize 和 batchSize并给 JDBC 连接串加上超时参数。如果不要求大事务原子性建议把同步模式设成按批提交让一批失败只重放一批不用整表重来。生产环境我习惯先跑 10 万行数据做压测观察连接稳定性再跑全量。5.4 现象时间戳字段有 Null增量漏数据源表时间戳字段不是非空约束部分历史数据该字段为 Null。增量同步判断update_time lastTime时Null 不参与任何比较这些行永远拉不出来。这是时间戳增量方案的通病和数据量无关。解决思路有两个如果业务能接受把源表的 Null 值刷成一个历史时间比如 1970-01-01保证参与比较如果源表不能改必须让同步任务对 Null 字段做兜底例如把增量条件改成update_time lastTime OR update_time IS NULL再配合主键幂等处理。更好的方案是推动业务侧给所有新写入的数据强制填充时间字段。5.5 现象字符集不一致导致乱码目的端表里的中文显示成乱码原因通常是三层里的任意一层源端 JDBC 连接没指定字符集、目的端数据库默认字符集不是 UTF-8、配置文件本身存成了 GBK。这个问题在全量同步最容易看到因为历史数据的字符集混乱会在批量写入时集中暴露。解决方法是统一链路字符集MySQL 连接串带useUnicodetruecharacterEncodingutf8PostgreSQL 目的库初始化时用 UTF8配置文件统一保存为 UTF-8 编码。如果源端本身是 GBK 库建议先查询出真实编码再在 JDBC 连接串里指定不要在同步工具里强行转换。6. 进阶把 dbswitch 同步任务做成可观测的定时流水线跑通了全量、接上了增量剩下的事情是把任务交给运维。我的做法是给 dbswitch 补齐三块能力任务日志、失败重试、一致性校验。6.1 给每次同步加任务日志dbswitch 自带的输出是控制台和文件日志但对生产来说不够。我会在每个 cron 脚本里把开始时间、结束时间、状态追加到一张任务表。这样每天的同步情况可以一条 SQL 查出来而不是翻文件日志。任务表和位点表可以放在同一个库方便统一监控。6.2 失败自动重试与告警增量同步失败时不要盲目重试先看失败类型。连接超时可以重试数据转换错误重试多少次都一样。我的策略是脚本里捕获退出码连接类错误重试三次间隔一分钟数据错误直接退出并告警。告警渠道不强求能发到群或者邮件都行关键是让值班的人知道某一轮任务没有跑完。6.3 验证同步结果的一致性同步完成后的验证不能只靠行数。我会额外跑一个分组计数对比比如订单表按日期分组两边各算一组总数差异超过阈值就标记任务成功但有异常。更彻底的做法是对关键表做一列校验和用标准的聚合函数对主键和时间戳字段做哈希汇总两边比对哈希值一致才能认为数据真的对得上。这个可观测改造做完dbswitch 才算真正从临时迁移工具变成可维护的数据库同步方案。我自己的习惯是每个新表接入时先跑三天观察增量位点和延迟稳定后再放进监控范围。数据同步这件事跑通只是开始能持续稳定跑下去才是目标。希望这些实践能帮你少走弯路。本文还有配套的精品资源点击获取