小林在镇上开了家杂货铺,生意越做越大,分店开到了邻市。总部大刘是数据工程师,他要把所有分店的销售数据统一收进总部仓库。第一次迁数据时,大刘说:“这就像搬家——把所有货架上的商品装箱、装车、运到新仓库,一件不落。”可每月这么搬一次,货车租金和人力成本高得吓人。后来大刘换了法子:“以后每天只把新到的货和卖掉的货记下来,用快递单送过去,不用再把整个仓库翻一遍。”这就是全量同步与增量同步的区别:一个简单粗暴但费时费力,一个轻快灵巧但依赖“变更情报”。本文要用“搬家”与“快递”这对生活搭档,带你搞懂ETL同步策略的选型、陷阱与工程落地。

岗位关联

掌握增量和全量同步策略,是数据工程师设计高效ETL链路的看家本领。你不需要每次都“搬整座仓库”,只需知道什么时候用卡车、什么时候让快递员跑腿——这决定了数据管道的成本、延迟与稳定。

目录
  1. 可视化演示
  2. 全量同步原理
  3. 增量同步原理
  4. 时间戳字段
  5. CDC变更捕获
  6. 全量比对方案
  7. 增量合并策略
  8. 方案选型决策
  9. 同步链路设计
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

全量同步原理

小林的第一家分店开张后,大刘要把他三个店的Excel汇总到总部数据库。大刘说:“这最简单,就叫全量同步——像搬家公司一样,把每个仓库的货架全搬空,装车运到总部,再按原样摆好。”小林问:“那要搬多久?”大刘指着报表说:“你看,每个店几万行数据,三个店加起来约100GB,以每秒50MB的传输速度,光搬运就要半小时。”全量同步的魅力正在于此:逻辑简单,不需要任何“情报”,只要把整张表拷贝过去,数据一定完整。

所谓全量快照,就是某时刻整张表的完整副本。实现上通常先清空目标表(TRUNCATE),再逐行插入(INSERT),或者直接用数据库自带的导出导入工具。它的优点是绝对可靠,缺点也直白:数据量越大,耗时越长,而且拷贝期间源表往往要加锁,业务写入会被阻塞。因此全量同步只适合小表初始化或低频兜底,比如维表、配置表,或数据仓库分层中的首次灌数。

搬完家后,小林发现仓库门口堆满了箱子,员工要重新整理上架,期间新订单也没法正常处理。全量同步就是这样一个“大动干戈”的糙汉子:可靠,但每次都要兴师动众。所以它适合一年搬一次家,不适合天天搬家。

Tfull=VR+TlockT_{full} = \frac{V}{R} + T_{lock}
全量同步总耗时 = 数据体积 ÷ 传输速率 + 锁表时间。V越大、R越慢,耗时越不可接受;T_lock的存在说明它不是“零侵入”方案。
  1. 总耗时构成全量同步总时间由传输时间和锁表时间两部分叠加而成。
    Tfull=Tcopy+TlockT_{full} = T_{copy} + T_{lock}
  2. 数据体积V 代表待拷贝的数据总量,单位通常取 GB,由行数与每行大小乘积决定。
    V=∑i=1nrow_sizeiV = \sum_{i=1}^{n} \text{row\_size}_i
  3. 传输速率R 是网络或导出工具单位时间传送的数据量,瓶颈常在磁盘 I/O 或带宽。
    Tcopy=VRT_{copy} = \frac{V}{R}
  4. 锁表代价全量拷贝时源表可能被锁定,期间写入请求只能排队等待,这就是 T_lock。
    Tlock=∑wait_timejT_{lock} = \sum \text{wait\_time}_j

增量同步原理

第二次搬家时小林学乖了:“能不能别总搬空仓库?”大刘点点头:“当然可以,增量同步就是只送新包裹。”他把一个快递员的角色引进来:每天凌晨,快递员只把当天新到的货和卖掉的货装上车,送到总部仓库。货少了,车轻了,时间从半小时缩到三分钟。增量同步的核心是增量窗口——一个“上次送过哪里”的记号,快递员只处理窗口之后的新数据,不再回望老数据。

实现增量同步的关键在于“判断哪些是新的”。最常见的手段是在源表里找一个变更标记,比如业务主键的自增序号、更新时间字段,或者事务日志。每次同步记录下当前取到的最大位置(业内叫水位),下次从水位之后继续取。这样同步的数据量从“全表 V”降为“变更部分 ΔV”,效率和实时性都大幅提升,可以按分钟甚至秒级调度。

快递员轻装上路,小林终于松了口气。但大刘提醒他:“快递员能干活,前提是每个包裹上都贴着发货时间。要是没有这个标签,他根本不知道哪些是新货。”于是,时间戳字段成了下一个要解决的问题。

Tinc=ΔVRT_{inc} = \frac{\Delta V}{R}
增量同步耗时只与变更数据量 ΔV 相关,与整张表的总大小无关。表再大,只要每天只改几千行,同步就是秒级的事。
  1. 变更数据量ΔV 表示两次同步之间发生变化的行数及对应体积,是全量 V 的一个极小子集。
    ΔV=Vnew+Vupdated\Delta V = V_{new} + V_{updated}
  2. 传输速率不变R 仍然是网络传输速率,但传输对象从整表变为变更集合。
    Tinc=ΔVRT_{inc} = \frac{\Delta V}{R}
  3. 窗口推进同步完成后水位 t 更新为本次最大变更时间,保证下次不重复取数。
    twatermark=max⁡(updated_at)t_{watermark} = \max(\text{updated\_at})
  4. 效率对比当 ΔV 远小于 V 时,增量耗时远小于全量耗时,调度频率也可随之提高。
    Tinc≪Tfull  ⟺  ΔV≪VT_{inc} \ll T_{full} \iff \Delta V \ll V

时间戳字段

快递员拿到的新包裹都贴着一张面单,写着下单时间。增量同步也一样,源表里通常要有一个“面单字段”——updated_at(最后更新时间)或 created_at(创建时间)。大刘在小林的订单表里检查了一遍,发现每行订单都有updated_at字段,下单、改价、发货都会自动更新它。太好了,就用它当增量标记:同步时执行一条查询,只捞updated_at大于上次水位的数据,这就是增量水位推进。

但时间戳字段不是万能的。小林的分店开到了美国,那边服务器记录的updated_at是纽约时间,总部的水位却是北京时间,两边时差让快递员彻底懵了——不是漏送就是重复送。更棘手的陷阱是:如果订单被物理删除,这一行直接从表里消失了,updated_at永远不会再出现,增量同步根本感知不到删除动作。此外,如果业务代码偷懒没更新updated_at,数据变更也会悄悄溜过水位,造成静默丢失。

快递员渐渐发现,光看面单远远不够——要是店员忘记贴面单,或者包裹被小偷顺走,他永远察觉不到。大刘说:“别慌,咱们换一个更聪明的快递员,他不用看面单,只看记账本。”

Sync SQL:WHERE updated_at>twatermark\text{Sync SQL:} \quad \text{WHERE } \text{updated\_at} > t_{watermark}
增量同步的查询本质上是一个时间过滤条件:只取出更新时间晚于当前水位的行。时区不一致、删除操作、字段未更新,都会让这个简单条件失效。
  1. 过滤条件同步查询以水位移为下界,只筛选后续变更。
    σupdated_at>twatermark(orders)\sigma_{\text{updated\_at} > t_{watermark}}(\text{orders})
  2. 水位初始化首次同步时水位通常设为最小值或某历史时点,之后逐步推进。
    twatermark(0)=tinitt_{watermark}^{(0)} = t_{init}
  3. 水位更新每轮同步取本批最大时间戳作为新水位,保证不重不漏。
    twatermark(k+1)=max⁡(updated_ati(k))t_{watermark}^{(k+1)} = \max(\text{updated\_at}^{(k)}_i)
  4. 时区归一化多时区场景必须统一为 UTC 存储,否则水位比较失去意义。
    tutc=tlocal−timezone_offsett_{utc} = t_{local} - \text{timezone\_offset}

CDC变更捕获

大刘给小林引入了一个“高级快递员”——CDC(Change Data Capture,变更数据捕获)。普通快递员要一件件翻货架找新货,高级快递员却直接站在仓库的安检门旁边,每件货进出时,安检系统都会自动“滴”一声,把货号记录到流水账本里。这流水账本是数据库自己写的,叫 binlog(MySQL)或 redo log(Oracle/PostgreSQL的 WAL)。CDC 工具(如 Canal、Debezium、Flink CDC)解析这些日志,就能拿到每一笔插入、更新、删除记录,实时转发给目标端。

CDC 的优势在于不侵入业务:它读取的是数据库底层的二进制日志,不要求业务表有updated_at字段,也不依赖业务代码配合。它本质上是把主从复制的原理拿来给 ETL 用——主库产生日志,从库/同步任务消费日志。每个日志都有一个位置编号(位点),类似于快递单号。同步组件记录当前消费到位点,下次从该位点继续读,保证变更不丢、不乱序。

有了高级快递员,数据几乎是“秒级”到达总部。大刘却按住了兴奋的小林:“高级快递员也会看走眼。比如数据库日志被误删、binlog 保留期太短,历史变更就查不到了。”所以,还得安排每月一次“盘点对账”。

Lcur=Lstart+ΔLconsumedL_{cur} = L_{start} + \Delta L_{consumed}
位点公式:当前日志位置 = 起始日志位置 + 已消费的日志量。只要记住位点,断线重连后也能从断点续传,不会重复送包裹。
  1. 日志位点每条变更在 binlog 中都有唯一偏移量 L,类似书签页码。
    L∈[0, end_pos]L \in [0, \, \text{end\_pos}]
  2. 消费推进每处理一批变更,位点随之前进,增量恰好等于本批日志体积。
    Lcur=Lprev+ΔLL_{cur} = L_{prev} + \Delta L
  3. 断点续传记住位点后,即使同步进程崩溃,重启也能从上次位置继续。
    resume from Lcur⇒no data loss\text{resume from } L_{cur} \Rightarrow \text{no data loss}
  4. 顺序保障同一条事务日志串行消费,保证业务上的先后关系不被打破。
    order(ei)=order(Li)\text{order}(e_i) = \text{order}(L_i)

全量比对方案

总部每个月月底都会派一位资深库管员到各分店“盘点”:对照电脑里的库存清单,一件一件数货架上的实物,发现对不上的就记下来修正。ETL 中的全量比对就是这个思路——定期把源表全量扫描一遍,和目标表逐行比对,把增量同步漏掉的、改错的、被删除的行找出来补齐。它是增量同步的“安全网”。

全量比对的工程实现,通常不会真的逐字比对两个大表,那样太慢。更聪明的做法是给每一行计算一个哈希签名(hash 行签名):把这一行的所有字段拼接成一个字符串,再用 MD5 或 CRC32 算出一个短指纹。比对时只需比较指纹:源端和目标端同一主键的签名一致,说明数据一致;不一致或目标缺失,就触发行级修正。由于全量比对要扫描全表,代价高昂,所以只能低频周期执行——比如每天一次或每周一次。

盘点那天,库管员果然揪出了三笔在增量同步中悄悄消失的退货单。小林感慨:“原来再聪明的高级快递员,也需要月末盘点兜底。”大刘笑道:“对,但要记得,快递员天天跑,盘点别太勤——不然仓库又要被搬空了。”

sigi=Hash(ci1∥ci2∥⋯∥cik)sig_i = \text{Hash}(c_{i1} \| c_{i2} \| \cdots \| c_{ik})
行签名公式:把第 i 行的 k 个字段首尾拼接,再计算哈希值。签名相同代表行内容相同,签名不同代表行内容有差异。
  1. 字段拼接将一行所有列值用分隔符连接成统一字符串。
    row_str=c1∥’,’∥c2∥⋯∥ck\text{row\_str} = c_1 \| \text{','} \| c_2 \| \cdots \| c_k
  2. 哈希压缩对拼接串计算定长指纹,任意微小改动都会让签名面目全非。
    sigi=MD5(row_str)sig_i = \text{MD5}(\text{row\_str})
  3. 分组比对按主键把源端与目标端签名放在一起比较,找出差异集合。
    D={i∣sigi(src)≠sigi(dst)}D = \{ i \mid sig_i^{(src)} \neq sig_i^{(dst)} \}
  4. 差异修正对差异行执行覆盖写入,目标端最终收敛到源端状态。
    target[i]←source[i],∀i∈D\text{target}[i] \leftarrow \text{source}[i], \forall i \in D

增量合并策略

快递送到总部后,不是往地上一扔就完事。库管员要拆开包裹、核对货号、查库存表——如果这个货号已经在货架上了,就把旧标签撕掉换上新的;如果货架上还没有,就找个空位摆上去。这个动作在数据库里有个学名:upsert(update + insert,更新或插入)。它由数据库的"INSERT …… ON CONFLICT DO UPDATE"这类语法实现,是增量合并的核心。

增量数据往往乱序、重复,比如同一笔订单被改了三次,快递员送了三个包裹。合并策略必须做到幂等——同一个包裹送十次,最终货架上的状态也只应该是最新一次的结果。用 upsert 处理时,主键(货号)冲突就更新,不冲突就插入,天然具备去重能力。对于需要保留历史的场景,比如流水表,则会改成“只在目标表追加新版本行”,用版本号区分,这就是历史保留策略。合并阶段的冲突处理要“温和”:以业务场景为准决定新值覆盖旧值,还是旧值保护新值。

小林看着货架上的标签焕然一新,满意地点点头。大刘拍拍他:“现在你手里有三样工具了——搬家的卡车、跑腿的快递员、还有月末的盘点员。问题来了:什么时候用哪个?”

INSERT INTO t VALUES (...)  ON CONFLICT (id) DO UPDATE SET c=EXCLUDED.c\text{INSERT INTO } t \text{ VALUES (...)} \; \text{ON CONFLICT (id) DO UPDATE SET } c = \text{EXCLUDED.}c
upsert 语法:尝试插入新行,如果主键冲突,则把指定列更新为新传入的值。一次操作完成“不存在就插入、存在就更新”的合并逻辑。
  1. 尝试插入先把增量行按主键写入目标表,如果主键不冲突则直接成功。
    INSERT→if no conflict, done\text{INSERT} \to \text{if no conflict, done}
  2. 冲突分支主键已存在时,执行更新替代插入,保留一行而不是报错。
    ON CONFLICT (id)→UPDATE\text{ON CONFLICT (id)} \to \text{UPDATE}
  3. 新值覆盖EXCLUDED 表示本次尝试插入时被挡在外的行,用它覆盖旧值。
    cnew=EXCLUDED.cc_{new} = \text{EXCLUDED.}c
  4. 幂等保证重复执行同一合并,最终行状态一致,不会重复累积。
    apply(x,apply(x,y))=apply(x,y)\text{apply}(x, \text{apply}(x, y)) = \text{apply}(x, y)

方案选型决策

小林有三个仓库,货量各不相同,大刘给他支了个招——选工具要看“货量、时效、预算”三件事。第一,数据量权衡:表只有几百行,直接全量,一分钟搞定;表有几个亿,天天全量就是灾难。第二,延迟要求匹配:老板要看实时大屏,必须上 CDC,让数据秒级流动;只要每日报表,时间戳增量就够。第三,成本资源评估:全量同步要占用大量带宽和数据库 I/O,增量同步要写解析逻辑、维护位点,CDC 还要多部署一套组件——每个选择都有隐性成本。

没有哪个方案是永远正确的“银弹”。工程上最常见的答案是混合策略兜底:首次用全量同步建立基线,日常用增量或 CDC 追平变化,每晚再用全量比对校验差异。小表直接全量,大表增量为主,核心交易表上 CDC,非核心报表用时间戳。选型评估可以量化为一个成本模型:总成本是数据总量、变更量、延迟要求的加权组合,权重由业务场景决定。

大刘给三个仓库分别开了“药方”:小仓库月结表走全量,中仓库订单表走时间戳增量,核心交易库上 CDC 加每日比对。小林照方抓药,数据链路终于不再半夜报警了。但大刘最后补了一刀:“方案定好了,链路要是没有监控,出了事你还在睡大觉。”

C=α×V+β×ΔV+γ×TdelayC = \alpha \times V + \beta \times \Delta V + \gamma \times T_{delay}
成本公式:总成本 = α×全量数据量 + β×变更数据量 + γ×可接受延迟。α、β、γ 是不同业务场景下的权重系数,权重不同,最优方案也不同。
  1. 全量成本项αV 表示全量拷贝带来的固定开销,V 越大权重越高。
    Cfull=αVC_{full} = \alpha V
  2. 增量成本项βΔV 表示维护增量链路的花费,与变更量成正比。
    Cinc=βΔVC_{inc} = \beta \Delta V
  3. 延迟惩罚项γT_delay 表示同步延迟超预期时的代价,实时性要求越严,γ 越大。
    Clate=γTdelayC_{late} = \gamma T_{delay}
  4. 决策原则求解最小化成本的方案,必要时组合多种手段。
    min⁡strategyC  ⇒  full / inc / cdc / hybrid\min_{strategy} C \; \Rightarrow \; \text{full / inc / cdc / hybrid}

同步链路设计

快递公司之所以能承诺“次日达”,是因为每一件包裹从揽收到签收,每个环节都有扫码记录。小林的数据链路也需要这样的“物流追踪系统”——链路监控告警。同步任务每跑完一轮,就上报同步行数、耗时、水位位置;如果某轮耗时爆炸、行数异常归零、水位停滞不进,监控系统立刻发短信告警。同步指标要统计数据质量校验结果:源端和目标端行数是否一致,主键是否重复,敏感字段是否为 NULL,这些都可以用校验 SQL 跑出来。

再好的链路也会出故障:数据库宕机、网络闪断、CDC 组件 OOM。设计时必须预留故障快速恢复路径:增量任务要记录水位,重启后从断点续跑;全量任务要支持断点重试,不能一失败就从头再来。把同步、校验、告警、恢复串成一个完整同步闭环,数据稳定性才有保障。衡量链路健康度的核心指标是 SLA(服务可用性),即一年中系统正常服务时间的占比——99.9% 意味着每年故障时间不超过 8.76 小时。

小林看着监控大屏上流动的同步曲线,第一次觉得数据像快递一样“看得见、追得着”。从搬家车到快递员,从盘点员到物流追踪,他终于明白:ETL 同步不是选一个工具那么简单,而是一套从采集、合并、校验到监控的完整系统工程。

SLA=Ttotal−TdownTtotal×100%SLA = \frac{T_{total} - T_{down}}{T_{total}} \times 100\%
SLA 公式:服务可用性 =(总时间 − 故障时间)÷ 总时间。99.9% 听起来很高,但折算成每年近 9 小时的停机,依然要求链路具备快速恢复能力。
  1. 可用时长正常运行时长等于总时长减去故障时长。
    Tup=Ttotal−TdownT_{up} = T_{total} - T_{down}
  2. 比率计算可用性百分比是正常运行时长占总时长的比例。
    SLA=TupTtotal×100%SLA = \frac{T_{up}}{T_{total}} \times 100\%
  3. 故障预算反过来算,给定 SLA 目标,允许的最长停机时间。
    Tdown≤(1−SLA)×TtotalT_{down} \leq (1 - SLA) \times T_{total}
  4. 闭环保障监控发现异常 → 告警通知 → 快速恢复 → 比对校验,形成完整循环。
    monitor→alert→recover→verify\text{monitor} \to \text{alert} \to \text{recover} \to \text{verify}

AI 应用场景

数据仓库 ODS 层初始化

阶段:数据接入与治理

解决的问题:企业新建数据仓库时,业务库的历史数据与每日新增数据需要统一入仓。传统手工导出导入无法保证一致性,且重复劳动严重;AI 时代的数据平台要求 ODS 层快速铺底并持续增量跟进。

具体案例:一家连锁零售企业搭建数据中台,数据工程师设计了“首次全量 + 每日增量”的调度策略:第一周用 Sqoop 全量抽取 20 张核心表到 ODS 层建立基线,之后每天按 updated_at 水位增量抽取当日变更,每晚再用全量比对任务校验一天的数据一致性。三个月后增量同步 0 漏数。

实时数仓 CDC 入湖

阶段:实时特征与流式处理

解决的问题:实时推荐和风控需要秒级看到用户行为变更,轮询同步延迟高且压垮源库。CDC 通过解析 binlog 实现近实时变更捕获,大幅降低对业务库的侵入。

具体案例:某电商平台的订单库接入 Canal 组件,实时监听 binlog 变更并投递到 Kafka,Flink 消费后写入数据湖 Iceberg 表。同步延迟从分钟级降到秒级,且无需业务表增加任何时间戳字段,订单取消等删除操作也能被准确感知。

电商订单增量合并

阶段:数据清洗与建模

解决的问题:订单状态频繁变更(待支付→已支付→已发货→已完成),同一订单多次更新会产生多条重复记录,直接追加会造成数据脏乱。使用增量合并策略可使目标表始终保持每条订单的最新状态。

具体案例:某跨境电商数仓团队用 Spark SQL 的 MERGE INTO 语法,把每日增量订单合并进订单明细宽表,按订单号去重,状态字段取最新时间戳的版本。配合删除标记位实现软删除,既保留完整历史又能快速查询当前状态。

Python 演示

python58 行
class ETLManager:    """ETL sync manager: full / incremental / compare strategies."""     def __init__(self, source, target, last_sync=0):        self.source = source        self.target = target        self.last_sync = last_sync        self.sync_count = 0     def full_sync(self):        """Full sync: copy every row from source to target."""        for row in self.source:            self.target[row['id']] = row.copy()            self.sync_count += 1        return self.sync_count     def inc_sync(self, ts_field='updated_at'):        """Incremental sync: pull rows newer than watermark."""        new_max = self.last_sync        for row in self.source:            ts = row[ts_field]            if ts > self.last_sync:                self.target[row['id']] = row.copy()                self.sync_count += 1                if ts > new_max:                    new_max = ts        self.last_sync = new_max        return self.sync_count     def full_compare(self):        """Full compare: scan source and fill missing / dirty rows."""        for row in self.source:            row_id = row['id']            if row_id not in self.target or self.target[row_id] != row:                self.target[row_id] = row.copy()                self.sync_count += 1        return self.sync_count  if __name__ == '__main__':    src = [        {'id': 1, 'name': 'toothpaste', 'price': 15, 'updated_at': 100},        {'id': 2, 'name': 'towel', 'price': 25, 'updated_at': 120},    ]    dst = {}    etl = ETLManager(src, dst)     print('full sync rows:', etl.full_sync())     src.append({'id': 3, 'name': 'cup', 'price': 30, 'updated_at': 200})    print('inc sync rows:', etl.inc_sync())     src[0]['price'] = 18    src[0]['updated_at'] = 300    print('inc update rows:', etl.inc_sync())     print('compare fix rows:', etl.full_compare())    print('target table size:', len(dst))
目的:用 Python 模拟一个 ETL 同步管理器,演示全量拷贝、时间戳增量抽取、全量比对补差三种策略的调度结果。
  1. self.target[row['id']] = row.copy()按主键把源行浅拷贝写入目标字典,避免引用共享。
  2. if ts > self.last_sync:增量窗口的核心判断,只抽取水位之后的新数据。
  3. self.last_sync = new_max同步结束后推进水位,确保下轮不重复取数。
  4. if row_id not in self.target or self.target[row_id] != row:全量比对发现缺失或内容不一致的行。
  5. etl.inc_sync()第三次调用时源表首行价格已更新,增量窗口捕获到这次修改。

常见误区

误区既然增量又快又省资源,那所有表都应该用增量同步,全量同步是老古董,能不用就不用。

正解增量同步依赖可靠的变更标记,如果源表没有时间戳字段、没有主键、也没有开启日志,增量方案根本无从下手。而全量同步逻辑简单、天然完整,几百行的小配置表全量跑完只要几秒,比维护一套 CDC 组件划算得多。工具没有高下,只有适不适合——小表用全量是明智,大表用全量才是灾难。

为何常见:初学者容易从“性能最优”这个单一维度做判断,忽略了数据量、表结构和运维成本这些约束条件。

误区给表加了 `updated_at` 字段就能高枕无忧,所有新增、修改、删除都能被增量同步发现。

正解updated_at 只能感知更新和新增。物理删除会把整行去掉,时间戳无从更新;如果业务代码漏写这个字段,或数据库时钟回拨,还会造成漏数和错序。正确的做法要么用触发器/应用层保证时间戳可靠更新,要么用 CDC 读日志来覆盖删除场景,再配合全量比对兜底。

为何常见:初看时间戳字段很直观,“查到比我上次记录的时间还新的数据就是新数据”,但数据库底层删除机制和业务代码的不确定性很容易被忽略。

误区数据库不是本来就有 binlog 吗?那 CDC 是不是零成本,直接就能用?

正解binlog 是数据库的底层日志,它不会主动把变更推送给你。要把它变成同步能力,得部署 Canal、Debezium 或 Flink CDC 等组件,还要管理位点、处理 DDL 变更、解决日志过期问题,运维成本不低。CDC 的价值在于用组件复杂度换取业务零侵入和实时性,而不是白捡的便宜。

为何常见:把“底层存在日志”等同于“上层具备同步工具”,混淆了基础设施和业务能力的边界。

误区既然全量比对能发现不一致,那就每隔一小时跑一次,保证数据永远正确。

正解全量比对要扫描整张源表和目标表,计算每行哈希并比对签名,其资源消耗不亚于一次全量同步。对几亿行的大表频繁执行,会对数据库造成极大压力,反而拖垮业务。正确的姿势是低频兜底——每天或每周一次,配合增量同步的即时性,才是成本与可靠性的平衡点。

为何常见:只看到比对带来的“正确性收益”,忽略了扫描全表带来的“性能成本”,缺少工程上的权衡思维。

误区上线时选定了增量同步方案,就一直用下去,反正调度没报错。

正解业务是动态的:订单表从百万行涨到十亿行,原来好用的时间戳增量开始严重延迟;新的核心库有秒级实时需求,原先的每日批次完全跟不上;甚至源数据库从 MySQL 迁到了 PostgreSQL,原来的 binlog 方案也得换。选型决策需要定期复盘,当数据量、延迟要求、成本预算发生量级变化时,就要重新评估全量、增量、CDC、混合策略的取舍。

为何常见:把技术选型当成一次性决策,忽视了数据规模和业务需求是持续变化的变量。

面试题

Q1. 以下哪种场景最适合使用全量同步?

单选题 · easy

  • A.亿级订单表的秒级实时同步
  • B.小型维表的一次性初始化← 正确答案
  • C.高频日志流的毫秒级同步
  • D.超大主表的无锁持续增删改

答案:B。全量同步简单可靠,适合数据量小、变更不频繁的维表初始化。亿级订单表和毫秒级日志流对实时性和性能要求极高,属于增量或 CDC 的主场。超大主表全量会长时间锁表,因此选 B。

Q2. 增量同步能正常工作的先决条件是什么?

单选题 · mid

  • A.目标表必须配备复合主键
  • B.源表存在可判定的变更标记← 正确答案
  • C.源表数据量必须超过百GB
  • D.目标库必须支持分布式事务

答案:B。增量同步依赖时间戳、自增序号或日志等“变更标记”来区分新老数据,这是唯一硬性前提。目标表主键和分布式事务与增量能力无直接关系,数据量大小也不是必要条件。故选 B。

Q3. 以下哪项是时间戳增量同步的经典陷阱?

单选题 · mid

  • A.修改操作会刷新修改时间列
  • B.物理删除无法触达时间戳← 正确答案
  • C.时间戳无索引也可高效过滤
  • D.多源系统时区差异毫无影响

答案:B。物理删除会直接移除整行数据,updated_at 字段无从体现,增量同步自然感知不到删除。A 是正常设计而非陷阱;C 中无索引过滤必然慢;D 中时区不统一会直接导致水位混乱。故选 B。

Q4. CDC 变更数据捕获相比传统时间戳轮询的优势有哪些?

多选题 · mid

  • A.实时性远高于定时轮询← 正确答案
  • B.对源库性能压力更小← 正确答案
  • C.无需数据库日志支持
  • D.能感知删除操作日志← 正确答案

答案:ABD。CDC 直接解析 binlog,无需扫描业务表,因此实时性高且对源库压力小,同时日志中包含 DELETE 事件,能感知删除。C 恰恰相反——CDC 必须依赖数据库日志,没有日志寸步难行。

Q5. 全量比对方案在 ETL 链路中的主要用途是什么?

单选题 · easy

  • A.实时写入高并发业务请求
  • B.发现增量同步遗漏差异← 正确答案
  • C.提升源库查询响应速度
  • D.替代业务主键的生成

答案:B。全量比对通过扫描源端并对比目标端,找出增量同步漏掉、改错或删除的数据并补齐,是数据一致性的“安全网”。它不承担写入加速、查询优化或主键生成职责,因此选 B。

Q6. upsert 合并语义的正确理解是?

单选题 · mid

  • A.存在则更新不存在则插入← 正确答案
  • B.只允许插入不允许更新
  • C.先全表清空再批量插入
  • D.仅更新已有忽略新记录

答案:A。upsert 是 update + insert 的合体,核心语义就是主键冲突时更新、无冲突时插入。B 是纯插入,C 是全量重建,D 是只更新不插入,都与 upsert 语义不符。故选 A。

Q7. 设计一条健壮的增量同步链路,必须考虑哪些因素?

多选题 · hard

  • A.变更顺序的一致性问题← 正确答案
  • B.重复数据的幂等处理← 正确答案
  • C.删除操作的传递同步← 正确答案
  • D.源库物理磁盘总空间

答案:ABC。增量链路要保证同一主键的多次变更按序落地,重复投递时结果一致(幂等),同时要把删除操作正确传递到目标端。源库磁盘总空间与增量链路设计没有直接关系,属于无关干扰项。

Q8. 基于 binlog 解析的 CDC 同步,核心依赖是什么?

单选题 · hard

  • A.依赖源库的查询优化器
  • B.依赖数据库事务日志位置← 正确答案
  • C.依赖目标库的存储过程
  • D.依赖网络路由设备配置

答案:B。CDC 同步依赖 binlog 中的事务日志位点(offset)来记录消费进度、实现断点续传。查询优化器只影响 SQL 执行计划,目标库存储过程和网络路由设备与变更捕获无关,故选 B。

Q9. 数据仓库 ODS 层首次接入海量业务数据时,推荐哪种策略组合?

单选题 · mid

  • A.全量初始化叠加增量追平← 正确答案
  • B.长期只跑增量不补基线
  • C.全量比对后放弃目标表
  • D.依赖手工导出文件灌入

答案:A。海量数据首次入仓应先做一次全量同步建立基线,再用增量或 CDC 持续追平新增变更,兼顾完整性与效率。B 没有基线无法工作,C 放弃目标表等于没做,D 手工灌入不可扩展。故选 A。

Q10. 以下哪些属于合理的全量与增量混合调度策略?

多选题 · hard

  • A.首轮全量建立数据基线← 正确答案
  • B.定期全量比对校正偏差← 正确答案
  • C.增量失败后自动降级重建← 正确答案
  • D.全量期间永久禁止增量

答案:ABC。混合策略的核心是“全量建基线、增量追变化、比对做校验”,增量链路故障时临时切回全量重建是常见的降级手段。D 中“永久禁止增量”会让数据永远停留在全量时点,违背混合调度的初衷,故不选。