一台資料庫,真的可以一直加大嗎?
剛開始做網站時,資料庫通常只有一台。使用者、文章、訂單、留言,全都存在同一個 PostgreSQL、MySQL 或其他資料庫裡。資料量不大時,這是最簡單也通常最正確的設計。
流量增加後,我們可以先升級 CPU、RAM、SSD,或改善 SQL、加入 Index、Cache 和 Read Replica。這些方法都能延長單一資料庫的壽命。
但硬體不可能無限升級。
假設一個服務累積了數十億筆資料,而且每秒都有大量寫入。即使讀取可以交給 Replica,所有主要寫入仍可能集中在一個 Primary。這時問題不再只是「查詢夠不夠快」,而是單一資料庫本身已經成為容量與吞吐量的邊界。
其中一種解法,就是 Sharding。
Sharding 是什麼?
Sharding 可以翻成「分片」。核心概念很直接:
不要把所有資料放在同一個資料庫,而是依照某個規則,把不同資料分配到不同資料庫。
例如一個服務有 3 億名使用者,可以粗略想成:
Shard A:使用者 1~1 億
Shard B:使用者 1 億~2 億
Shard C:使用者 2 億~3 億
每個 Shard 都保存整體資料的一部分,而且通常可以獨立處理查詢與寫入。
這與 Replication 有一個重要差異。
Replication 是「同一份資料做多個副本」;Sharding 則是「不同資料拆到不同節點」。
如果原本有 900 GB 資料,做三份 Replica 並不會讓每個節點只存 300 GB;每個 Replica 原則上仍要保存那 900 GB。若使用 Sharding,則可能讓三個 Shard 各自負責約 300 GB。
所以兩者解決的問題不同,也經常一起使用:每個 Shard 本身還可以再配置 Replica,提高可用性與讀取能力。
真正困難的問題:資料要怎麼分?
建立三台資料庫並不難。困難的是決定「某一筆資料應該去哪一台」。
這個決定通常依靠 Shard Key。
例如以 user_id 作為 Shard Key:
應用程式或中介層收到請求後,先根據 Shard Key 判斷資料在哪裡,再把 SQL 送到正確的 Shard。
因此 Shard Key 的選擇非常重要。選錯之後,重新分配數十億筆資料可能是一場昂貴而高風險的 Migration。
Range-based Sharding
最容易理解的方法,是按照範圍切分。
例如:
優點是規則直觀,而且查詢某段連續範圍時很好判斷資料位置。
但它有一個明顯風險:Hotspot。
假設 user_id 持續遞增,新註冊使用者永遠落在最新的範圍,那麼大量新寫入可能全部集中到最後一個 Shard。雖然你有很多台資料庫,真正忙碌的卻只有其中一台。
Hash-based Sharding
另一種常見方式,是先對 Shard Key 做 Hash,再決定資料位置。
簡化來看,可以想成:
shard = hash(user_id) % 4
結果為 0 的資料進 Shard 0,結果為 1 的進 Shard 1,以此類推。
這通常能讓資料分布得比較平均,降低某個連續 ID 區間形成 Hotspot 的機率。
但新的問題也出現了。
如果原本只有 4 個 Shard,後來增加到 5 個,取模結果會改變,大量資料的位置可能因此需要重新計算。這會造成龐大的資料搬移成本。
因此實際的大型系統常會使用更進階的映射方式,例如 Consistent Hashing、Virtual Shard 或獨立的 Shard Map,避免每次擴容都重搬幾乎全部資料。
什麼是 Hot Shard?
即使資料筆數平均,負載也不一定平均。
假設社群平台依 user_id 分片,而某個 Shard 剛好放了幾個超大型帳號。它們每秒產生的讀寫量遠高於普通使用者,那個 Shard 就可能比其他節點忙很多。
這叫做 Hot Shard。
所以好的 Sharding 不是只看「每台有幾筆資料」,還要考慮資料的存取模式。
100 GB 很少被讀取的歷史資料,可能比 5 GB 每秒被大量更新的熱門資料更輕鬆。
Cross-shard Query 為什麼麻煩?
單一資料庫的方便之處,是資料都在同一個地方。
例如:
SELECT * FROM users ORDER BY created_at DESC LIMIT 100;
如果所有 users 都在同一個資料庫,資料庫可以直接執行這個查詢。
但如果 users 被拆到 20 個 Shard 呢?
系統可能必須:
向 20 個 Shard 分別查詢;
取得每個 Shard 的結果;
在應用層或中介層合併;
重新排序;
最後才取出真正的前 100 筆。
這就是 Cross-shard Query 的成本。
JOIN 也會變得更棘手。如果 users 在 Shard A,而某些相關資料位於 Shard C,原本資料庫內部一次完成的 JOIN,可能變成跨網路的多次查詢。
因此採用 Sharding 後,資料模型往往也要跟著改變。設計者會盡量讓經常一起查詢的資料落在同一個 Shard,減少跨 Shard 操作。
Transaction 也不再那麼簡單
假設銀行帳戶 A 在 Shard 1,帳戶 B 在 Shard 2,要完成一筆轉帳:
Shard 1 扣款成功,但 Shard 2 入帳失敗,怎麼辦?
在單一資料庫中,可以用 Transaction 和 ACID 機制處理。但跨越多個獨立資料庫後,原本的單機 Transaction 不再足夠。
系統可能需要 Distributed Transaction、Two-Phase Commit,或採用 Saga 等設計,把操作拆成多個步驟並準備補償機制。
這正是 Sharding 的核心代價之一:你得到水平擴充能力,但同時進入分散式系統的世界。
Rebalancing:增加 Shard 之後怎麼辦?
假設系統原本有 4 個 Shard,每台都快滿了,因此加入第 5 台。
新 Shard 不會自動讓舊資料平均消失到新節點。系統需要把部分資料從舊 Shard 搬到新 Shard,這個過程稱為 Rebalancing。
而搬資料時,網站通常不能停機。
因此真正的 Rebalancing 可能同時涉及:
複製既有資料;
同步搬移期間產生的新寫入;
切換 Routing;
驗證兩邊資料一致;
最後才移除舊位置的資料。
如果資料量是數 TB,這個過程可能持續很久。Sharding 因此不只是資料庫 schema 的問題,也是營運與基礎設施問題。
Sharding 和 Partitioning 一樣嗎?
兩者概念很接近,都是把資料切成多個部分,但使用語境通常不同。
Database Partitioning 常指同一個資料庫系統內部的資料切分。例如 PostgreSQL 可以把一張巨大資料表依日期切成多個 Partition,但仍由同一套資料庫系統管理。
Sharding 通常強調資料被分散到多個獨立資料庫節點,甚至不同伺服器。
可以把它粗略理解成:
Partitioning:一個資料庫裡分區管理。
Sharding:跨多個資料庫水平分片。
實際產品的術語可能略有不同,因此看文件時仍要確認該系統對 Shard 與 Partition 的定義。
Sharding 不是越早做越好
看到大型服務使用 Sharding,很容易產生一個錯覺:既然未來可能變大,那一開始就分片不是更專業嗎?
通常不是。
Sharding 會直接增加:
開發複雜度;
資料遷移難度;
查詢限制;
跨節點 Transaction 成本;
備份與還原複雜度;
監控與故障排除成本。
如果一台合理配置的資料庫就能處理目前負載,先保持單一資料庫通常更容易維護。
在考慮 Sharding 前,往往應先確認:
SQL 是否真的已經優化?
索引是否合理?
是否能把大量讀取交給 Cache?
是否能使用 Read Replica?
是否能先做 Partitioning?
是否只是某幾個 Query 特別慢?
硬體是否真的已接近合理上限?
只有當瓶頸確實需要水平拆分時,Sharding 才值得承擔它帶來的複雜度。
一個完整的請求會怎麼走?
假設一個平台以 user_id 作為 Shard Key。
使用者 81273 開啟自己的個人頁面時,流程可能是:
瀏覽器送出 request;
API Server 驗證身分;
Routing Layer 取得 user_id = 81273;
Shard Map 判斷它屬於 Shard 3;
Query 被送到 Shard 3;
Shard 3 回傳資料;
API Server 組合 response;
瀏覽器顯示頁面。
如果這筆請求還需要另一個 Shard 的資料,流程就會多出額外的網路 round trip、錯誤處理與結果合併。
所以 Sharding 的真正重點不是「多開幾台資料庫」。
它是在回答一個更難的問題:當資料已經不能由單一節點舒服地承擔時,我們要如何決定每一筆資料住在哪裡,並讓整個系統仍然像一個完整產品運作?
最後整理
Sharding 是把不同資料水平拆到多個資料庫節點的架構,用來突破單一資料庫的容量或吞吐量限制。
Shard Key 決定資料位置;Range-based 與 Hash-based 是常見分片方式;Hot Shard、Cross-shard Query、Distributed Transaction 和 Rebalancing 則是採用 Sharding 後必須面對的主要問題。
Replication 是複製相同資料,Sharding 是拆分不同資料;Partitioning 通常是在單一資料庫系統內切分資料。三者可以一起存在,但目的不同。
最重要的是:Sharding 並不是讓資料庫「自動變快」的按鈕。它是用更高的系統複雜度,換取水平擴充能力。
當單一資料庫還能可靠地解決問題時,保持簡單通常更好;真正撞到單機邊界時,再把資料拆開,才是 Sharding 應該出現的時機。
把概念放回真正的系統流程裡看,通常比只背名詞更容易理解它。