数据库分库分表设计与实践:从原理到 ShardingSphere 落地

By | 2026年9月22日

为什么需要分库分表?

当单表数据量超过 500 万行或单库磁盘容量、连接数达到瓶颈时,查询性能会急剧下降。此时垂直拆分(按业务拆库)和水平拆分(按规则拆表)成为必选项。但分库分表也引入了分布式事务、跨节点查询、全局 ID 等复杂问题。本文将带你从零设计一个订单分库分表方案,并基于 ShardingSphere-JDBC 实现。

一、分库分表策略选择

1.1 垂直拆分 vs 水平拆分

  • 垂直分库:按业务将不同表拆分到不同数据库,例如用户库、订单库、商品库。解决的是表数量多、IO 竞争问题。
  • 垂直分表:将宽表拆分为主表+扩展表,例如订单基础信息与订单详情。
  • 水平分库:同一张表按规则拆分到多个数据库。
  • 水平分表:同一张表按规则拆分为多张表,仍在同一库。

最佳实践:优先垂直拆分,再水平拆分;水平拆分时通常分库与分表同时进行,避免单库表过多。

1.2 分片键与分片算法

分片键(Sharding Key)是数据分布的依据,必须选择高基数、分布均匀、且高频查询条件的字段。订单场景常用 userid 或 orderid。

  • 取模:user_id % 4,分布均匀但扩容困难。
  • 范围:按时间范围分片,易扩容但可能热点。
  • 一致性哈希:扩容时影响小,但实现复杂。

注意:一旦选定分片键,后续所有查询尽量携带该字段,否则会全路由扫描。

二、实战:基于 ShardingSphere-JDBC 拆分订单表

2.1 环境准备

  • MySQL 8.0(两个库:ds0、ds1)
  • Spring Boot 2.7.x
  • ShardingSphere-JDBC 5.3.0

创建数据库与表:


-- 在 ds0 和 ds1 中分别执行
CREATE DATABASE ds0;
CREATE DATABASE ds1;

-- ds0 中创建 t_order_0, t_order_1
CREATE TABLE ds0.t_order_0 (
  order_id BIGINT NOT NULL,
  user_id BIGINT NOT NULL,
  amount DECIMAL(10,2),
  create_time DATETIME,
  PRIMARY KEY (order_id)
);
CREATE TABLE ds0.t_order_1 LIKE ds0.t_order_0;

-- ds1 中创建 t_order_0, t_order_1
CREATE TABLE ds1.t_order_0 LIKE ds0.t_order_0;
CREATE TABLE ds1.t_order_1 LIKE ds0.t_order_0;

2.2 引入依赖


<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
    <version>5.3.0</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.33</version>
</dependency>

2.3 配置分片规则

application.yml:


spring:
  shardingsphere:
    datasource:
      names: ds0,ds1
      ds0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/ds0?useSSL=false
        username: root
        password: 123456
      ds1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/ds1?useSSL=false
        username: root
        password: 123456
    rules:
      sharding:
        tables:
          t_order:
            actual-data-nodes: ds$->{0..1}.t_order_$->{0..1}
            database-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: db-inline
            table-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: table-inline
        sharding-algorithms:
          db-inline:
            type: INLINE
            props:
              algorithm-expression: ds$->{user_id % 2}
          table-inline:
            type: INLINE
            props:
              algorithm-expression: t_order_$->{user_id % 2}
    props:
      sql-show: true

注意:actual-data-nodes 必须覆盖所有真实表,否则插入时找不到表。

2.4 编写实体与 Mapper


@Data
@TableName("t_order")
public class Order {
    @TableId(type = IdType.ASSIGN_ID)
    private Long orderId;
    private Long userId;
    private BigDecimal amount;
    private LocalDateTime createTime;
}

@Mapper
public interface OrderMapper extends BaseMapper<Order> {
}

2.5 测试分片效果


@SpringBootTest
class OrderShardingTest {
    @Autowired
    private OrderMapper orderMapper;

    @Test
    void testInsert() {
        for (long userId = 1; userId <= 4; userId++) {
            Order order = new Order();
            order.setUserId(userId);
            order.setAmount(new BigDecimal("100.00"));
            order.setCreateTime(LocalDateTime.now());
            orderMapper.insert(order);
        }
    }

    @Test
    void testSelect() {
        // 携带分片键,精准路由
        Order order = orderMapper.selectById(1L);
        System.out.println(order);
    }
}

执行 testInsert 后,观察日志:userid=1 会路由到 ds1.torder1(1%2=1),userid=2 路由到 ds0.torder0。数据均匀分散。

三、常见坑与解决方案

3.1 分布式主键

分库分表后不能依赖数据库自增。ShardingSphere 内置雪花算法,通过 @TableId(type = IdType.ASSIGN_ID) 自动生成 19 位 Long 型 ID。

3.2 跨节点分页

LIMIT 10 OFFSET 100 会从所有分片取 110 条再内存归并,性能差。建议:避免深分页,或使用“上次最大 ID”作为游标。

3.3 分布式事务

跨库更新需引入 Seata 或使用柔性事务。ShardingSphere 支持 LOCAL、XA、BASE 事务,生产环境推荐 BASE + 消息最终一致。

3.4 数据迁移与扩容

从单库迁移到分库分表时,可使用 ShardingSphere-Scaling 或双写方案。扩容时建议成倍增加分片(如 2 库变 4 库),减少数据迁移量。

四、总结与延伸

分库分表是解决海量数据存储与高并发的有效手段,但会带来复杂性。核心步骤:

  1. 评估是否必须分(单表 < 500 万优先优化索引、归档)。
  2. 选择合适分片键与算法,确保均匀且高频查询携带。
  3. 使用 ShardingSphere-JDBC 透明化分片,减少侵入。
  4. 处理好全局 ID、跨节点查询、事务。

下一步:可研究 ShardingSphere-Proxy(代理层)、读写分离、弹性伸缩,以及结合 NewSQL 如 TiDB。

> 本文代码已上传至 GitHub 示例仓库,欢迎实践。