高併發資料庫設計:讀寫分離、分庫分表與 Redis 快取策略|2025
高併發資料庫設計:讀寫分離、分庫分表與快取策略
前言:資料庫是高併發系統最常見的瓶頸
系統變慢了。你打開監控一看,CPU 還好、記憶體還好、網路還好。
問題出在資料庫。
查詢堆積、連線數飆高、回應時間從 50ms 變成 5 秒。這是高併發系統最常見的場景。
根據經驗,90% 的系統效能問題出在資料層。
本文將帶你從瓶頸分析開始,一路講到讀寫分離、分庫分表、Redis 快取設計,以及那三個讓人頭痛的快取問題:穿透、擊穿、雪崩。
如果你還不熟悉高併發的整體架構,建議先閱讀高併發是什麼?完整指南。
一、資料庫瓶頸分析
在優化之前,你需要知道問題出在哪裡。
1.1 連線數限制
每個資料庫連線都要佔用記憶體。MySQL 預設最大連線數是 151,PostgreSQL 是 100。
當併發請求超過連線數上限,新的請求只能等待。等待太久就超時,用戶看到錯誤。
症狀:
Too many connections錯誤- 應用程式拿不到連線
- 請求排隊等待
解法:
- 使用連線池(HikariCP、PgBouncer)
- 調高 max_connections(但有上限)
- 讀寫分離,分散連線
1.2 讀寫競爭
資料庫有鎖機制。寫入時可能鎖住整張表或整行,其他請求只能等。
高併發場景下,熱點資料被頻繁讀寫,鎖競爭變得嚴重。
症狀:
- 大量請求處於
waiting for lock狀態 - 寫入操作卡住讀取
- 資料庫 CPU 使用率飆高但吞吐量不高
解法:
- 讀寫分離(讀不影響寫)
- 樂觀鎖取代悲觀鎖
- 減少鎖持有時間
- 熱點資料放快取
1.3 單機容量上限
一台資料庫能存多少資料?能處理多少 QPS?都有上限。
當資料量達到幾億、幾十億筆,單機查詢效能會顯著下降。
症狀:
- 查詢時間隨資料量增加
- 磁碟空間不足
- 備份還原時間過長
解法:
- 分庫分表
- 歸檔舊資料
- 使用分散式資料庫
二、讀寫分離實作
讀寫分離是高併發資料庫優化的第一步。
2.1 原理說明
大多數應用是「讀多寫少」。電商網站 90% 的請求是瀏覽商品,只有 10% 是下單購買。
讀寫分離的思路:
- 主庫(Master):負責所有寫入操作
- 從庫(Slave):負責讀取操作,可以有多個
寫入請求 → 主庫
讀取請求 → 從庫 1 / 從庫 2 / 從庫 3
主庫的資料透過複製(Replication)同步到從庫。
2.2 實作方式
應用層實作
在程式碼中判斷 SQL 類型,決定要送到主庫還是從庫。
# 虛擬碼
if sql.startswith("SELECT"):
connection = slave_pool.get_connection()
else:
connection = master_pool.get_connection()
中間件實作
使用專門的中間件自動路由:
- MySQL Proxy
- ProxySQL
- MaxScale
- ShardingSphere
中間件的好處是對應用透明,不需要改 code。
雲端託管方案
主流雲端資料庫都支援讀寫分離:
- AWS Aurora:自動讀寫分離 endpoint
- GCP Cloud SQL:建立 Read Replica
- Azure SQL:設定 Read Scale-Out
2.3 同步延遲問題
從庫的資料不是即時同步的,有延遲(通常幾毫秒到幾秒)。
這會造成「剛寫入的資料讀不到」的問題。
解決方案:
- 強制走主庫:對時效性要求高的查詢,強制從主庫讀取
- 延遲感知路由:監控從庫延遲,延遲過高時切回主庫
- 因果一致性:帶著寫入時的 GTID 去讀,確保從庫已同步

三、分庫分表策略
當單機資料庫到達極限,分庫分表是下一步。
3.1 何時該分庫分表
該分的訊號:
- 單表資料量超過 1000 萬筆(經驗值)
- 單庫大小超過 500GB
- 單庫連線數不夠用
- 查詢效能持續下降
不該分的情況:
- 資料量還小,優化 SQL 和索引就夠了
- 沒有做好讀寫分離和快取
- 團隊沒有分散式資料庫經驗
分庫分表會大幅增加複雜度,不是輕易的決定。
3.2 分庫策略
垂直分庫:按業務拆分
把不同業務的表放到不同資料庫:
- 用戶庫:users、user_profiles
- 訂單庫:orders、order_items
- 商品庫:products、categories
好處是業務隔離,一個庫掛了不影響其他庫。
水平分庫:按規則拆分
把同一張表的資料分到多個資料庫:
- 用戶庫 1:user_id 1-1000 萬
- 用戶庫 2:user_id 1000 萬-2000 萬
好處是突破單機容量限制。
3.3 分表策略
垂直分表:拆列
把一張寬表拆成多張表:
- users:id, name, email(常用欄位)
- user_details:id, bio, avatar, preferences(不常用欄位)
減少單行大小,提升查詢效能。
水平分表:拆行
把資料按規則分到多張表:
- orders_202401:2024 年 1 月的訂單
- orders_202402:2024 年 2 月的訂單
或按 ID 取模:
- orders_0:order_id % 4 = 0
- orders_1:order_id % 4 = 1
- orders_2:order_id % 4 = 2
- orders_3:order_id % 4 = 3
3.4 分片鍵選擇
分片鍵(Sharding Key)決定資料分到哪個庫/表。選擇至關重要。
好的分片鍵特性:
- 查詢常用:大多數查詢都會帶這個欄位
- 分布均勻:資料能均勻分散,不要傾斜
- 不常變動:分片鍵改變會導致資料遷移
常見分片鍵:
- 用戶 ID(user_id)
- 訂單 ID(order_id)
- 時間(created_at)
- 租戶 ID(tenant_id,SaaS 場景)
3.5 跨庫查詢難題
分庫分表後,最大的挑戰是跨庫查詢。
問題 1:跨庫 JOIN
用戶表在用戶庫,訂單表在訂單庫。要查「用戶的所有訂單」怎麼辦?
解法:
- 應用層組裝(先查用戶,再查訂單)
- 資料冗餘(訂單表存用戶名稱)
- 寬表(把關聯資料提前合併)
問題 2:跨庫分頁
訂單分在 4 個庫,要取「最新 10 筆訂單」怎麼辦?
解法:
- 每個庫取 10 筆,應用層合併排序
- 搜尋引擎(Elasticsearch)做查詢
- 避免深分頁
問題 3:全局唯一 ID
分庫後不能用自增 ID,因為每個庫都會產生重複的 ID。
解法:
- UUID(簡單但佔空間)
- Snowflake ID(Twitter 方案)
- 資料庫號段(美團 Leaf)
四、Redis 快取設計
Redis 是高併發系統的標配。用對了,資料庫壓力減少 90%。
4.1 快取架構
請求 → 應用層 → Redis 快取 → 資料庫
↓
快取命中直接返回
Cache Aside Pattern(最常用)
- 先查快取
- 快取沒有,查資料庫
- 查到後寫入快取
- 返回結果
更新時:
- 更新資料庫
- 刪除快取(不是更新)
為什麼是「刪除」而不是「更新」?因為併發更新可能導致快取和資料庫不一致。刪除後,下次請求會重新從資料庫讀取。
4.2 快取策略
快取什麼?
- 熱點資料(頻繁讀取)
- 計算成本高的結果
- 不常變動的資料
快取多久?
- TTL(Time To Live)設定過期時間
- 依據資料特性設定:熱點商品 5 分鐘、用戶資料 1 小時、靜態配置 1 天
- 加入隨機值,避免同時過期
快取多大?
- 評估熱點資料大小
- 預留 20% 空間給突發流量
- 設定淘汰策略(LRU 最常用)
4.3 分散式鎖
高併發場景需要分散式鎖來保護共享資源。
場景:秒殺扣庫存。100 個請求同時扣,如果不加鎖,可能超賣。
Redis 分散式鎖實作:
# 加鎖
SET lock_key unique_value NX PX 30000
# 解鎖(用 Lua 腳本保證原子性)
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
注意事項:
- 設定過期時間,避免死鎖
- 用唯一值識別鎖的擁有者
- 解鎖時驗證是否是自己的鎖
如果需要更可靠的分散式鎖,考慮 Redlock 演算法或 Redisson。
更多秒殺設計細節,請參考高併發交易系統設計。
4.4 效能調優
連線池配置
不要每次請求都新建連線。使用連線池,預建立連線重複使用。
Pipeline 批量操作
多個命令一起發送,減少網路往返。
pipe = redis.pipeline()
pipe.get("key1")
pipe.get("key2")
pipe.get("key3")
results = pipe.execute()
選擇正確的資料結構
- 簡單 Key-Value:String
- 物件屬性:Hash
- 排行榜:Sorted Set
- 佇列:List
- 去重計數:Set / HyperLogLog
五、快取問題解決方案
用快取就會遇到這三個問題。面試必考,工作必踩。
5.1 快取穿透
問題描述:
查詢一個不存在的資料。快取沒有,資料庫也沒有。每次請求都打到資料庫。
惡意攻擊者可以用這招打爆你的資料庫。
解決方案:
方案 1:空值快取
資料庫查不到時,快取一個空值(TTL 設短一點)。
GET user:999999 → null(快取)
不用查資料庫
方案 2:布隆過濾器
在快取前加一層布隆過濾器。不存在的 Key 直接攔截,不查資料庫。
請求 → 布隆過濾器 → 快取 → 資料庫
↓
不存在直接返回
布隆過濾器有一定的誤判率,但不會漏判。
5.2 快取擊穿
問題描述:
某個熱點 Key 過期的瞬間,大量請求同時打到資料庫。
想像雙 11 的爆款商品,快取過期的那一刻,幾萬個請求同時去查資料庫。
解決方案:
方案 1:互斥鎖
只有一個請求去查資料庫並更新快取,其他請求等待。
value = redis.get(key)
if value is None:
if redis.setnx(lock_key, 1): # 獲取鎖
value = db.query(key)
redis.set(key, value)
redis.delete(lock_key)
else:
sleep(0.1)
return get_with_lock(key) # 重試
return value
方案 2:永不過期 + 後台更新
熱點 Key 不設過期時間,由後台定時更新。
快取:永不過期
後台任務:每 5 分鐘更新一次
5.3 快取雪崩
問題描述:
大量 Key 同時過期,請求全部打到資料庫。或者 Redis 本身掛掉。
解決方案:
方案 1:過期時間加隨機值
ttl = base_ttl + random.randint(0, 300) # 基礎 TTL + 0-300 秒隨機
redis.setex(key, ttl, value)
方案 2:多層快取
本地快取 + Redis + 資料庫。Redis 掛了還有本地快取頂著。
方案 3:Redis 高可用
使用 Redis Sentinel 或 Redis Cluster,避免單點故障。
方案 4:熔斷降級
當資料庫壓力過大,觸發熔斷,返回預設值或錯誤提示。

資料庫撐不住流量? 從讀寫分離到分庫分表,每一步都有陷阱。 預約架構諮詢,讓有經驗的顧問幫你規劃資料層優化方案。
六、雲端資料庫選型
如果你用雲端,不需要自己管理資料庫。但要選對服務。
6.1 AWS 方案
| 服務 | 類型 | 適用場景 |
|---|---|---|
| RDS | 關聯式 | 傳統應用,MySQL/PostgreSQL |
| Aurora | 關聯式(雲原生) | 高併發 MySQL/PostgreSQL,自動擴展 |
| DynamoDB | NoSQL(Key-Value) | 超高吞吐,無伺服器 |
| ElastiCache | 快取 | Redis/Memcached 託管 |
Aurora 亮點:
- 比 RDS MySQL 快 5 倍
- 自動擴展到 128TB
- 15 個讀取副本
- 跨可用區高可用
6.2 GCP 方案
| 服務 | 類型 | 適用場景 |
|---|---|---|
| Cloud SQL | 關聯式 | MySQL/PostgreSQL/SQL Server |
| Cloud Spanner | 關聯式(分散式) | 全球一致性,超大規模 |
| Firestore | NoSQL(文件) | 行動應用、即時同步 |
| Memorystore | 快取 | Redis 託管 |
Cloud Spanner 亮點:
- 水平擴展的關聯式資料庫
- 全球分散式
- 強一致性(這很難得)
- 99.999% SLA
6.3 Azure 方案
| 服務 | 類型 | 適用場景 |
|---|---|---|
| Azure SQL | 關聯式 | SQL Server 生態 |
| Cosmos DB | NoSQL(多模型) | 全球分散、多種 API |
| Azure Cache | 快取 | Redis 託管 |
Cosmos DB 亮點:
- 多模型(文件、Key-Value、圖形、列族)
- 全球分散式
- 多種一致性級別可選
- 毫秒級回應
6.4 選型比較表
| 需求 | AWS | GCP | Azure |
|---|---|---|---|
| MySQL 託管 | Aurora | Cloud SQL | Azure SQL |
| 超大規模關聯式 | Aurora | Spanner | Azure SQL Hyperscale |
| 全球分散式 NoSQL | DynamoDB Global | Spanner / Firestore | Cosmos DB |
| Redis 託管 | ElastiCache | Memorystore | Azure Cache |
更詳細的雲端架構比較,請參考雲端高併發架構。
七、實戰案例
案例:電商訂單系統優化
背景:
- 日訂單量 50 萬筆
- 大促期間 QPS 突破 10,000
- 資料庫經常撐不住
優化步驟:
第一步:加快取
- 商品資訊放 Redis,TTL 5 分鐘
- 資料庫讀取減少 70%
第二步:讀寫分離
- 主庫寫入,兩個從庫讀取
- 使用 ProxySQL 做路由
- 從庫分擔 80% 讀取流量
第三步:訂單表分表
- 按月分表:orders_202501、orders_202502
- 歷史訂單歸檔到冷儲存
- 熱資料查詢效能提升 3 倍
結果:
- QPS 從 3,000 提升到 15,000
- 資料庫 CPU 使用率從 90% 降到 40%
- 大促期間系統穩定
常見問題 FAQ
Q1: 什麼時候該用 Redis?
只要是「讀多寫少」的熱點資料,就適合放 Redis。包括:Session、商品資訊、排行榜、計數器、分散式鎖。
Q2: 讀寫分離會影響資料一致性嗎?
會有延遲,通常幾毫秒到幾秒。對於「剛寫入就要讀」的場景,需要強制從主庫讀取。
Q3: 分庫分表後怎麼做報表?
分庫分表後,跨庫查詢很麻煩。報表建議用獨立的資料倉儲(如 ClickHouse、BigQuery),定時從業務庫同步資料。
Q4: NoSQL 可以取代關聯式資料庫嗎?
看場景。需要複雜查詢、JOIN、交易的場景,關聯式資料庫依然是首選。NoSQL 適合高吞吐、Schema 靈活的場景。
Q5: 快取和資料庫不一致怎麼辦?
使用「刪除快取」而非「更新快取」策略。如果還是不一致,設定較短的 TTL,或使用 CDC(Change Data Capture)同步。
結論:資料層優化是系統效能的關鍵
資料庫是高併發系統的生命線。優化對了,效能提升 10 倍不是夢。
本文重點回顧:
- 資料庫瓶頸通常出在連線數、鎖競爭、單機容量
- 讀寫分離是第一步,用從庫分擔讀取壓力
- 分庫分表突破單機限制,但複雜度大增
- Redis 快取減少 90% 資料庫壓力
- 快取穿透用空值或布隆過濾器解決
- 快取擊穿用互斥鎖或永不過期解決
- 快取雪崩用隨機 TTL 和高可用解決
- 雲端資料庫省心,但要選對服務
延伸閱讀:
架構設計需要第二意見?
好的資料層設計能節省數倍的成本。如果你正在:
- 資料庫撐不住流量,需要優化
- 評估是否該分庫分表
- 選擇雲端資料庫服務
預約架構諮詢,讓我們一起檢視你的資料層架構。
所有諮詢內容完全保密,沒有銷售壓力。
參考資料
- Martin Kleppmann,《Designing Data-Intensive Applications》(2017)
- Redis 官方文檔,《Redis Best Practices》(2024)
- AWS,《Amazon Aurora User Guide》(2024)
- Google Cloud,《Cloud Spanner Best Practices》(2024)
- 阿里巴巴,《阿里雲資料庫最佳實踐》(2023)
相關文章
高併發是什麼?2025 完整指南:定義、架構設計與雲端解決方案
高併發(High Concurrency)是什麼意思?本文完整解析高併發的定義、常見問題、架構設計模式,以及如何利用 Redis、資料庫優化、雲端服務來處理高流量場景。無論你是要應對電商大促、搶票系統還是即時交易,這篇指南都能幫你設計出高可用的系統架構。
雲端資料庫雲端資料庫是什麼?2025完整指南|免費方案、平台比較、建立教學
完整解析雲端資料庫的定義、優缺點與應用場景。比較 AWS、GCP、Azure 三大平台,推薦免費方案,手把手教你建立第一個雲端資料庫。
雲端資料庫雲端資料庫 MySQL 整合指南:從本地遷移到雲端的完整教學
完整教學如何將 MySQL 資料庫遷移到雲端,包含 AWS RDS、GCP Cloud SQL、Azure Database for MySQL 的設定步驟與最佳實踐。