SyncTool · 数据库实时同步工具

共计 10485 个字符,预计需要花费 27 分钟才能阅读完成。

SyncTool · 数据库实时同步工具 在异构数据库之间实时同步结构与数据的开箱即用 Web 应用。单个 jar 启动,浏览器点几下即可让 Oracle 的表持续流向 PostgreSQL、让 MySQL 的增量实时落到达梦 —— 不需要 Kafka、不需要 ZooKeeper、不需要写一行代码。

技术栈:Spring Boot 2.7 单体架构 + Thymeleaf 服务端渲染 + Quartz 调度 + H2 内嵌元数据库。零外部依赖,内网离线可用。

效果截图 看板:全局同步态势一屏掌握 项目数、连接数、同步表数、24 小时变更量与最近活动流。
SyncTool · 数据库实时同步工具

数据库连接:保存前先测通 选择数据库类型后自动生成 JDBC URL,可预览、可测试;非内置驱动填写 jar 路径即可动态加载。

SyncTool · 数据库实时同步工具

项目列表:多任务并行,启停自如 每个项目一组「源库 → 目标库」,独立启动 / 暂停,状态与最近同步时间一目了然。

SyncTool · 数据库实时同步工具

项目详情:逐表勾选与游标策略可视化 表 / 视图 / 存储过程分组勾选,支持搜索与批量操作;每张表的增量检测策略直接标注,IDENTITY 与 NONE 会显著提示(意味着更新可能同步不到)。

SyncTool · 数据库实时同步工具

变更日志:每一次变更都可追溯 对象名、变更类型、影响行数、耗时与完整错误详情。

SyncTool · 数据库实时同步工具


核心功能[td]

分类能力
项目管理配置源库 / 目标库,多项目并行互不干扰
连接测试保存前即可验证连通性,支持预览自动拼装的 JDBC URL
对象选择表 / 视图 / 存储过程,默认全选,支持搜索与批量勾选
同步内容表结构、表数据、索引、视图、存储过程与函数
自动建对象目标库不存在时按目标方言自动创建
SQL 方言适配类型映射、函数名转换、标识符引号、分页语法、存储过程包装
实时同步轮询检测(默认 2 秒),也可用 Cron 表达式精确编排
故障恢复进程重启后从上次游标继续,停机期间的变更会被补齐
启停控制随时启动 / 暂停,支持手动「立即同步」
变更记录每次变更的对象、类型、行数、耗时与错误详情
看板项目数、连接数、同步表数、24 小时变更量与最近活动
国际化中 / 英文切换,默认语言按浏览器时区智能推断
主题昼夜模式切换,未手动选择时跟随系统
全本地化前端无 CDN、无 webfont、运行时零外部请求,内网离线可用

前端设计 蓝紫渐变主色(#4f46e5 → #ec4899)、柔和阴影、玻璃拟态导航栏。样式为手写 CSS + 设计令牌(CSS 变量),无框架、无构建步骤。

主题切换

  • 主题写在 <html data-theme=”dark|light”>,只覆盖 CSS 变量,因此组件无需第二套深色规则
  • 存储键 SyncTool-theme(localStorage)
  • 未手动选择过时跟随系统 prefers-color-scheme,并监听系统主题变化实时跟随;用户点过切换按钮之后就以用户选择为准,不再被系统覆盖
  • <head> 内联脚本在首屏渲染前应用主题,避免深色用户看到一帧白底闪烁

默认语言判定(优先级从高到低)

[td]

优先级来源说明
1语言 Cookie用户点过语言按钮,显式选择最高优先
2浏览器时区Asia/Shanghai、Asia/Hong_Kong、Asia/Taipei、Asia/Macau 等 → 中文,其余 → 英文
3Accept-Language时区尚未上报时(首个请求)的兜底
4简体中文最终默认值

为什么时区优先于 Accept-Language:境外华人用户的浏览器语言常常是英文,但人在国内时区。时区是「想看哪种语言」更准的信号。

由于本项目是服务端渲染,语言必须在渲染前定好,所以时区由前端脚本探测后写入 SyncTool_TZ cookie,服务端 TimezoneAwareLocaleResolver 读取它决定 Locale。首次访问若渲染语言与时区推断不符会自动刷新一次;用户显式选过语言后不再刷新。

离线/内网可用 —— 所有前端资源均在仓库内,运行时不发起任何外部请求:

[td]

资源说明
css/app.css手写样式,含设计令牌与深色主题
js/theme.js js/app.js原生 JS,无 jQuery、无框架
vendor/css/bootstrap-icons.min.css + vendor/fonts/*.woff2图标字体,本地托管
vendor/favicon.svg内联渐变 SVG

字体使用系统字体栈(PingFang SC / Microsoft YaHei 等),不引入 webfont;下拉箭头等小图形使用内联 data: URI。静态资源总体积约 440KB。验证方式:抓取任意页面 HTML,其中 src/href 引用的外部 http(s) 地址数量为 0。


与市面已有工具的对比 一览表[td]

维度SyncToolDebezium + KafkaCanalFlink CDCDataXKettleSymmetricDSNavicat/DBEaver 数据传输
部署形态单个 jarKafka + Connect + ZK/KRaftCanal Server (+MQ)Flink 集群 (JM/TM)客户端脚本桌面 + 资源库每节点部署引擎桌面客户端
外部依赖Kafka、ZooKeeperZooKeeper(集群)Flink、Checkpoint 存储无(但需 JSON 作业)JVM + 插件数据库触发器
配置方式Web 界面点选YAML/REST + 代码消费配置文件 + 客户端代码SQL/DataStream 代码JSON 作业文件图形化 ETL 拖拽properties + 建触发器向导
持续增量同步✅ 轮询 /Cron✅ 日志级✅ binlog✅ 日志级❌ 一次性批量⚠️ 需自建定时与增量逻辑✅ 触发器❌ 一次性
结构(DDL)同步✅ 自动建表 / 索引 / 视图 / 存储过程⚠️ 输出 DDL 事件,落库需自写⚠️ 仅事件⚠️ 需自定义❌ 需预建表⚠️ 手工映射⚠️ 有限✅ 但仅一次性
异构方言转换✅ 类型 / 函数 / 引号 / 分页 / 过程❌ 需自行实现⚠️ 部分⚠️ 类型映射有限⚠️ 手工⚠️ 有限⚠️ 一次性映射
国产数据库✅ 达梦 / 金仓 /GBase/ 神通 /OpenGauss❌ 基本不支持❌ 仅 MySQL⚠️ 少数⚠️ 需自写插件⚠️ 靠通用 JDBC⚠️ 有限⚠️ 部分
源库侵入性只读查询,零侵入需开 binlog/wal + 复制权限需开 binlog需开 binlog/wal只读只读需建触发器只读
幂等 / 断点续传✅ 主键 upsert + 游标持久化✅ offset✅ checkpoint❌ 需自建
可视化监控✅ 看板 + 变更日志需接 Prometheus/Grafana需自建Flink UI(偏作业)日志有限有 Web 控制台
上手成本分钟级中高中高低(但不解决持续同步)
内网离线✅ 无外网请求
适用规模中小规模、部门级、信创迁移大规模流式MySQL 生态大规模流式大批量离线复杂 ETL多主复制临时搬数

图例:✅ 原生支持 ⚠️ 部分支持 / 需额外工作 ❌ 不支持

五个真正的差异点1.「一个 jar」对「一套基础设施」

Debezium / Flink CDC 是优秀的流式框架,但要跑起来一条 MySQL → PostgreSQL 的链路,你需要 Kafka、Kafka Connect、协调服务,再写一个消费端把事件翻译成目标库的 DML。SyncTool 的等价操作是:java -jar synctool.jar,打开浏览器,建两个连接,建一个项目,点「启动同步」。当同步需求的规模配不上一套流式基础设施的运维成本时,这个差距就是决定性的。

2. 结构同步是一等公民,不是留给你的作业

绝大多数 CDC 工具只解决「数据流」,目标表得你自己先建好;DataX 更是明确要求预建表。SyncTool 会读取源库元数据,按目标库方言自动创建表、索引、视图、存储过程,并在源库 DDL 变更后把差异传播过去。跨异构库迁移里,建表和类型映射的工作量往往比搬数据本身更大。

3. 为国产数据库与信创迁移而生

达梦、人大金仓、南大通用、神通、OpenGauss 是内置的一等选项 —— 不是「通过通用 JDBC 也许能连上」,而是各自有专门的方言实现:MERGE INTO … FROM DUAL 的 upsert 写法、类型上限(Oracle VARCHAR2 4000)、函数名差异、标识符引号规则都已处理。Oracle/SQL Server → 国产库的替换场景是本工具的主战场,而这恰恰是 Debezium、Canal 生态最薄弱的地方。

4. 零侵入源库

SymmetricDS 需要在源库建触发器;Debezium / Canal / Flink CDC 需要开启 binlog / WAL 逻辑复制并申请复制权限 —— 在很多生产库上,这是一次要走审批流程的变更。SyncTool 只需要一个只读账号,通过查询游标列做增量,源库结构和配置一动不动。

5. 一次性搬数 vs 持续同步

Navicat / DBeaver 的「数据传输」和 DataX 解决的是「把数据搬过去一次」。SyncTool 解决的是「让两边持续保持一致」:进程重启后从游标继续、停机期间的变更会被补齐、每一行写入都是幂等的。这是两个完全不同的问题。

什么时候不该用 SyncTool诚实地讲清边界:

  • 需要毫秒级延迟或严格的变更顺序 → 用 Debezium / Flink CDC。轮询方案的延迟下限就是轮询间隔。
  • 需要捕获物理删除且表很大 → 无源库审计表时,删除检测要比对双方主键全集,仅对行数低于 full-compare-max-rows 的表启用。
  • 单表数亿行的一次性初始化 → DataX 这类专为批量吞吐设计的工具更快。
  • 需要复杂 ETL 变换(清洗、聚合、多流 join)→ 用 Kettle / Flink。SyncTool 做的是同步,不是转换。
  • 多主双向复制 → 用 SymmetricDS。本工具假定目标库仅由自己写入。

支持的数据库MySQL、MariaDB、Oracle、SQL Server、DB2、PostgreSQL、OpenGauss、达梦 (DM)、人大金仓 (KingBase)、南大通用 (GBase)、神通 (Oscar)、H2,以及自定义数据库(提供 JDBC URL、驱动类名与驱动 jar 路径,运行时动态加载)。

内置驱动仅 MySQL / PostgreSQL / H2;其余数据库需在连接配置中填写驱动 jar 路径,工具会用独立 URLClassLoader 加载并通过 DriverShim 注册到 DriverManager。这样做的好处是:发行包不必捆绑一堆商业驱动,也不会因为驱动版本冲突污染应用类加载器。


部署步骤 环境要求[td]

要求
JDK17 或以上
Maven3.6+(仅构建时需要)
内存建议 ≥ 512MB 堆
端口默认 8080
磁盘元数据库 + 日志 + 快照,建议预留 1GB

一、构建 git clone https://github.com/vfaner/synctool.git# 国内网络请使用镜像:# git clone https://gitee.com/super_rgh/synctool.gitcd synctoolmvn clean package -DskipTests 产物:target/synctool.jar(可执行 fat jar)。

二、启动 java -jar target/synctool.jar 访问 http://localhost:8080 即可。

首次启动会在当前工作目录下自动创建:

[td]

目录内容
./data工具自身的元数据(H2 文件库:连接、项目、游标、锁、变更日志)
./logs运行日志
./snapshots元数据快照目录(可通过 sync.snapshot-dir 修改)

⚠️ 这些是相对路径。请固定在同一目录下启动,或用绝对路径覆盖配置,否则重启后会找不到原有数据。

三、生产环境配置(重要)在 jar 同级目录创建 application.yml:

server:  port: 8080spring:  datasource:    url: jdbc:h2:file:/opt/synctool/data/synctool;MODE=MySQL;AUTO_SERVER=TRUEsync:  poll-interval: 2000              # 轮询间隔(毫秒)snapshot-dir: /opt/synctool/snapshots  batch-size: 500  fetch-size: 1000  safety-lag-ms: 1000  row-count-audit-interval-ms: 60000  full-compare-max-rows: 20000  lock-ttl-ms: 300000  crypto-password: 请改成你自己的强口令      # ← 必须修改  crypto-salt: 请改成你自己的 16 位十六进制盐   # ← 必须修改 logging:  file:    path: /opt/synctool/logs启动时指定:

java -jar synctool.jar –spring.config.location=file:./application.yml

🔐 安全提示:sync.crypto-password 与 sync.crypto-salt 用于加密存储的数据库连接密码,发行包带有默认值,生产环境必须修改。修改后已存储的旧密码将无法解密,需在界面上重新填写。crypto-salt 必须是合法的十六进制字符串。

四、加载非内置驱动 对于 Oracle、SQL Server、DB2、达梦、金仓等,把厂商驱动 jar 放到服务器上,例如:

mkdir -p /opt/synctool/driverscp ojdbc8.jar DmJdbcDriver18.jar kingbase8-8.6.0.jar /opt/synctool/drivers/然后在「数据库连接」页面新建连接时,填写驱动 jar 路径(如 /opt/synctool/drivers/ojdbc8.jar)与驱动类名(选择预设类型时会自动填好)。点击「测试连接」验证加载成功即可保存。

五、后台常驻 方式 A:systemd(推荐)

/etc/systemd/system/synctool.service:

[Unit]Description=SyncTool Database SyncAfter=network.target[Service]Type=simpleUser=synctoolWorkingDirectory=/opt/synctoolExecStart=/usr/bin/java -Xms512m -Xmx1g -jar /opt/synctool/synctool.jar \  –spring.config.location=file:/opt/synctool/application.ymlRestart=alwaysRestartSec=10[Install]WantedBy=multi-user.targetsudo systemctl daemon-reloadsudo systemctl enable –now synctoolsudo systemctl status synctool方式 B:nohup(快速验证)

cd /opt/synctoolnohup java -jar synctool.jar > /dev/null 2>&1 &六、升级 sudo systemctl stop synctoolcp target/synctool.jar /opt/synctool/synctool.jarsudo systemctl start synctool 元数据库使用 ddl-auto: update,表结构会自动演进。升级前请备份 ./data 目录。停机期间源库产生的变更会在重启后由游标机制自动补齐,不会丢失。


使用流程

  • 数据库连接 → 新建源库和目标库连接 → 点击「测试连接」确认可用
  • 项目 → 新建项目 → 选择源库与目标库
  • 进入项目详情 → 勾选要同步的表 / 视图 / 存储过程 → 配置同步选项 → 保存
  • 点击「立即同步」验证一次,或点击「启动同步」开始持续轮询
  • 在「变更日志」查看每次同步的明细

并发与一致性设计 这是本工具的核心设计点,值得单独说明。

增量窗口是闭区间 每个周期同步表数据时:

  • 先从源库读取 MAX(游标列) 作为本次窗口的上界(水位线)
  • 查询 游标列 > 上次游标 AND 游标列 <= 本次水位线 的数据
  • 写入目标库并提交
  • 提交成功后才把游标推进到水位线

关键在于第 2 步的上界。如果不加上界,一次耗时较长的读取可能把游标推进到它实际并未读到的数据之后 —— 那些行会被永久跳过。加了上界后,同步期间新写入源库的数据自然落在窗口之外,下个周期被捕获。

游标在数据提交之后才推进 源库和目标库是两个异构数据库,没有跨库事务。因此选择的顺序是:先提交目标库数据,再持久化游标。

  • 若在两者之间崩溃 → 下次重放同一窗口
  • 若在目标库提交前崩溃 → 事务回滚,同样重放

两种情况都不会丢数据。代价是同一行可能被投递两次,这由下一点消化。

所有写入都是幂等的 每一行都通过基于主键的 upsert 写入,重复执行收敛到同一状态:

[td]

数据库语句
MySQL / MariaDBINSERT … ON DUPLICATE KEY UPDATE
PostgreSQL 系INSERT … ON CONFLICT (pk) DO UPDATE
Oracle / DMMERGE INTO … USING (SELECT ? FROM DUAL)
SQL ServerMERGE … WITH (HOLDLOCK)
DB2MERGE INTO … USING (VALUES (?))
通用 / 自定义UPDATE-then-INSERT(无原生 upsert 时的兜底,含唯一冲突重试)

这把「至少一次投递」变成了「结果上的恰好一次」。删除同样是基于主键的条件删除,重复执行无副作用。

无主键表:无法识别既有行,因此无法保证幂等。工具会明确告警,建议为表添加主键。

时间戳安全回退 时间戳由语句执行时刻决定,但行要到事务提交才对我们可见。一个「开始早、提交晚」的源库事务,其时间戳可能低于我们已经推进的水位线 —— 那它就会被永久跳过。

因此持久化水位线时会回退 sync.safety-lag-ms(默认 1 秒)。代价是这段窗口内少量已同步的行被重复投递,由幂等写入消化;收益是晚提交的事务不会丢失。

单实例执行:三层锁[td]

层级覆盖范围不足
@DisallowConcurrentExecution同一调度器内同一 Job 不并发触发只管 Quartz,不管手动执行;进程重启即失效
JVM ReentrantLock(按项目)同进程内的手动「立即同步」与调度执行互斥进程外无效
数据库锁行(带过期时间)跨进程、跨节点——

数据库锁是保证能跨重启的那一层:内存锁随进程消失,若只有内存锁,硬杀进程后新实例无法得知旧实例是否仍在运行。锁行带租约(sync.lock-ttl-ms,默认 5 分钟),崩溃实例的锁可被接管而不会永久阻塞项目;同时启动时会主动释放本实例上次遗留的锁。

获取锁使用条件 UPDATE(WHERE lock_owner IS NULL OR lock_owner = ? OR lock_expires_at < ?),两个实例竞争时只有一条 UPDATE 能匹配,因此不会同时获得锁。

结构变更的恢复 元数据快照持久化在 metadata_snapshot 表中,每个对象 DDL 应用成功后立即更新自己的快照。因此:

  • 工具停机期间源库发生的 DDL 变更,重启后通过快照比对被发现
  • 中途崩溃时,已应用的对象保留快照,其余下个周期重新检测
  • CREATE TABLE 容忍「已存在」错误,使结构同步同样可重放

行数审计 游标机制只能证明「我读到了哪里」,不能证明「目标库真的还留着这些行」。因此每 sync.row-count-audit-interval-ms(默认 60 秒)会做一次行数审计,核对目标库实际持有的行数与游标声称已投递的量是否一致,发现漂移时记录到变更日志。


增量检测策略 工具按可靠性从高到低选择:

[td]

策略触发条件能力
TIMESTAMP存在 update_time / updated_at / last_modified 等时间戳类型列检测新增 和 更新
IDENTITY存在创建时间列,或单列数字主键仅检测新增
FULL_COMPARE无游标列,且行数低于 sync.full-compare-max-rows每周期全表 upsert
NONE无游标列且表过大仅首次全量加载,之后跳过并说明原因

命名匹配要求列类型确实是时间类型 —— 名为 update_time 的 VARCHAR 不会被当作时间戳游标,因为字符串比较的顺序不可靠。

可在项目详情页为每张表手动指定游标列,优先级高于自动识别。若某表策略为 IDENTITY 或 NONE,界面会直接标出,因为这意味着更新可能同步不到。


配置项application.yml 中的 sync.*:

[td]

默认说明
poll-interval2000轮询间隔(毫秒)
snapshot-dir./snapshots元数据快照目录
batch-size500每个 JDBC 批次行数
fetch-size1000源库结果集读取批量
max-retries3连续失败多少次后标记任务为 ERROR
safety-lag-ms1000时间戳水位线回退量,见上文
row-count-audit-interval-ms60000行数审计间隔(毫秒)
full-compare-max-rows20000全表比对的行数上限
lock-ttl-ms300000同步锁租约时长(毫秒)
crypto-password(默认值)密码加密密钥,生产环境必须修改
crypto-salt(默认值)加密盐值(十六进制),生产环境必须修改

连接密码使用 Spring Security Crypto 的 AES-256 加密后存储,带 enc: 前缀标记以避免重复加密,并兼容加密启用前写入的明文。


架构 com.synctool├── config           配置:i18n、Quartz、Jackson、SyncProperties├── controller       MVC 控制器;controller/api 为 REST 端点├── service│   ├── connection   DataSourceManager、DriverLoader、DriverShim、连接测试│   ├── metadata     MetadataReader 各方言实现 + 快照服务│   ├── monitor      ChangeDetector(结构差异)、CursorStrategyResolver│   ├── converter    SqlDialect 各实现、类型映射、SQL 体转换│   ├── sync         SyncEngine、DataSyncService、StructureSyncService、DdlExecutor│   └── task         Quartz 调度、三层锁、上下文装配、启动恢复├── model            JPA 实体与枚举├── repository       Spring Data JPA├── dto              SyncConfig、ChangeEvent、SyncResult、meta/* 元数据模型└── util             CryptoUtil 关于 @Transactional 的一处设计SyncStateWriter、SyncLockStore、SyncTaskStore 被拆成独立的 Bean,而不是把方法放在 SyncEngine / SyncLockService 上。原因是 Spring 的 @Transactional 基于代理:同类内部自调用会绕过代理,REQUIRES_NEW 和 @Modifying 查询将失去事务语义。跨 Bean 调用才能让这些语义真正生效 —— 而游标推进与锁获取恰恰依赖它。


测试mvn test43 个单元测试,覆盖:

  • 方言不变量:每种方言都能生成处理冲突的幂等 upsert;绑定顺序与占位符数量一致;类型映射不越过各产品上限(Oracle VARCHAR2 4000、SQL Server 4000、无精度 NUMBER 不产生 DECIMAL(0,0));不可移植的默认值被丢弃而非生成非法 DDL
  • 游标策略:解析优先级;IDENTITY 策略正确标记「可能漏掉更新」;配置列失效时降级而非报错;VARCHAR 类型的 update_time 不被误用
  • 游标序列化:时间戳以 UTC ISO-8601 往返,毫秒精度不丢失;超长数字降级为 BigDecimal;损坏值视为「未同步」而非抛异常
  • 密码加密:往返、不重复加密、兼容历史明文、相同密码密文不同

另有端到端脚本(H2 源 / 目标库,20 项断言),覆盖首次全量加载、增量新增、增量更新、重复同步幂等性、DDL 列新增传播、并发写入下源目标行数一致且无重复、并发调用被锁拒绝、停机期间写入在重启后被补齐、自动轮询、变更日志与游标策略上报。


已知限制

  • 存储过程转换:函数名、标识符引号、FROM DUAL、分页语法等机械差异可自动转换;但 PL/SQL、T-SQL、PL/pgSQL 的过程化控制流结构不同,复杂过程无法可靠自动翻译。这类对象会保留源码尝试执行,失败时给出具体错误,可在项目配置中提供手动 DDL 覆盖。
  • 行删除检测:无源库审计表时需比对双方主键全集,因此仅对行数低于 full-compare-max-rows 的表启用。
  • 无主键表:无法保证写入幂等,重放可能产生重复行;工具会告警。
  • 目标库写入方:假定目标库仅由本工具写入。
  • 延迟:轮询方案的延迟下限即轮询间隔。若需更低延迟,可扩展接入 Debezium 解析 binlog / LogMiner。

参与贡献 欢迎 Issue 与 Pull Request:

如果这个项目对你有帮助,欢迎点个 Star ⭐

SyncTool · 数据库实时同步工具 SyncTool · 数据库实时同步工具 SyncTool · 数据库实时同步工具 SyncTool · 数据库实时同步工具 SyncTool · 数据库实时同步工具

正文完
 0
suyan
版权声明:本站原创文章,由 suyan 于2026-09-01发表,共计10485字。
转载说明:转载本网站任何内容,请按照转载方式正确书写本站原文地址。本站提供的一切软件、教程和内容信息仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件QQ邮箱:suyanw520@163.com 与我们联系处理。敬请谅解!
评论(没有评论)
验证码