分散式 SQL:把關聯式資料庫拆成全球可擴展的系統
Distributed SQL 想保留 SQL、交易與一致性語意,同時取得水平擴展、高可用與跨區部署能力。它不是單純把 PostgreSQL 放進多台機器,而是把 Query Planning、Sharding、Consensus、MVCC 與 Clock/Timestamp 重新組合成一個資料庫系統。
總覽與定位
Distributed SQL 的核心矛盾:SQL + ACID + Scale-out + 多區容錯,四者很難同時便宜取得。
傳統單機關聯式資料庫擅長複雜查詢、索引、Join、交易與強一致語意,但垂直擴展有上限;NoSQL 系統擅長水平擴展與高可用,卻通常要求你放棄部分 SQL 能力或交易語意。Distributed SQL / NewSQL 站在兩者中間:它把資料切成 Range / Tablet / Shard,使用 Raft 或 Paxos 複製每個分片,並在 SQL 層維持關聯式模型。
Distributed SQL 把「複寫一致性」與「分散式交易」產品化:Replication 決定單一分片如何複製,Transaction protocol 決定跨分片寫入如何一起提交,SQL layer 則把這些複雜度藏在查詢介面後面。
核心架構
一個 NewSQL 系統通常可拆成 SQL Gateway、Metadata、Range / Tablet、Consensus Replica 與 Transaction Layer。
面試時不要只說「它會自動 sharding」。更完整的架構回答應該說清楚:Client 連到哪裡、SQL 在哪裡被解析與規劃、資料如何定位到分片、寫入如何複製、跨分片交易如何協調,以及 metadata 變更如何被安全傳播。
| 層級 | 主要責任 | 常見風險 |
|---|---|---|
| SQL Layer | 查詢規劃、Join 策略、下推計算 | 跨分片 Join 造成大量 network shuffle |
| Storage Layer | Range split、LSM/B-Tree、secondary index | 熱 key、索引寫入放大、compaction 抖動 |
| Replication Layer | Raft/Paxos、leader election、log replication | 跨區 majority 讓 commit latency 被 WAN 主導 |
| Transaction Layer | 跨分片 atomic commit、timestamp ordering | 高衝突 workload 造成 retry storm |
架構重點:Distributed SQL 不是單一技術,而是多層協調機制的組合。
分片、複寫與交易
Distributed SQL 的 correctness 成本,主要來自「一次 SQL 寫入可能同時碰到多個 Consensus group」。
單一分片寫入相對直覺:找到該 key 所屬 range,把 mutation 發給 leaseholder,經過 Raft majority 後 committed。跨分片交易則更難,因為每個分片都有自己的 consensus log;系統必須保證「所有分片都提交」或「所有分片都撤銷」,避免只更新訂單沒有扣庫存。
Raft/Paxos 只保證單一 replica group 的 log 一致;跨多個 group 的 atomicity 仍需要 transaction protocol、timestamp ordering、conflict detection 與 retry。把「每個分片都強一致」誤認成「任意多分片交易都免費」是常見錯誤。
實務系統會努力把常見交易限制在單分片內,例如用 tenant_id 或 account_id 做 locality-aware primary key。當交易跨分片時,系統仍可保持 correctness,但 commit path 會穿越更多 replica group,latency 與 abort probability 都會上升。
時間戳與一致性讀取
全域排序是 Distributed SQL 的靈魂:讀寫要能回答「我看到的是哪個時間點的資料」。
Distributed SQL 常用 MVCC 讓同一筆資料保存多個版本,讀取以 timestamp 選擇可見版本。難點在於:多台機器沒有完美同步的時鐘,跨分片交易又需要一個能比較先後的 commit timestamp。不同系統會採用 TrueTime、Hybrid Logical Clock (HLC)、timestamp oracle,或透過 lease 與 uncertainty interval 限制時鐘不確定性。
| 機制 | 用途 | 代價 |
|---|---|---|
| MVCC Snapshot | 讀取某個 timestamp 前已提交的穩定版本 | 需要保存歷史版本並處理 GC / compaction |
| HLC / TrueTime | 產生可比較的 commit timestamp | 時鐘不確定性可能導致等待或重試 |
| Follower Read | 從本地副本讀較舊但一致的 snapshot | 必須接受 staleness bound,例如讀 5 秒前資料 |
| Serializable Retry | 衝突時重跑交易以維持隔離語意 | 高爭用 workload 的尾延遲與應用複雜度上升 |
跨區讀取可以從本地 follower 讀取已確定不會被未來寫入推翻的舊 snapshot;這能大幅降低 read latency,但產品必須接受 bounded staleness。若「付款完成後立刻看到最新狀態」是硬需求,仍要用 leader/leaseholder read 或 session token 保護。
互動:把一條 SQL 拆成分散式執行計畫
同一條語句,換個 key、換個切分、換個客戶端所在區,執行計畫與延遲組成就完全不同。
下方的拆解器不播劇本:它先用你給的 key 區間去對照 range 邊界,算出這條語句會落在哪幾個 Raft group,再逐階段攤開每一層做了什麼、花掉幾次跨區往返、哪些往返可以並行。三個最值得盯的翻轉:觸及一個 range 的寫入走 1PC、觸及兩個以上就必須走 2PC;扇出的多個 range 是並行的,關鍵路徑取最慢那一個,不是加總;Follower Read 是逐 range 判定的——同一條 scan 完全可能一半由本地副本服務、一半還是得跨區找 leaseholder。
SELECT …
尺上的邊界是由「切分數」算出來的,不是畫死的;上方括號是 WHERE 的 key 區間,被它蓋到的 range 才會進入執行計畫。每段下方標的是實際服務者(F=本地 follower、L=leaseholder)。時鐘模型採 HLC,所以計畫裡沒有 commit wait;若換成 TrueTime,提交後還要多等一段 2ε 的 commit wait 才能回覆客戶端。
扇出那一列的細條是每個 range 各自的往返,它們共用同一段 x(同時發出),所以該階段的長度是取最慢的一條,不是把它們加總。虛線框的 intent resolve 畫在總延遲右側,代表它是背景清理、不計入關鍵路徑,但仍然要花 RPC。扇出列裡的 quorum 等待,就是 Leader Election 與 Raft 篇談 commitIndex 要靠 majority ack 才能推進的那一段。
觀察重點:把區間寬度從 1 拉大到跨越切分邊界的那一刻,提交協定會從 1PC 翻成 2PC、計畫多出一段 transaction record 往返——這正是「跨分片交易貴在哪裡」的具體數字。
選型與風險
NewSQL 適合需要強語意與水平擴展的核心資料,不適合把所有資料庫問題都一鍵外包。
Distributed SQL 最適合的場景,是你同時需要 relational model、交易、一致性、高可用與資料量/地理範圍擴展。例如金融帳務、訂單狀態、庫存、身份與多租戶 SaaS metadata。但如果 workload 主要是 append-only analytics、超高吞吐 event ingestion、或可以接受 eventual consistency 的 key-value lookup,專用 OLAP、log system 或 NoSQL 可能更簡單。
| 適合選用 | 謹慎選用 | 設計提醒 |
|---|---|---|
| 核心交易資料需要 ACID 與 HA | 大量跨分片 Join / aggregation | 把 transaction boundary 與 partition key 一起設計 |
| 多區部署且需要自動 failover | 每次寫入都要求全球同步低延遲 | 用 locality-aware schema 把常見路徑留在同區 |
| 團隊希望保留 SQL 生態 | 極端熱 key 或高衝突 counter | 對熱點使用分桶、預聚合或重新建模 |
如果 schema 沒有 locality 設計、交易任意跨租戶、或 secondary index 過多,Distributed SQL 會把問題從「單機容量」轉成「跨節點協調」。系統仍然正確,但 payload、latency 與 retry rate 可能快速上升。
先說需求:資料量、交易語意、地理分布與 latency SLA。再說架構:SQL Gateway、Range sharding、Consensus replication、MVCC timestamp。最後說取捨:單分片快、跨分片慢,Follower Read 可降讀延遲但有 staleness,熱點與 retry 是主要風險。