双写必须由业务代码显式控制,不能依赖中间件;需封装统一写入入口如SafeDualWriter.write(),确保两库均成功才确认,失败则降级单写并告警;文档结构映射须硬编码处理,避免语义错位;Oplog起点须严格对齐双写开启时刻,否则导致数据丢失;最终一致性需通过哈希比对验证,而非仅依赖同步延迟监控。

双写必须由业务代码显式控制,不能依赖中间件
很多人以为加个代理或改驱动配置就能自动双写,实际不是。MongoDB 没有内置双写开关,save()、insert_one()、update_one() 等操作必须在应用层拆成两路:一路写旧集合,一路写新集合(或新结构的映射表)。
常见错误现象:用连接池自动转发写请求,结果字段映射失败、类型转换报错、单边写入失败却无感知。
- 必须封装统一的写入入口,比如
SafeDualWriter.write(),只在两库都返回成功时才确认写入完成 - 若新结构写入失败,需记录
failed_dual_write_log+ 降级为单写,并触发告警,避免静默丢数据 - 不要复用旧 ORM 的 save 方法,否则字段名、嵌套路径、默认值等容易漏映射
文档结构映射必须提前评估并硬编码处理
从 user.profile.address 这种嵌套对象迁到关系型表或扁平化文档,不是简单 copy 字段——语义错位会直接导致插入失败或查询逻辑崩坏。
典型问题包括:
-
Date类型:MongoDB 的ISODate("2025-03-12T08:30:00Z")在金仓/MySQL 中需转为TIMESTAMP WITH TIME ZONE,否则报invalid input syntax for type timestamp with time zone -
status字段:旧库存字符串"active",新库设计为status_id INT枚举,不显式做{"active": 1, "inactive": 2}映射就会插不进 - 数组字段:如
tags: ["a", "b"],若新结构要求存为关联表,就得在双写时主动展开插入多行
建议用 KDTS 工具先跑一次结构评估:java -jar kdms-cli.jar --task assess --source-type mongodb --source-host ...,生成字段映射报告再动手改代码。
Oplog 起始点对齐不准,会导致永久性数据丢失
双写启动后,旧库里那几分钟没被双写的变更,得靠解析 oplog.rs 补全。但 Oplog 是循环覆盖的,启动太晚,关键变更可能已被刷掉。
实操陷阱:
- 不能用 Python 的
datetime.fromtimestamp(oplog_entry["ts"])直接解析 ——ts是Timestamp类型,得用oplog_entry["ts"].time_t提取秒级时间戳 - KFS 或自研同步工具虽支持断点续传,但首次拉取起点必须严格等于双写开启时刻的
ts值,差一毫秒就可能重复或跳过 - 推荐做法:双写开启前,先执行
db.oplog.rs.find().sort({$natural: -1}).limit(1)记下最新ts,作为增量同步起点
切换读写前必须验证最终一致性,不能只看同步延迟
监控面板显示 “同步延迟 0ms” 不代表数据一致。MongoDB 副本集或 Oplog 解析工具只保证写操作顺序重放,不校验业务语义是否等价。
容易被忽略的复杂点:
- 索引缺失:新结构上没建对应索引,查得慢或查不准,但同步本身不报错
- 聚合逻辑变化:旧库用
$lookup拆分聚合,新库改用 JOIN,字段别名或空值处理不一致 - 时间字段时区:旧库用 UTC 存,新库用本地时区写入,同一条记录查出来时间差 8 小时
真正可靠的验证方式是:抽样比对关键业务文档的哈希值(如 sha256(json.dumps(doc, sort_keys=True))),而不是只检查 _id 和更新时间。











