Skip to content

ShardingSphere — 分库分表

架构概览

Apache ShardingSphere 提供两种接入模式:

ShardingSphere-JDBC(进程内):
  应用 ──► ShardingSphere-JDBC ──► MySQL1
                               ──► MySQL2
  优点:无额外网络开销,性能最优
  缺点:与应用语言绑定(Java)

ShardingSphere-Proxy(独立代理):
  应用 ──► ShardingSphere-Proxy ──► MySQL1
                                ──► MySQL2
  优点:语言无关,透明接入
  缺点:额外网络跳转

分片策略

分库分表规则

yaml
# shardingsphere-jdbc 配置(YAML)
dataSources:
  ds0:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://mysql0:3306/order_db0
    username: root
    password: password
  ds1:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    jdbcUrl: jdbc:mysql://mysql1:3306/order_db1
    username: root
    password: password

rules:
  - !SHARDING
    tables:
      order:
        actualDataNodes: ds${0..1}.order_${0..3}  # 2库 × 4表 = 8张表
        databaseStrategy:
          standard:
            shardingColumn: user_id
            shardingAlgorithmName: db-inline
        tableStrategy:
          standard:
            shardingColumn: order_id
            shardingAlgorithmName: table-inline
        keyGenerateStrategy:
          column: order_id
          keyGeneratorName: snowflake

    shardingAlgorithms:
      db-inline:
        type: INLINE
        props:
          algorithm-expression: ds${user_id % 2}
      table-inline:
        type: INLINE
        props:
          algorithm-expression: order_${order_id % 4}

    keyGenerators:
      snowflake:
        type: SNOWFLAKE

分片算法类型

算法说明适用场景
INLINE表达式分片简单取模、范围
STANDARD精确 + 范围分片自定义复杂逻辑
COMPLEX多列分片联合分片键
HINT强制路由不含分片键的查询
MOD取模均匀分布
HASH_MOD哈希取模字符串分片键
BOUNDARY_RANGE范围边界按时间/ID范围分片

绑定表(避免跨库 JOIN)

yaml
rules:
  - !SHARDING
    bindingTables:
      - order, order_item  # 同一分片键,路由到同一分片
sql
-- 绑定表 JOIN 不会产生笛卡尔积
SELECT o.*, oi.* 
FROM order o 
JOIN order_item oi ON o.order_id = oi.order_id 
WHERE o.user_id = 123;
-- 路由到:ds1.order_1 JOIN ds1.order_item_1(同一分片)

广播表(字典表)

yaml
rules:
  - !SHARDING
    broadcastTables:
      - province  # 每个分片都有完整数据
      - city

读写分离

yaml
rules:
  - !READWRITE_SPLITTING
    dataSources:
      rw-ds:
        staticStrategy:
          writeDataSourceName: write-ds
          readDataSourceNames:
            - read-ds-0
            - read-ds-1
        loadBalancerName: round-robin

    loadBalancers:
      round-robin:
        type: ROUND_ROBIN
java
// 强制路由到主库(事务后立即读取)
HintManager hintManager = HintManager.getInstance();
hintManager.setWriteRouteOnly();
try {
    // 此处查询强制走主库
    Order order = orderMapper.findById(orderId);
} finally {
    hintManager.close();
}

分布式事务

Seata AT 模式

yaml
rules:
  - !TRANSACTION
    defaultType: XA  # 或 BASE(Seata)
    providerType: Seata
java
@Transactional
@ShardingTransactionType(TransactionType.BASE)  // Seata 柔性事务
public void createOrder(Order order, List<OrderItem> items) {
    orderMapper.insert(order);
    items.forEach(orderItemMapper::insert);
    inventoryService.deduct(items);  // 跨服务调用
}

XA 强一致事务

java
@Transactional
@ShardingTransactionType(TransactionType.XA)
public void transferMoney(Long fromUserId, Long toUserId, BigDecimal amount) {
    // 跨分片的转账操作,XA 保证原子性
    accountMapper.deduct(fromUserId, amount);
    accountMapper.add(toUserId, amount);
}

数据迁移(ShardingSphere Scaling)

yaml
# 迁移配置
rules:
  - !MIGRATION
    input:
      workerThread: 40
      batchSize: 1000
    output:
      workerThread: 40
      batchSize: 1000
    streamChannel:
      type: MEMORY
      props:
        block-queue-size: 10000
sql
-- 创建迁移任务
MIGRATE TABLE ds.order INTO sharding_db.order;

-- 查看迁移状态
SHOW MIGRATION STATUS;

-- 验证数据一致性
CHECK MIGRATION STATUS;

-- 完成迁移(切换流量)
COMMIT MIGRATION;

最佳实践

分片键选择原则

1. 高基数:分片键的值要足够分散(避免数据倾斜)
   ✅ user_id, order_id
   ❌ status(只有几个值)

2. 查询频率:大多数查询都包含分片键
   ✅ WHERE user_id = ?
   ❌ WHERE create_time BETWEEN ? AND ?(需要全分片扫描)

3. 不可变:分片键的值不应该变化
   ✅ user_id(用户ID不变)
   ❌ status(状态会变化,变化后路由到不同分片)

避免跨分片查询

sql
-- ❌ 跨分片查询(性能差)
SELECT * FROM order WHERE create_time > '2024-01-01';
-- 需要查询所有分片,然后合并结果

-- ✅ 带分片键的查询(路由到单分片)
SELECT * FROM order WHERE user_id = 123 AND create_time > '2024-01-01';

分页查询优化

sql
-- ❌ 深分页(性能极差)
SELECT * FROM order WHERE user_id = 123 ORDER BY create_time DESC LIMIT 10000, 10;
-- ShardingSphere 需要从每个分片取 10010 条,然后合并排序

-- ✅ 游标分页(推荐)
SELECT * FROM order 
WHERE user_id = 123 AND create_time < '2024-01-15 10:00:00'
ORDER BY create_time DESC 
LIMIT 10;

故障处理案例

案例一:数据倾斜

现象:某个分片数据量远大于其他分片。

原因:分片键选择不当(如按 status 分片)或热点用户数据集中。

解决

  • 重新选择分片键(需要数据迁移)
  • 使用复合分片键
  • 对热点数据单独处理

案例二:跨分片 ORDER BY 性能问题

现象:带排序的查询响应时间很长。

原因:ShardingSphere 需要从所有分片获取数据,在内存中合并排序。

解决

yaml
# 限制归并结果集大小
props:
  max-connections-size-per-query: 1
  sql-show: true
  # 流式归并(减少内存占用)
  executor-size: 20

案例三:分布式 ID 重复

现象:使用数据库自增 ID 导致跨分片 ID 冲突。

解决:使用 Snowflake 或 UUID:

yaml
keyGenerators:
  snowflake:
    type: SNOWFLAKE
    props:
      worker-id: 1  # 每个实例不同的 worker-id

PaaS 中间件生态系统深度学习文档