{T}

分库分表实战与 ShardingSphere

当单库单表的数据量持续增长(千万、上亿),或并发写入远超单库上限时,就需要分库分表。本文系统讲解拆分策略、分片键选择、全局 ID、扩容与跨库 Join/事务,以及主流的 ShardingSphere 中间件实践。

一、为什么需要分库分表

单库单表的容量与性能瓶颈:

  • 数据量瓶颈:单表数据上亿后,B+ 树层级变深,索引维护和扫描成本上升,查询变慢。
  • 写入瓶颈:单库写并发受限(锁竞争、binlog、主从复制延迟)。
  • 存储瓶颈:单机磁盘容量有限,无法无限扩展。

分库分表的目的:通过水平拆分把数据分布到多库多表,降低单表数据量、分散读写压力,实现水平扩展。

二、拆分策略

2.1 垂直拆分(按业务/字段)

  • 垂直分库:按业务模块拆到不同库(订单库、用户库、商品库),各库独立,降低耦合与并发压力。
  • 垂直分表:把一张大表的字段拆分(如把不常访问的大字段拆到扩展表),减少行宽,提高单页记录数。
text
垂直分库: 订单库(订单表) | 用户库(用户表) | 商品库(商品表)
垂直分表: user(常用字段) + user_ext(大字段: bio/avatar)

2.2 水平拆分(按行数据)——核心

水平分表:把同一张表的数据按某规则分到多张结构相同的表。 水平分库:把数据分布到多个数据库实例。

text
水平分表: order_0, order_1, order_2, order_3(按 order_id 取模)
水平分库: db_order_0, db_order_1(每库内再分表)

2.3 分片算法

算法说明示例特点
取模hash(key) % 表数user_id % 4简单,但扩容需重分片
范围按 ID/时间区间id < 1000 -> t0扩容友好,但可能数据倾斜
一致性哈希环形哈希,减少迁移均匀分布 + 虚拟节点扩容只需迁移少量数据
哈希分片对分片键做哈希md5(user_id) % N分布均匀

取模 vs 范围的选择

  • 取模:数据均匀,但扩容困难(从 4 变 8 需要 rehash 迁移大量数据)。
  • 范围:扩容简单(新表即可),但热点可能集中在某段(如新订单都在最新表)。

三、分片键(Sharding Key)的选择

分片键是分库分表的核心决策,选错会导致大部分查询变成"全分片扫描"。

选择原则

  1. 高基数:取值足够多,避免单分片数据倾斜。
  2. 高频查询条件:业务查询最多的字段(如 user_idorder_id)应作为分片键。
  3. 均匀分布:保证各分片数据量大致均匀。
  4. 避免跨分片关联:分片键相同的记录尽量落在同一分片。

常见问题——非分片键查询怎么办

  • 若经常用 order_id 查但按 user_id 分片,可用 映射表(维护 order_id→user_id 关系)或 冗余字段(在表里冗余 user_id)。
  • 或者对热点查询字段建立 全局索引表,先查到分片再查询。

四、全局 ID(分布式 ID)

分库分表后,数据库自增主键失效,需要全局唯一 ID。常用方案(详见「分布式 ID 生成方案」):

  • 雪花算法(Snowflake):趋势递增、高性能,主推。
  • 号段模式(Leaf-segment):批量取号。
  • Redis INCR:简单但依赖 Redis。
  • UUID:无序、过长,不适合主键。

分片键若用全局 ID,建议ID 末尾带分片信息(如 snowflake 的机器位/或 ID 后拼分片号),便于按 ID 定位分片,避免每次都 rehash。

五、跨库 Join 与分布式事务

分库分表最大的代价是牺牲了关系型数据库的关联与事务能力

5.1 跨库 Join 的解决

  1. 宽表冗余:把关联字段冗余到查询侧,避免 Join。
  2. 应用层组装:拆成多次单表查询,在应用层(内存)组装结果。
  3. 全局表:字典、配置等小表在所有分片冗余一份(广播表)。
  4. 中间件聚合:ShardingSphere 支持跨库 Join(有性能成本)。

5.2 分布式事务

跨库写操作无法用单库事务,需分布式事务方案(详见「分布式事务与最终一致性」):

  • 2PC/3PC:强一致,性能差。
  • TCC:补偿式,性能较好,实现复杂。
  • 本地消息表 + 消息队列:最终一致,主流。
  • Seata(AT 模式):基于代理的最终一致方案,业界常用。

六、数据扩容与迁移

取模分片的扩容难题:从 N 个分片扩到 M 个,hash(key) % N 变成 % M,几乎所有数据都要迁移。

解决思路

  1. 一致性哈希:只有部分数据需要迁移,但实现复杂。
  2. 翻倍扩容(2N→2N):取模扩容时,key 原本落在 % 2N 的,扩容到 % 4N 后,只有一半数据需要迁移,且迁移逻辑简单(旧分片的一半)。
  3. 双写 + 迁移 + 切换:写两个集群,迁移完成后切换读流量。
  4. 基于 binlog 的增量迁移:用订阅 binlog 把增量数据同步到新分片,完成后再全量校验。

七、ShardingSphere 中间件

ShardingSphere 是 Apache 顶级开源项目,核心产品:

产品定位说明
ShardingSphere-JDBCJDBC 增强(客户端)应用内代理,改连接即可,无需独立部署,主流
ShardingSphere-Proxy独立代理(服务端)模拟数据库,客户端无需改动
ShardingSphere-Sidecar云原生用于 Kubernetes 场景

7.1 ShardingSphere-JDBC 使用(以 Spring Boot + MyBatis 为例)

yaml
# application.yml
spring:
  shardingsphere:
    datasource:
      names: ds0,ds1
      ds0:
        url: jdbc:mysql://localhost:3306/order_db_0
      ds1:
        url: jdbc:mysql://localhost:3306/order_db_1
    rules:
      sharding:
        tables:
          t_order:
            actual-data-nodes: ds${0..1}.t_order_${0..3}
            table-strategy:
              standard:
                sharding-column: order_id
                sharding-algorithm-name: order_inline
            key-generate-strategy:
              column: order_id
              key-generator-name: snowflake
        sharding-algorithms:
          order_inline:
            type: INLINE
            props:
              algorithm-expression: t_order_${order_id % 4}
        key-generators:
          snowflake:
            type: SNOWFLAKE
java
// 使用上与普通 Mapper 完全一致,ShardingSphere 自动路由分片
orderMapper.insert(order);              // 自动生成雪花ID并按 order_id 路由
Order o = orderMapper.selectById(id);   // 自动路由到正确分片

ShardingSphere-JDBC 的核心能力

  • SQL 路由:根据分片键把 SQL 路由到正确的数据源/表。
  • 分片结果归并:多分片查询结果合并、排序、分页。
  • 主键生成:内置雪花算法。
  • 读写分离:读写分离配置。
  • 分布式事务:集成 Seata。

7.2 使用注意事项

  1. 没有分片键的查询会广播到全部分片再归并,性能差,尽量避免。
  2. 不支持的部分 SQL:部分跨分片的子查询、LIMIT 跨分片的深度分页需注意。
  3. 分布式事务:ShardingSphere 支持事务类型需在配置中指定(LOCAL/XA/BASE)。
  4. 深度分页(如 LIMIT 100000,10)在分片下会全分片取出再归并,性能差,需用游标/分页键优化。

八、分库分表的时机与代价

不要过早分库分表。它带来的问题:

  • 复杂度剧增:SQL 路由、事务、Join 全部受影响。
  • 运维成本高:多实例、多表维护。
  • 扩容迁移风险大。

建议:优先用读写分离缓存索引优化归档冷数据解决;当单表数据量到千万级且持续增长、写入并发成为瓶颈时,再考虑分库分表,并做好分片键设计扩容预案

小结

  • 分库分表核心是水平拆分,关键是选对分片键分片算法(取模/范围/一致性哈希)。
  • 全局 ID 用雪花算法;跨库 Join 用宽表/应用组装/广播表;跨库事务用分布式事务方案
  • ShardingSphere-JDBC 是最主流的分库分表方案,应用透明接入。
  • 分库分表是最后的手段,优先用缓存、索引、归档等低成本方案,并做好扩容预案。

版本差异(MySQL 5.7 → 8.0/8.4)

特性旧版(本文编写时,MySQL 5.7)当前(MySQL 8.0/8.4 LTS)
默认字符集utf8(需显式配置 utf8mb4)utf8mb4(MySQL 8.0 起默认)
索引普通 B+Tree降序索引、隐藏索引、函数索引(8.0+)
SQL 能力常规查询递归 CTE、窗口函数(8.0+)
版本策略5.78.0(主流)/ 8.4 LTS / 9.x(创新版)
Java 驱动mysql-connector-java 5.x/8.0mysql-connector-j 8.x/9.x

本文基于 MySQL 5.7 编写,核心概念(索引、事务、锁、MVCC、InnoDB)在 8.0/8.4 中依然适用;8.0 的默认字符集、隐藏索引与 SQL 增强(CTE/窗口函数)是升级后的主要差异。