分庫分錶帶來了哪些問題?

👉 這是一個或許對你有用的社群
🐱 一對一交流/面試小冊/簡歷最佳化/求職解惑,歡迎加入芋道快速開發平臺知識星球。下面是星球提供的部分資料:
👉這是一個或許對你有用的開源專案
國產 Star 破 10w+ 的開源專案,前端包括管理後臺 + 微信小程式,後端支援單體和微服務架構。
功能涵蓋 RBAC 許可權、SaaS 多租戶、資料許可權、商城、支付、工作流、大屏報表、微信公眾號、ERPCRMAI 大模型等等功能:
  • Boot 多模組架構:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 微服務架構:https://gitee.com/zhijiantianya/yudao-cloud
  • 影片教程:https://doc.iocoder.cn
【國內首批】支援 JDK 17/21 + SpringBoot 3.3、JDK 8/11 + Spring Boot 2.7 雙版本 

分庫分表是解決單庫單表效能瓶頸的有效手段,但也會引入新的複雜性和技術挑戰。
這篇文章跟大家一起聊聊,分庫分表後帶來的7個問題,以及相關的解決方案,希望對你會有所幫助。

1. 全域性唯一 ID 問題

問題描述

在分庫分表後,每張表的自增 ID 只在本表範圍內唯一,但無法保證全域性唯一。
例如:
  • 訂單表_1 的主鍵從 1 開始,訂單表_2 的主鍵也從 1 開始。
  • 在需要全域性唯一 ID 的場景(如訂單號、使用者 ID)中會發生衝突。

解決方案

1.1 使用分散式 ID 生成器

推薦工具:
  • Snowflake :Twitter 開源的分散式 ID 演算法。
  • 百度 UidGenerator :基於 Snowflake 的改進版。
  • Leaf :美團開源,號段模式和 Snowflake 雙支援。
程式碼示例:Snowflake 演算法
publicclassSnowflakeIdGenerator

{

privatefinallong

 epoch = 

1622476800000L

// 自定義時間戳
privatefinallong

 workerIdBits = 

5L

// 機器ID
privatefinallong

 datacenterIdBits = 

5L

// 資料中心ID
privatefinallong

 sequenceBits = 

12L

// 序列號

privatefinallong

 maxWorkerId = ~(-

1L

 << workerIdBits);

privatefinallong

 maxDatacenterId = ~(-

1L

 << datacenterIdBits);

privatefinallong

 sequenceMask = ~(-

1L

 << sequenceBits);

privatelong

 workerId;

privatelong

 datacenterId;

privatelong

 sequence = 

0L

;

privatelong

 lastTimestamp = -

1L

;

publicSnowflakeIdGenerator(long workerId, long datacenterId)

{

if

 (workerId > maxWorkerId || workerId < 

0

thrownew

 IllegalArgumentException(

"Worker ID out of range"

);

if

 (datacenterId > maxDatacenterId || datacenterId < 

0

thrownew

 IllegalArgumentException(

"Datacenter ID out of range"

);

this

.workerId = workerId;

this

.datacenterId = datacenterId;

    }

publicsynchronizedlongnextId()

{

long

 timestamp = System.currentTimeMillis();

if

 (timestamp < lastTimestamp) 

thrownew

 RuntimeException(

"Clock moved backwards"

);

if

 (timestamp == lastTimestamp) {

            sequence = (sequence + 

1

) & sequenceMask;

if

 (sequence == 

0

) timestamp = waitNextMillis(lastTimestamp);

        } 

else

 sequence = 

0L

;
        lastTimestamp = timestamp;

return

 ((timestamp - epoch) << (workerIdBits + datacenterIdBits + sequenceBits))

                | (datacenterId << (workerIdBits + sequenceBits))

                | (workerId << sequenceBits)

                | sequence;

    }

privatelongwaitNextMillis(long lastTimestamp)

{

long

 timestamp = System.currentTimeMillis();

while

 (timestamp <= lastTimestamp) timestamp = System.currentTimeMillis();

return

 timestamp;

    }

}

1.2 資料庫號段分配

  • 原理 :維護一個獨立的 global_id 表,分庫按步長分配 ID:
    • 庫 1:ID 步長為 2,從 1 開始(1, 3, 5…)。
    • 庫 2:ID 步長為 2,從 2 開始(2, 4, 6…)。
示例

CREATE TABLE 

global_id

(

        id INT PRIMARY KEY AUTO_INCREMENT,

        stub CHAR(

1

)

 NOT NULL UNIQUE

        )

;

        -- 步長設定:

        SET @

@auto

_increment_increment = 

2

;

        SET @

@auto

_increment_offset = 

1

;

基於 Spring Boot + MyBatis Plus + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 影片教程:https://doc.iocoder.cn/video/

2. 跨庫跨表查詢複雜性

問題描述

分庫分表後,聚合查詢(如總數統計、分頁查詢)需要跨多個分片表執行,增加了查詢複雜度。
例如:
  • 查詢所有訂單總數,需要跨 10 個訂單表聚合。
  • 按建立時間分頁查詢所有訂單。

解決方案

2.1 使用中介軟體(推薦)

  • ShardingSphereMyCAT :支援 SQL 分片執行和結果合併。
  • 優點:業務程式碼無需修改,中介軟體完成分庫分表邏輯。

2.2 手動分片查詢

  • 按分片逐一查詢資料,在業務層合併結果。
示例程式碼:聚合查詢
publicintcountAllOrders()

{

int

 total = 

0

;

for

 (String db : List.of(

"db1"

"db2"

"db3"

)) {

        String sql = 

"SELECT COUNT(*) FROM "

 + db + 

".orders"

;

        total += jdbcTemplate.queryForObject(sql, Integer

.class)

;

        }

return

 total;

        }

示例程式碼:跨分片分頁查詢
public List<Order> paginateOrders(int page, int size)

{

        List<Order> allOrders = 

new

 ArrayList<>();

for

 (String table : List.of(

"orders_1"

"orders_2"

)) {

        String sql = 

"SELECT * FROM "

 + table + 

" LIMIT 100"

;

        allOrders.addAll(jdbcTemplate.query(sql, 

new

 OrderRowMapper()));

        }

        allOrders.sort(Comparator.comparing(Order::getCreatedAt));

return

 allOrders.stream()

        .skip((page - 

1

) * size)

        .limit(size)

        .collect(Collectors.toList());

        }

手動分片查詢的方案,如果資料比較多,效能會比較差。
基於 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/yudao-cloud
  • 影片教程:https://doc.iocoder.cn/video/

3. 分散式事務問題

問題描述

分散式事務(如訂單表在庫 A,庫存表在庫 B)無法使用單庫事務,導致可能會出現資料的一致性問題。

解決方案

3.1 分散式事務框架

  • Seata :支援跨庫的分散式事務。
  • 示例程式碼
@GlobalTransactional
publicvoidcreateOrder(Order order)

{

        orderService.saveOrder(order); 

// 寫入庫A

        stockService.reduceStock(order.getProductId()); 

// 更新庫B

        }

3.2 柔性事務

  • 使用訊息中介軟體實現最終一致性。
  • 典型實現:RocketMQ 訊息事務

4. 分片鍵設計問題

問題描述

分片鍵選擇不當可能導致資料傾斜(熱點問題)或查詢路由效率低。

解決方案

4.1 分片鍵設計原則

  1. 資料分佈均勻 :避免熱點問題。
  2. 常用查詢欄位 :儘量選高頻查詢欄位。

4.2 路由表

  • 維護全域性路由表,對映分片鍵到分表。
示例程式碼:路由表查詢
public String getTargetTable(int userId)

{

        String sql = 

"SELECT table_name FROM routing_table WHERE user_id = ?"

;

return

 jdbcTemplate.queryForObject(sql, 

new

 Object[]{userId}, String

.class)

;

        }

5. 資料遷移問題

問題描述

擴容(如從 4 個分片擴充套件到 8 個分片)時,舊資料需要遷移到新分片,遷移複雜且可能影響線上服務。

解決方案

5.1 雙寫策略

  • 資料遷移期間,舊錶和新表同時寫入。
  • 待遷移完成後,切換到新表。

5.2 增量同步

  • 使用 Canal 監聽 MySQL Binlog,將資料遷移到新分片。
示例:Canal 配置

canal.destinations:

        example:

        mysql:

        hostname: localhost

        port: 

3306

        username: root

        password: password

        kafka:

        servers: localhost:

9092

        topic: example_topic

6. 分頁查詢問題

問題描述

分頁查詢需要從多個分片表合併資料,再統一分頁,邏輯複雜度增加。

解決方案

  1. 各分片分頁後合併 :先按分片分頁查詢,業務層合併排序後分頁。
  2. 中介軟體支援分頁 :如 ShardingSphere。
示例程式碼:跨分片分頁
public List<Order> queryPagedOrders(int page, int size)

{

        List<Order> results = 

new

 ArrayList<>();

for

 (String table : List.of(

"orders_1"

"orders_2"

)) {

        results.addAll(jdbcTemplate.query(

"SELECT * FROM "

 + table + 

" LIMIT 100"

new

 OrderRowMapper()));

        }

        results.sort(Comparator.comparing(Order::getCreatedAt));

return

 results.stream().skip((page - 

1

) * size).limit(size).collect(Collectors.toList());

        }

但如果分的表太多,可能會有記憶體佔用過多的問題,需要做好控制。

7. 運維複雜性

問題描述

分庫分表後,運維難度增加:
  • 資料庫例項多,監控和備份複雜。
  • 故障排查需要跨多個庫。

解決方案

  1. 自動化運維平臺 :如阿里雲 DMS。
  2. 監控工具 :使用 Prometheus + Grafana 實現分片監控。

總結

分庫分表本質上是“效能換複雜度”,它雖然能有效提升系統的效能和擴充套件性,但問題也隨之而來。
分庫分表後帶來的問題總結如下:
問題 解決方案
全域性唯一 ID 雪花演算法、號段分配、Leaf
跨庫跨表查詢 中介軟體支援(如 ShardingSphere)或手動合併
分散式事務 分散式事務框架(Seata)、訊息最終一致性
分片鍵設計問題 路由表或高效分片鍵
資料遷移問題 雙寫策略或增量同步(如 Canal)
分頁查詢問題 分片查詢後合併排序
運維複雜性 自動化工具(DMS)、監控工具(Prometheus + Grafana)
應根據業務場景選擇適合的分庫分表策略,並透過工具和技術方案,解決由此帶來的一些問題,最終實現系統的高效能與高可靠性。

歡迎加入我的知識星球,全面提升技術能力。
👉 加入方式,長按”或“掃描”下方二維碼噢
星球的內容包括:專案實戰、面試招聘、原始碼解析、學習路線。
文章有幫助的話,在看,轉發吧。
謝謝支援喲 (*^__^*)

相關文章