多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

数据库批量迁移同步工程化落地:dbswitch全量与增量实战

数据库批量迁移同步工程化落地:dbswitch全量与增量实战 简介dbswitch是一款面向数据库开发与运维人员的批量迁移同步工具用于解决源端数据库向目的端数据库的结构与数据同步问题适合需要跨库迁移、数据集成或增量变更同步的中高级开发者使用。资源包共506个文件以306个java源码、36个js与25个vue前端文件为核心辅以20个jar依赖、15个xml配置、13个sql脚本及9个sh、4个cmd启动脚本另有少量yml、html、css、md等压缩包约99.09MB整体为可编译运行的多模块工程。功能上支持字段类型、主键信息与建表语句转换并生成建表SQL支持基于正则的表名与字段名映射数据同步基于JDBC分批次读取并以insert/copy方式分批写入目的库还支持有主键表的增量变更同步变化数据计算千万级数据量性能需在生产环境验证。目前已有793人学习下载可帮助读者快速理解迁移同步的工程结构与实现思路。1. 数据库批量迁移同步的工程化落地dbswitch 能解决哪些真问题凌晨两点运维群里弹出一条消息某业务库要做机房搬迁涉及几十张表、上亿行数据要求停机窗口不超过四小时。这种场景下手工写 mysqldump 加脚本拼凑的方案基本等于给自己埋雷——字段类型对不上、增量追不上、断点续传没有、迁移完数据对不上账。dbswitch 这类工具的价值就在这里它把「源端数据库向目的端数据库的批量迁移同步」这件事从一次性脚本变成了可配置、可重复、可增量追平的工程流程。它适合三类人一是做数据平台迁移的工程师需要把异构库的表结构和数据整体搬过去二是做数仓同步的开发者需要定时把业务库的增量变更同步到分析库三是运维同学面对停机窗口极短的搬迁任务需要全量加增量组合把停机时间压到最低。核心能力就两条线全量迁移负责把存量数据一次性搬完增量同步负责在全量之后持续追平源端的变更。下面按「先跑通全量、再挂上增量、最后处理坑」的顺序拆开讲。2. 全量迁移先跑通从建表到数据搬完的最小闭环2.1 全量迁移到底做了什么全量迁移不是简单地把数据 SELECT 出来 INSERT 过去。它要处理四件事读源端的表结构元数据、在目的端重建表、分批把数据读出来写过去、记录迁移进度以便中断后能续。dbswitch 的常见做法是先把源端表的 DDL 解析成内部模型再根据目的端数据库的方言生成建表语句然后按主键或唯一键分页读取数据每批写入目的端。这里有个容易被忽略的点全量迁移期间源端如果有写入这批新数据是不在全量范围内的。所以全量迁移必须和增量同步配合使用全量负责某个时间点之前的存量增量从那个时间点开始追。如果只做全量不做增量迁移完成后源端和目标端立刻就不一致了。选型上dbswitch 支持多种源端和目的端组合常见的是 MySQL 到 MySQL、MySQL 到 PostgreSQL、Oracle 到 MySQL 这类异构场景。异构迁移最大的麻烦是类型映射比如 MySQL 的tinyint(1)到 PostgreSQL 该用什么类型Oracle 的NUMBER到 MySQL 又该怎么落。dbswitch 内置了一套类型映射规则但实际项目里往往需要根据业务语义微调。2.2 用配置文件定义一个迁移任务dbswitch 通常通过配置文件或命令行参数来定义迁移任务。下面是一个典型的全量迁移配置示例用 YAML 描述源端、目的端和要迁移的表# dbswitch 全量迁移任务配置示例 source: type: mysql host: 10.0.0.11 port: 3306 database: biz_db username: migrator password: ****** # 只迁移指定 schema 下的表避免全库扫描 includeTables: - order_main - order_detail - user_profile target: type: postgresql host: 10.0.0.21 port: 5432 database: analytics_db schema: public username: loader password: ****** migration: mode: full # full 表示全量incr 表示增量 batchSize: 5000 # 每批读取行数过大容易 OOM过小速度慢 threadCount: 4 # 并发迁移的表数量 createTable: true # 目的端不存在时自动建表 dropTable: false # 是否先删目的端表首次迁移可设 true fetchSize: 1000 # JDBC 游标每次拉取行数这份配置里几个参数值得展开说。batchSize控制的是应用层每次从结果集里取多少行再批量写入设太大内存扛不住设太小网络往返次数多。经验值在 2000 到 10000 之间宽表取小值窄表取大值。fetchSize是 JDBC 层面的游标大小MySQL 需要配合useCursorFetchtrue才生效PostgreSQL 默认就支持游标。threadCount是并发迁移的表数量不是单表内的并发度单表迁移本身是串行的靠多表并发来提升整体吞吐。2.3 执行迁移并验证结果配置写好后用命令行触发迁移# 启动全量迁移任务 dbswitch --config ./migration-full.yaml --job full_order_migrate # 查看任务状态确认是否完成 dbswitch --status --job full_order_migrate # 迁移完成后做行数比对 dbswitch --compare --config ./migration-full.yaml --tables order_main,order_detail执行逻辑是工具先连接源端读取表结构在目的端执行建表语句然后逐表启动迁移线程。每个线程内部按主键排序分页读取每读完一批就写入目的端并更新进度。如果中途失败重新执行时会从上次记录的位点继续不会从头再来。验证环节不能只看工具报的成功。至少要做两件事一是行数比对源端和目标端每张表的 count 必须一致二是抽样比对随机取若干主键把两边的整行数据拉出来逐字段对比。行数一致但内容不一致的情况并不少见尤其是涉及字符集转换或时间精度截断的时候。注意全量迁移前务必确认目的端表的字符集和排序规则。源端是 utf8mb4、目的端建成 utf8 的话emoji 和部分生僻字会在写入时直接报错或变成问号这种问题在迁移完成后才发现的代价很大。3. 增量同步接上让目的端持续追平源端变更3.1 增量同步的两种实现路径增量同步的核心问题是怎么知道源端哪些数据变了。常见做法有两类。第一类是基于时间戳字段轮询比如表里有update_time每次同步时记录上次同步到的最大时间下一轮只查大于这个时间的数据。这种方式实现简单但有两个硬伤物理删除的数据抓不到同一秒内的多次更新可能漏掉。第二类是基于数据库的变更日志MySQL 用 binlogPostgreSQL 用逻辑复制槽Oracle 用 LogMiner。这种方式能捕获所有变更包括删除但配置复杂对数据库权限和参数有要求。dbswitch 的增量同步通常支持这两种模式实际项目里如果源端是 MySQL 且能开启 binlog优先走日志解析如果源端是业务上不方便改配置的库退而求其次用时间戳轮询但要接受删除丢失的代价。3.2 配置一个基于时间戳的增量任务下面是一个增量同步的配置片段在之前全量配置的基础上增加增量相关参数migration: mode: incr incrMode: timestamp # timestamp 或 binlog timestampColumn: update_time # 用于判断变更的时间字段 timestampFormat: yyyy-MM-dd HH:mm:ss initialTimestamp: 2024-01-01 00:00:00 # 首次增量起点 syncInterval: 30 # 轮询间隔单位秒 deleteStrategy: ignore # 时间戳模式无法捕获删除只能忽略 conflictStrategy: upsert # 主键冲突时执行更新而非报错timestampColumn必须是源端表上真实存在且随每次更新变化的字段。有些表用gmt_modified有些用last_update配置前要逐表确认。conflictStrategy设为 upsert 是关键因为增量数据里可能包含已经同步过的记录用 INSERT 会直接主键冲突报错用 upsert 则变成更新。syncInterval设太小会给源端造成持续查询压力设太大则同步延迟高30 秒是个折中值对实时性要求高的场景可以降到 5 到 10 秒。3.3 增量同步的启动与监控增量任务启动方式和全量类似但它是常驻进程# 启动增量同步前台运行便于观察日志 dbswitch --config ./migration-incr.yaml --job incr_order_sync --daemon false # 后台运行并输出日志到文件 nohup dbswitch --config ./migration-incr.yaml --job incr_order_sync sync.log 21 # 查看同步延迟对比源端最大时间和已同步位点 dbswitch --lag --job incr_order_sync监控增量同步是否健康主要看两个指标一是延迟即源端最新数据的时间减去已同步到的位点时间正常应该在秒级二是错误计数如果日志里持续出现写入失败说明目的端表结构或约束和源端不匹配需要停下来排查而不是让它一直重试。注意时间戳轮询模式下如果源端同一行数据在两次轮询之间被更新了多次只有最后一次会被同步。这对最终一致性没有影响但如果业务需要保留每一次变更的历史就必须走 binlog 模式。4. 避坑与排查迁移同步里最容易翻车的五个地方4.1 大表迁移到一半内存溢出现象是迁移进程突然退出日志里出现 OutOfMemoryError重启后从断点继续又很快再次溢出。原因通常是batchSize设得过大或者源端表里有超大字段比如 TEXT、BLOB一批数据加载到内存后体积远超预期。解决办法是把batchSize降到 1000 甚至 500同时确认 JDBC 驱动没有把整个结果集一次性加载。MySQL 连接串里加useCursorFetchtruedefaultFetchSize1000PostgreSQL 则要确保 autocommit 为 false 才能启用游标。4.2 增量同步延迟越来越大现象是同步延迟从几秒逐渐涨到几分钟甚至几小时。原因一般是单线程同步扛不住源端的写入量或者每轮查询没有走索引导致全表扫描。先确认timestampColumn上有没有索引没有的话立刻加上。如果索引没问题就要考虑把大表拆成多个小任务并行同步或者改用 binlog 模式降低对源端的查询压力。还有一种情况是目的端写入慢比如目的端有大量触发器或索引每批写入都要维护索引这时候要评估是否在同步期间临时禁用非必要索引。4.3 异构迁移后字段值被截断或精度丢失现象是迁移完成后发现某些字段的值和源端对不上比如小数位数变少、字符串被截断、时间少了毫秒。原因是源端和目的端的类型映射没有覆盖到这些细节。MySQL 的datetime(3)到 PostgreSQL 如果建成timestamp而不带精度毫秒就丢了。decimal(18,6)到某些库如果映射成decimal(18,2)小数位就被截断。解决办法是在建表阶段人工审核 DDL对精度敏感的字段手动调整目的端类型不要完全依赖自动映射。4.4 迁移期间源端 DDL 变更导致任务失败现象是迁移进行到某张表时突然报字段不存在或类型不匹配。原因是迁移期间有人在源端执行了 ALTER TABLE加了列或改了列类型而迁移任务用的是启动时缓存的表结构。解决办法是在迁移窗口内冻结源端的 DDL 操作如果做不到就要在任务失败后重新拉取表结构再继续。更稳妥的做法是把大表迁移安排在业务低峰期并提前和开发团队确认没有发版计划。4.5 目的端主键冲突导致增量数据写不进去现象是增量同步日志里反复出现 duplicate key 错误数据同步停滞。原因是全量迁移和增量同步的起点没有对齐全量已经把某条记录搬过去了增量又从更早的时间点开始追同一条记录被写了两次。解决办法是确保增量的initialTimestamp不早于全量迁移开始时源端的最小时间戳并且把conflictStrategy设为 upsert。如果已经出现冲突手动把冲突记录在目的端删掉再让增量重跑或者直接调整增量起点跳过冲突区间。5. 把迁移做成可重复的流程校验、回滚与自动化迁移做完不是终点能证明迁移做对了才是。我一般会在迁移完成后跑一套校验脚本核心逻辑是分片抽样比对# 分片抽样比对源端和目的端数据 import hashlib def row_hash(row): # 把整行拼成字符串再取哈希避免逐字段比较的繁琐 return hashlib.md5(|.join(str(v) for v in row).encode()).hexdigest() def compare_table(src_conn, dst_conn, table, pk_column, sample_size1000): # 随机取 sample_size 个主键 pks fetch_random_pks(src_conn, table, pk_column, sample_size) mismatch [] for pk in pks: src_row fetch_row(src_conn, table, pk_column, pk) dst_row fetch_row(dst_conn, table, pk_column, pk) if row_hash(src_row) ! row_hash(dst_row): mismatch.append(pk) return mismatch这段代码的关键在于抽样策略。不要只抽最新的数据也不要只抽最老的数据要按主键范围均匀分布地抽。如果抽样发现不一致先看是不是时间字段的时区问题——源端存的是 UTC目的端按本地时区解释这种差异在跨时区部署时非常常见。确认不是时区问题后再逐字段对比找出具体是哪一列不一致。回滚方案也要提前准备。全量迁移如果中途发现目的端建表有问题最安全的做法是删掉目的端已迁移的表重新来而不是试图修补。所以迁移前要确认目的端有足够的磁盘空间容纳至少两份数据一份是迁移中的一份是回滚后重建用的。增量同步的回滚相对简单停掉任务、清掉目的端增量期间写入的数据、调整起点重新启动即可。自动化方面我习惯把全量迁移和增量同步串成一个流程先跑全量全量完成后记录一个时间点 T然后以 T 为起点启动增量。这个流程可以用调度工具串起来全量任务成功后自动触发增量任务避免人工衔接时忘记启动增量导致数据不一致。调度工具里要配置失败告警全量失败或增量延迟超过阈值时能及时收到通知。最后说一个我踩过的坑有一次迁移完成后校验通过但业务上线后发现某些查询结果不对。排查了很久才意识到源端表上有触发器在写入时会自动更新某些字段而迁移工具直接写目的端绕过了触发器导致这些字段的值和源端不一致。后来我们在目的端也建了同样的触发器或者在迁移时把这些字段的值直接算好写进去。这件事让我养成了一个习惯迁移前先把源端表上的触发器、存储过程、外键约束全部列出来逐个确认迁移后是否需要重建。希望这个经验能帮你少走一段弯路。本文还有配套的精品资源点击获取
返回列表