强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 17 章:分库分表

第 17 章:分库分表

当单表数据量超过千万级,单库容量成为瓶颈时,分库分表是必经之路。


17.1 什么时候需要分库分表

指标阈值说明
单表行数> 5000 万查询变慢,索引效率下降
单库数据量> 500GB备份恢复时间过长
单库写入 QPS> 5000主库压力过大
单表大小> 30GBDDL 操作耗时

⚠️ 分库分表是最后手段,之前应先尝试:优化索引、读写分离、缓存、归档历史数据。


17.2 垂直拆分

17.2.1 垂直分库

按业务模块拆分到不同数据库。

拆分前:
app_db
├── users
├── orders
├── products
└── messages

拆分后:
user_db    → users, user_profiles
order_db   → orders, order_items
product_db → products, categories
msg_db     → messages, notifications

17.2.2 垂直分表

将大表的列拆分为多张表。

-- 拆分前:一张表包含所有字段
CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    username VARCHAR(50),
    email VARCHAR(100),
    avatar_url VARCHAR(500),
    bio TEXT,           -- 不常访问的大字段
    settings JSON,      -- 不常访问的大字段
    created_at DATETIME
);

-- 拆分后:主表 + 扩展表
CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    username VARCHAR(50),
    email VARCHAR(100),
    created_at DATETIME
);

CREATE TABLE user_profiles (
    user_id BIGINT PRIMARY KEY,
    avatar_url VARCHAR(500),
    bio TEXT,
    settings JSON,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

17.3 水平拆分

17.3.1 水平分表(同库)

将一张大表按行拆分为多张小表。

-- 按用户 ID 取模分 4 张表
CREATE TABLE orders_0 (...);
CREATE TABLE orders_1 (...);
CREATE TABLE orders_2 (...);
CREATE TABLE orders_3 (...);

-- 路由规则:order_id % 4 = 表后缀

17.3.2 水平分库分表(不同库)

db_0.orders_0  db_0.orders_1  db_0.orders_2  db_0.orders_3
db_1.orders_0  db_1.orders_1  db_1.orders_2  db_1.orders_3

路由规则:user_id % 4 = 库后缀
         order_id % 4 = 表后缀

17.3.3 分片键选择

分片键优点缺点
user_id用户数据集中,查询高效大用户问题
order_id均匀分布跨用户查询困难
时间易于归档老数据热点问题(集中在最新分片)

💡 分片键选择原则

  • 选择查询频率最高的条件字段
  • 数据分布均匀
  • 避免跨分片查询

17.4 ShardingSphere

17.4.1 概述

Apache ShardingSphere 是最流行的分布式数据库中间件。

产品说明适用场景
ShardingSphere-JDBCJava JAR 包,嵌入应用Java 应用
ShardingSphere-Proxy独立代理服务,兼容 MySQL 协议任何语言

17.4.2 ShardingSphere-Proxy 配置

# config-sharding.yaml
schemaName: myapp

dataSources:
  ds_0:
    url: jdbc:mysql://192.168.1.100:3306/db_0?useSSL=false
    username: root
    password: root
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
  ds_1:
    url: jdbc:mysql://192.168.1.101:3306/db_1?useSSL=false
    username: root
    password: root
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50

rules:
  - !SHARDING
    tables:
      orders:
        actualDataNodes: ds_${0..1}.orders_${0..3}
        tableStrategy:
          standard:
            shardingColumn: order_id
            shardingAlgorithmName: orders_inline
        keyGenerateStrategy:
          column: order_id
          keyGeneratorName: snowflake
    shardingAlgorithms:
      orders_inline:
        type: INLINE
        props:
          algorithm-expression: orders_${order_id % 4}

17.4.3 分布式 ID 方案

方案说明优点缺点
雪花算法64位:时间戳+机器ID+序列号有序、高性能时钟回拨问题
UUID128位全局唯一简单无序、占空间
号段模式从数据库批量获取 ID 段简单数据库瓶颈
Redis INCRRedis 自增高性能依赖 Redis

17.5 分库分表后的挑战

挑战解决方案
跨分片 JOIN冗余字段、应用层组装、全局表
跨分片排序分页各分片查出 N 条,应用层合并排序
跨分片聚合各分片聚合后,应用层再聚合
跨分片事务Seata 等分布式事务框架
数据迁移双写 + 对比 + 切换
全局唯一 ID雪花算法

业务场景

场景 1:订单表分库分表

分片键:user_id(按用户查询最多)
分片数:4 库 × 8 表 = 32 张表
路由:user_id % 4 = 库后缀, order_id % 8 = 表后缀

扩展阅读