数据库分库分表实战:ShardingSphere路由策略与跨片查询优化方案

分库分表的触发时机与架构决策

数据库分库分表不是性能优化的首选方案。当单表数据量超过5000万行、单库QPS超过3万、或数据增长速度导致备份窗口无法覆盖时,才考虑分库分表。过早分片引入的分布式事务、跨片查询、数据迁移等问题远比单库慢查询更难解决。

分库分表有两种架构路径:客户端分片(ShardingSphere-JDBC)和代理分片(ShardingSphere-Proxy)。客户端分片性能更好(无网络跳转),但与应用强耦合;代理分片对应用透明,但增加一跳延迟。读写分离场景推荐代理模式,纯分片场景推荐客户端模式。

ShardingSphere-JDBC路由策略配置

ShardingSphere支持精确分片、范围分片、复合分片和Hint分片四种路由策略。选择路由策略的依据是查询模式。

# application.yml - ShardingSphere分片配置
spring:
  shardingsphere:
    datasource:
      names: ds0,ds1
      ds0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://mysql-0:3306/order_db_0
      ds1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://mysql-1:3306/order_db_1
    rules:
      sharding:
        tables:
          t_order:
            actual-data-nodes: ds${0..1}.t_order_${0..15}
            table-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: order-table-inline
            database-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: order-db-mod
            key-generate-strategy:
              column: order_id
              key-generator-name: snowflake
        sharding-algorithms:
          order-table-inline:
            type: INLINE
            props:
              algorithm-expression: t_order_${user_id % 16}
          order-db-mod:
            type: MOD
            props:
              sharding-count: 2

此配置中,user_id决定数据路由:先按user_id % 2选库,再按user_id % 16选表。这种两级分片策略确保同一用户的所有订单落在同一库同一表,避免跨库查询。

分片键选择的核心原则

分片键决定了数据的分布和查询的效率。选错分片键等于将分库分表的性能瓶颈从数据库转移到应用层。

高基数原则:分片键的值域必须足够大,保证数据均匀分布。user_id、order_id是好的分片键,status、type等低基数字段会导致数据倾斜。

查询亲和原则:分片键必须出现在最频繁的查询条件中。订单系统最频繁的查询是”按用户查订单”,因此user_id是合理的分片键。

避免跨片JOIN原则:有关联关系的表尽量使用相同分片键。订单表和订单明细表都以user_id为分片键,可以保证关联查询落在同一分片上。

当查询条件不含分片键时,ShardingSphere会执行全路由扫描(所有分片都查),性能退化到分片前的水平甚至更差。这种场景需要通过广播表、绑定表或冗余字段来规避。

跨片查询优化策略

跨片查询是分库分表后最大的性能挑战。以下三种策略按优先级排列:

1. 绑定表(Binding Tables)

将分片规则相同的表声明为绑定表,ShardingSphere在解析SQL时将笛卡尔积查询优化为内连接查询,避免无效的跨片组合。

# 绑定表配置
rules:
  sharding:
    binding-tables:
      - t_order,t_order_item
    tables:
      t_order_item:
        actual-data-nodes: ds${0..1}.t_order_item_${0..15}
        table-strategy:
          standard:
            sharding-column: user_id
            sharding-algorithm-name: order-table-inline

2. 广播表(Broadcast Tables)

小数据量的维度表(如地区表、品类表)配置为广播表,每个库都存一份全量数据,避免跨库JOIN。

# 广播表配置
rules:
  sharding:
    broadcast-tables:
      - t_region
      - t_category

3. 冗余字段与宽表

在订单表中冗余存储merchant_name字段,避免查询订单时需要关联商户表。冗余带来的数据一致性风险通过消息队列异步同步解决。

-- 冗余字段同步方案
UPDATE t_order SET merchant_name = ? WHERE merchant_id = ?;
-- ShardingSphere自动路由到所有相关分片

分页查询的跨片归并优化

分页查询在分片环境下性能极差。SELECT * FROM t_order ORDER BY create_time LIMIT 100000, 10在16个分片上实际查询的是16 x 100010条记录再归并,内存和时间消耗远超预期。

优化方案:禁止深度分页,改为游标分页。

// 游标分页实现
public PageResult<Order> queryByCursor(Long lastOrderId, int pageSize) {
    String sql = "SELECT * FROM t_order " +
        "WHERE order_id > ? ORDER BY order_id LIMIT ?";
    List<Order> orders = jdbcTemplate.query(sql,
        (rs, rowNum) -> mapOrder(rs),
        lastOrderId, pageSize);
    return new PageResult<>(
        orders,
        orders.isEmpty() ? null : orders.get(orders.size() - 1).getOrderId()
    );
}

游标分页要求排序字段与分片键一致或有索引。当排序字段不是分片键时,仍需全分片扫描。这种场景推荐引入ES作为查询索引,MySQL仅作为数据存储,复杂查询走ES,精确查询走MySQL分片。

数据迁移与扩容方案

分片扩容(如从2库扩到4库)需要数据迁移。ShardingSphere的弹性伸缩模块支持在线扩容,但该功能仍在社区孵化阶段。生产环境更成熟的方案是双写+数据校验:

1. 应用层同时写旧分片和新分片(双写),新分片按新规则路由。

2. 后台任务按分片键范围将旧数据迁移到新分片。

3. 数据校验脚本对比新旧分片数据一致性。

4. 确认一致后切换读流量到新分片,再切换写流量。

5. 下线旧分片。整个过程中业务零中断。

分库分表是数据库层面的架构决策,实施前必须评估3年内的数据增长预期。过细的分片增加运维成本,过粗的分片很快需要扩容。起始分片数建议为预估峰值的2-4倍,留出足够的扩容缓冲期。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/shu-ju-ku-fen-ku-fen-biao-shi-zhan-shardingsphere-lu-you-ce/

(0)
小编小编
上一篇 2小时前
下一篇 2小时前

相关推荐