1. 引言
在大規模互聯網應用中,隨著資料量的不斷增長,單庫單表的架構無法滿足高並發和大數據儲存的需求。分庫分錶是一種常見的資料庫架構最佳化方案,它可以提高資料庫的吞吐量、減少單表資料量並提升查詢效率。然而,分庫分錶也帶來了許多複雜的問題,例如主鍵產生、分頁查詢、分散式事務、跨表查詢、高可用性等。
本文將詳細講解分庫分錶的核心概念、關鍵技術及其實作方式,並結合範例程式碼,幫助開發者理解如何在實際專案中應用分庫分錶。
2. 分庫分錶概述
2.1 什麼是分庫分錶?
分庫分錶是將原本儲存在一個資料庫表中的資料分割到多個資料庫或多個資料表中的技術,主要有以下兩種方式:
- 水平分庫分錶(Sharding):按照某個欄位(如
user_id
)的範圍或哈希值,將資料拆分到不同的資料庫實例和表中。例如:- 使用者ID
1~1000
存在db1.user_table_1
,1001~2000
存在db1.user_table_2
- 使用者ID
2001~3000
存在db2.user_table_1
,3001~4000
存在db2.user_table_2
- 使用者ID
- 垂直分庫分錶(Vertical Partitioning):按照業務模組拆分,如
使用者表
和訂單表
儲存在不同的資料庫中,適用於不同業務系統的資料隔離。
2.2 為什麼要使用分庫分錶?
- 解決單庫效能瓶頸:單一資料庫無法承載大量並發請求,拆分資料庫可以均衡流量壓力。
- 提高讀寫效率:減少單表的資料量,提高查詢和插入速度。
- 降低儲存成本:可以使用多個較低配置的資料庫執行個體來取代高配資料庫。
- 提升系統高可用性:多個資料庫實例可以相互獨立,避免單點故障。
3. 分庫分錶主鍵生成
在分庫分錶後,全域唯一的主鍵產生成為一個關鍵問題。常見的主鍵產生方式包括:
- UUID:全域唯一,但長度較長,儲存開銷大,不利於索引查詢。
- 資料庫自增ID:單機可用,但無法跨資料庫唯一。
Snowflake(雪花演算法):
- Twitter 提出的分散式ID 產生演算法,基於時間戳記+機器ID+序號
Go 實作範例:
package main import ( "fmt" "sync" "time" ) const ( workerIDBits = 5 datacenterIDBits = 5 sequenceBits = 12 maxWorkerID = -1 ^ (-1 << workerIDBits) maxDatacenterID = -1 ^ (-1 << datacenterIDBits) maxSequence = -1 ^ (-1 << sequenceBits) ) type Snowflake struct { mu sync.Mutex lastTimestamp int64 workerID int64 datacenterID int64 sequence int64 } func NewSnowflake(workerID, datacenterID int64) *Snowflake { return &Snowflake{ workerID: workerID, datacenterID: datacenterID, } } func (s *Snowflake) NextID() int64 { s.mu.Lock() defer s.mu.Unlock() timestamp := time.Now().UnixNano() / 1e6 if timestamp == s.lastTimestamp { s.sequence = (s.sequence + 1) & maxSequence if s.sequence == 0 { for timestamp <= s.lastTimestamp { timestamp = time.Now().UnixNano() / 1e6 } } } else { s.sequence = 0 } s.lastTimestamp = timestamp return ((timestamp << (workerIDBits + datacenterIDBits + sequenceBits)) | (s.datacenterID << (workerIDBits + sequenceBits)) | (s.workerID << sequenceBits) | s.sequence) } func main() { sf := NewSnowflake(1, 1) fmt.Println(sf.NextID()) }
- 資料庫分段號段方式:
- 預分配ID 段,如
db1
產生1~10000
,db2
產生10001~20000
,適用於少量資料庫的情況。
- 預分配ID 段,如
4. 分庫分錶分頁查詢
分頁查詢在分庫分錶後變得複雜,因為資料分散在多個表和庫中,常見的解決方案:
- 全庫查詢+合併排序:
SELECT * FROM ( SELECT id, name, create_time FROM db1.user_table_1 UNION ALL SELECT id, name, create_time FROM db1.user_table_2 ) AS temp ORDER BY create_time DESC LIMIT 10 OFFSET 0;
- 利用快取:先查詢主鍵ID,再分庫查詢詳細數據,減少跨庫查詢的開銷。
5. 分庫分錶後的分散式事務
分散式事務的核心問題是多個資料庫執行個體之間的交易一致性,常見解決方案:
- TCC(Try-Confirm-Cancel)模式
- 本地訊息表+可靠訊息佇列
- Seata 分散式事務框架
範例:使用 golangTP
+ gorm
解決分散式事務問題:
func createOrder(db1, db2 *gorm.DB) error {
return db1.Transaction(func(tx *gorm.DB) error {
if err := tx.Exec("UPDATE users SET balance = balance - 100 WHERE id = ?", 1).Error; err != nil {
return err
}
if err := db2.Exec("INSERT INTO orders (user_id, amount) VALUES (?, ?)", 1, 100).Error; err != nil {
return err
}
return nil
})
}
9. 總結
本文詳細介紹了分庫分錶的原理、主鍵產生、分頁查詢、分散式事務、無分錶鍵查詢、容量預估及高可用方案,並結合程式碼範例說明如何解決關鍵問題。
🔗 參考資料: