快轉到主要內容
  1. Core/

Redis 與 SQL 數據同步全解析|5 大情境的讀寫流與consistency策略

Idle Engineer
作者
Idle Engineer
AI Runs. I Nap. | 404 Career Not Found
目錄

TL;DR | 面試情境模擬
#

👴 面試官:Redis 和 SQL 同時使用時,資料要怎麼同步?哪些場景要設 TTL,哪些不能設?

🧑‍💻 :核心是先判斷資料屬性:短期狀態可設 TTL,核心資產不能隨便過期。Session 適合用 Redis TTL;排行榜與共同好友通常 SQL 保存真實資料、Redis 負責即時查詢;瀏覽數用 Redis 抗高併發,再由 Worker 批次回寫 SQL;購物車屬於用戶資產,不應短 TTL,通常結帳時才同步成訂單。


情境一:使用者登入(Session)
#

核心概念:臨時狀態驗證,需保證consistency。

📊 data-flow (Data Flow)
#

  1. 寫入時 (Write)
    • MySQL:執行 INSERT INTO sessions,作為登入紀錄的最終真相(Source of Truth)。
    • Redis:執行 SETEX session:1001 3600 "data"。利用 TTL (Time To Live) 設定過期時間,時間一到自動刪除,無須手動清理。
  2. 讀取時 (Read)
    • 先查 Redis GET session:1001。若有(Cache Hit)直接驗證;若無(Cache Miss)則查 MySQL 並回寫 Redis。
  3. 同步策略 (Logout)
    • 使用者登出時,先刪除 Redis Key,再更新 MySQL 狀態

情境二:遊戲排行榜(Leaderboard)
#

核心概念:Sorted Set (ZSet) 的跳躍表結構。

🤔 為什麼 Redis 能「即時排序」?
#

  • MySQL 瓶頸ORDER BY 是「事後排序」,數據量大時需要全表掃描與排序,效能極差。
  • Redis 優勢:底層使用 Skip List (跳躍表)。執行 ZADD 時,Redis 會根據分數 (Score) 直接將成員插入到正確位置,維持寫入即有序

📊 data-flow
#

  1. 寫入時 (Dual Write)
    • MySQLUPDATE user_scores SET score = 9550 (永久保存紀錄)。
    • RedisZADD game:rank 9550 "user_1001" (即時更新排名)。
    • 註:此類高頻變動數據通常「以 Redis 為準」顯示,MySQL 僅作備份。
  2. 讀取時
    • ZREVRANGE game:rank 0 9:直接從跳躍表截取前 10 名,O(log N) 複雜度,極快。

情境三:文章瀏覽數(高併發寫入)
#

核心概念:Redis 扛流量,後台排程 (Worker) 批次回寫。

🤔 為什麼要用 Worker 與 SCAN?
#

  1. Worker (背景任務):一個在伺服器後台定期運行的腳本(如每 5 分鐘一次)。負責將 Redis 的暫存數據同步至 MySQL,避免前端請求直接阻塞database。
  2. SCAN 指令
    • 使用 SCAN 0 MATCH article:*:views COUNT 100
    • COUNT 100 代表「分批提示」,告訴 Redis 每次只掃描約 100 筆 Key。因為 Redis 是單執行緒,若用 KEYS 一次性遍歷所有 Key 會導致服務卡頓 (Blocking),SCAN 則能非阻塞地分批處理。

📊 data-flow
#

  1. 寫入時 (高併發)
    • RedisINCR article:888:views (原子操作,無鎖累加)。
    • MySQL不操作。避免高併發下的行鎖 (Row Lock) 效能瓶頸。
  2. 同步時 (定時)
    • Worker 讀取 Redis 值 (如 500),解析文章 ID。
    • 執行 SQL:UPDATE articles SET view_count = view_count + 500 WHERE id = 888
      • 註:這是標準 SQL 語法,database會在內部讀取舊值加上 500 並寫入,避免程式計算的時間差問題。
    • 重置 Redis:執行 SET article:888:views 0
      • 註:必須重置。若不重置,下次會將「舊數據」再次累加進 MySQL,導致雙倍計數。

情境四:共同好友(集合運算)
#

核心概念:Set 交集運算,取代複雜 SQL 子查詢。

🤔 為什麼比 SQL JOIN 快?
#

若要找出 Alice (1001) 和 Bob (1002) 的共同好友:

  • SQL 做法:需要用到子查詢或 IN 語法,當好友列表龐大時,查詢效率會隨資料量下降。
    -- 找出 Alice 的好友,且該好友也在 Bob 的好友列表中
    SELECT friend_id FROM friendships 
    WHERE user_id = 1001 
    AND friend_id IN (SELECT friend_id FROM friendships WHERE user_id = 1002);
    
  • Redis 做法SINTER user:1001:friends user:1002:friends。直接在記憶體中計算數學交集,速度極快。

📊 data-flow
#

  1. 寫入時 (雙向寫入)
    • MySQLINSERT INTO friendships ... (建立關係)。
    • Redis:應用程式需同時更新雙方的 Set (雙寫)。
      SADD user:1001:friends "1002"  -- 把 Bob 加入 Alice 的好友圈
      SADD user:1002:friends "1001"  -- 把 Alice 加入 Bob 的好友圈
      
  2. 讀取時
    • SINTER user:1001:friends user:1002:friends -> 瞬間回傳共同好友 ID 列表。

情境五:購物車(狀態persistence)
#

核心概念:頻繁局部修改,不應設 TTL。

🤔 為什麼購物車不能過期?
#

購物車屬於用戶資產。使用者可能幾天後才結帳,若設 TTL (如 1 小時),過期後購物車清空會嚴重影響體驗。實務上通常不設 TTL 或設定極長的過期時間。

📊 data-flow
#

  1. 寫入時
    • RedisHSET cart:1001 product_A 3。利用 Hash 結構,可直接修改單一商品數量,無需讀取整個購物車。
    • MySQL暫時不寫。購物車屬於「中間狀態」,頻繁寫入database成本過高。
  2. 讀取時
    • HGETALL cart:1001
  3. 同步時 (結帳觸發)
    • 使用者點擊結帳 -> 讀取 Redis 內容 -> 生成訂單寫入 MySQL -> 刪除 Redis cart:1001

💡 總結:一眼看懂data-flow與同步策略
#

情境 Redis 結構 為什麼用 Redis? 數據同步策略 (Sync Strategy)
Session String (Key-Value) TTL 自動過期,加速驗證 Cache Aside:登出時刪 Redis,保持與 DB 一致。
排行榜 ZSet (跳躍表) 寫入即排序,無需事後 ORDER BY 雙寫:MySQL 存紀錄,Redis 負責即時顯示。
瀏覽數 String (Counter) 原子操作 INCR,無鎖抗高併發 Worker 批次回寫:定時累加至 MySQL 後重置 Redis,不設 TTL
共同好友 Set (集合) 記憶體交集運算遠快於 SQL 子查詢 雙寫 (雙向):新增好友時需同時寫入雙方的 Set。
購物車 Hash (字典) 支援局部修改 (HINCRBY),無需讀寫整包 結帳觸發:平時只存 Redis,結帳時寫入 MySQL 並清空。