你是小阿巴,剛入職一家電商公司。

第一天上班,老闆就交給你一個艱鉅的任務:定期把公司的訂單數據同步到數據分析倉庫。
一聽到數據同步這 4 個字,你立刻汗流浹背了。

你的哥哥程序員魚皮,曾經就是在大公司負責數據同步。結果雙十一當天,近 2 小時的訂單數據沒有同步過去。數據分析團隊看到的數據是 2 小時前的,以為銷量沒達到預期,就沒有及時給熱銷商品補貨。最終錯失了 1 個多億的銷售額!魚皮也因此被老闆優化掉了。

作為一名程序員,怎能輕易退縮?你握緊拳頭,一定要完成好這個任務!

建議觀看本文對應視頻版:https://bilibili.com/video/BV1Xvnoz7EKJ
全量同步
什麼是數據同步呢?就像你有兩個手機,要把其中一個手機的照片複製到另一個手機裏。數據同步就是把一個數據庫的數據,定期複製到另一個數據庫裏。
你心想:這還不簡單嗎?我寫個定時任務,每天 把整個訂單表的數據 全部查出來,然後 一股腦插入 到數據倉庫!

這種方法叫 全量同步,不管數據有沒有變化,每次都把所有數據重新複製一遍,簡單粗暴。
# 查出全部訂單
orders = db.query("SELECT * FROM orders")
# 清空已有老數據
warehouse.execute("DELETE FROM orders")
# 插入新值
warehouse.insert(order)
第一天,公司有 1 萬條訂單數據。你的程序跑了 3 個小時,同步成功!
老闆看着數據誇到:不錯啊小阿巴,開發神速啊!

你內心暗爽:高端的功能往往只需要最簡單的代碼。

基礎告警
第二天一早,你還沒到公司,就收到了運營小姐姐的奪命連環 call:“小阿巴!出大事了!昨天晚上的數據同步失敗了,現在老闆還沒看到昨天的數據,他正在會議室發火呢!”

你意識到:現在的程序是不可靠的,如果因為網絡等原因同步失敗了,可能要到第二天才會發現。
於是你給程序加了一段邏輯:如果同步失敗,會記錄錯誤日誌,同時發郵件通知管理員,並且把數據庫回滾到同步前的狀態。

這樣如果出了問題,你會比老闆先發現,手動重新執行一遍同步任務就好。
增量同步
過了幾天,老闆黑着臉找你了:“小阿巴,為什麼今天數據還沒同步完?”
你看了下訂單數據,立刻發現了問題。現在有 10 萬條數據,程序跑了 30 個小時還沒結束;這樣下去,如果有 100 萬條數據,估計要 300 個小時!
老闆:少廢話,快點解決,不然送你到隔壁餐飲部沉澱沉澱。

你開始思考:既然全量同步太慢,那我只同步每天新增的訂單不就行了?

這就是 增量同步,約定每天 0 點執行同步任務,只同步昨天 0 點之後創建的訂單。

這樣大大減少了每次同步的數據量,任務的執行時間不再線性增加了。
但很快,你發了一個問題,訂單的狀態是會發生變化的,比如用户付款後又退款。
如果使用訂單創建時間作為增量同步的分界標誌,只能同步新增的訂單,但是訂單狀態的更新不會同步!

於是,聰明的你使用 updated_time 字段進行增量同步,這個字段會在每次數據變化時自動更新。

這樣新增和修改的數據都能同步了!
你內心竊喜:聰明如我小阿巴,老闆誇我好開發。

批處理
過了幾天,公司搞了一波大促活動,當天訂單量比平時多了幾倍,老闆請大家通宵狂歡開 party。
結果正在你歡唱 “只因你太美” 的時候,突然公司的運維大叫:不好了不好了,我們的項目服務器卡死了,用户無法下單!
你看了看時間,0 點多,不正是數據同步任務的執行時間麼?你瞬間汗流浹背,放下麥克風,趕回公司修 Bug。
原來是因為一次查詢出來的訂單數據太多了,都加載到內存,導致服務器 OOM 內存溢出了。

你悔不當初:唉,早該想到這個問題,既然單次處理數據量太大有風險,那我就 分批處理。
每 100 條數據為一批,每次只從數據庫中分頁查詢出這一批數據,同步完這一批,再執行下一批。

這樣每次只處理少量數據,內存壓力小;而且如果某一批處理失敗,只需要回滾和重新同步這一批,而不是全部重來。
問題算是解決了,但是今夜你徹夜難眠,你似乎成了公司業務增長後,唯一不開心的那個人。

希望今後不會再遇到這種鬧心事了吧。
遊標機制
但生活總是這樣,屋漏偏逢連夜雨。兩天後,你又收到了老闆的咆哮:“狗阿巴,這就是你做的數據同步?你自己看看丟了多少數據!”
你大驚,丟失數據?不應該啊。。。
經過仔細排查,你發現了問題:如果在分頁查詢的過程中,有新的數據被插入或更新,就會導致數據偏移和丟失!
比如查詢第 1 頁時,符合條件的訂單有 4 條:
訂單 A:updateTime=08:00
訂單 B:updateTime=09:00
訂單 C:updateTime=09:30
訂單 D:updateTime=09:50
執行第 1 頁查詢,每頁 2 條,偏移量為 0:
SELECT * FROM orders
WHERE updated_time >= '2025-09-08' and updated_time < '2025-09-09'
ORDER BY updated_time
LIMIT 2 OFFSET 0; -- 每頁 2 條,第 1 頁
查詢結果返回訂單 A(08:00)和訂單 B(09:00),同步完成後將偏移量調整為下一頁 OFFSET=2。
可就在查詢下一頁前,訂單 B 因為 “修改訂單狀態” 被更新,這時訂單 B 的更新時間就不在查詢範圍內了。
這就導致查詢下一頁 OFFSET=2 時,直接跳過了前兩條訂單 A 和 C,從 D 開始同步,導致訂單 C 丟失。

怎麼解決這個問題呢?
這可難不倒你小阿巴,既然 動態數據集 中使用 SQL 自帶的 OFFSET 偏移作為分頁起點會出問題,那不妨 自定義一個標誌來記錄下一批要同步的起點。
這就是遊標機制。
比如我約定自增的主鍵 id 作為遊標,每查詢一批數據之後,把這批數據的最後一條記錄作為新的遊標值;查詢下一批數據時,只查詢 id > 遊標的數據。

這樣不僅防止數據丟失,還避免了 OFFSET 深度分頁帶來的性能問題。
此外,如果把遊標想象成進度條的斷點,可以更清晰地記錄同步進度,失敗後可以從斷點繼續。
做完這一通優化後,你長吁了一口氣,工作看來是保住了。

性能優化
你以為終於可以安穩一段時間了。但沒想到,公司的好日子才剛剛開始。
隨着老闆大力擴張業務,公司每天的訂單量可以達到百萬條,你的同步任務每次要跑幾個小時才能完成。
更要命的是,老闆現在對數據的依賴越來越強,要求每隔 2 個小時就要同步一次數據!
怎麼能夠讓任務執行更快呢?
這時,你想起了曾經在

首先,修改插入數據庫的操作方式。之前是一條一條地插入訂單數據,改為執行一條批處理語句來同時插入同一批內多個訂單數據,減少了跟數據庫通信的次數,性能大幅提升。

但是你覺得還不夠快,於是優化了批處理的流程。之前是等一批同步完再同步下一批,串行執行;現在你啓動了多個線程,每個線程負責處理一批訂單的同步,多個線程可以同時搬磚幹活,效率大幅提高。

這一通操作下來,老闆都激動了:“小阿巴,你這技術可以啊!好好幹,我給你升職加薪!”
你笑了,有了老闆這句話,你想繼續努力給公司賣命了。

實時同步
兩年後,隨着你頭髮的消逝,公司總部決定要上市了!

老闆找到了你:“阿巴阿巴,咱們的數據同步能不能做到實時?我想給投資人展示實時的業務監控。最好是用户剛下單,投資人立刻就能在大屏幕上看到!"

你早料到會有這個需求,帥氣地甩出一句話:“交給我吧,我讓你見識一下企業級方案!”

這兩年你也沒閒着,像實時數據分析這種典型需求的實現方案,你早已爛熟於心。
普通的定時任務已經無法滿足實時性的要求,只能搬出 CDC + 消息隊列 這兩大殺器了。
CDC(Change Data Capture)就像給數據庫安裝了一個 24 小時實時監控的攝像頭,數據有任何變化,攝像頭都能立刻發現。
消息隊列就像一個快遞中轉站。數據庫變化的消息先發送到這個中轉站,然後同步數據的程序從中轉站取出數據並寫入到數據分析倉庫中。

有了中轉站後,如果消息特別多,程序來不及處理,也不會丟失數據。
實現了這套方案之後,用户只要一下單,100 毫秒內老闆就能在儀表盤上看到。而且保險起見,你還給消息隊列加了個監控,如果消息堆積太多,就會自動告警。
他開心得像個孩子:“小阿巴,我給你升職加薪,還給你帶新人!”
你又笑了,開始幻想着你和新人坐在高高的辦公桌上,你給他講述自己光輝事蹟,他向你投來羨慕的目光。

方案完善
半個月後,公司迎來了雙十一大促活動。
你正在和新來的實習生阿坤吹牛皮,沒想到瞬間產生了大量訂單,像洪水一般,讓你的實時同步系統出現了各種異常:
-
有些訂單重複同步了
-
有些訂單的狀態變化順序錯亂了
-
還有大量消息堆積在消息隊列裏處理不過來,導致數據沒有及時同步

監控大屏上各種告警此起彼伏,老闆的臉色越來越難看:“狗阿巴,這就是你説的企業級方案?”
這一刻,你有點恍惚:“我只是知道可以這麼實現,但沒人告訴我出了這些問題怎麼解決。。。”
這時,你身旁的實習生阿坤説話了:“我來!”

只見他對着電腦一通操作,嘴裏振振有詞:
1)消息重複問題,可以通過冪等機制解決。給每條消息一個唯一編號,處理過的消息編號記錄下來,就不會重複處理了。

2)消息亂序問題,可以通過分區解決。把相關的消息放到同一個分區,保證同一個訂單的消息按順序處理。

3)消息堆積問題,可以通過搭建集羣和動態擴容,增加對消息的處理能力。

“這些點在使用消息隊列來實現數據同步的時候就要考慮到才對呀!”
你聽着阿坤説的話,默默低下了頭,內心五味雜陳,這就是應屆生的水平麼?
阿坤看着你的代碼接着説:“不是哥,做個數據同步要這麼麻煩麼?DataX、Canal、Debezium,這麼多現成的企業級數據同步工具你不用?非要自己寫代碼?”

你無言以對,內心感受到了來自實習生的一噸暴擊。
阿坤甚至一臉嫌棄地看着你:“不是哥們,連數據對賬都不做麼?你自己都不定期檢查同步前後的數據是否一致麼?我這有份數據同步的企業級方案,你好好看看吧。”


老闆拍了拍阿坤的肩膀:“小夥子優秀,導師的導師,今後公司的未來就靠你了,我給你升職加薪。至於老阿巴,你好自為之吧。”
這個城市,又多了一個傷心的人,給個點贊收藏三連,幫忙回回血吧。
