【架构实战】海量数据存储:分库分表中间件实战

一、什么时候需要分库分表

单库单表的瓶颈:

问题 表现 阈值参考
单表数据量过大 查询变慢,索引失效 单表>1000万行
单库连接数不足 连接池耗尽 并发>1000
单库磁盘空间不足 写入失败 磁盘>80%
单库CPU/内存不足 响应变慢 CPU>70%

分库分表的目标:

  • 将数据分散到多个库/表,降低单库压力
  • 提升查询性能
  • 提高系统可用性

二、分库分表策略

1. 垂直分库

按业务模块拆分:

复制代码
单体数据库
├── 用户表
├── 订单表
├── 商品表
└── 支付表

↓ 垂直分库

用户库          订单库          商品库
├── 用户表      ├── 订单表      ├── 商品表
└── 地址表      └── 订单详情表  └── 商品分类表

2. 水平分表

按数据量拆分同一张表:

复制代码
order表(1亿行)

↓ 水平分表(按user_id取模)

order_0(user_id % 4 = 0)
order_1(user_id % 4 = 1)
order_2(user_id % 4 = 2)
order_3(user_id % 4 = 3)

3. 分片键选择

好的分片键:

  • 数据分布均匀
  • 查询时能精确定位分片
  • 不会频繁变更

常见分片键:

业务 分片键 分片方式
订单 user_id 取模
用户 user_id 取模
日志 时间 按月/年
商品 category_id 取模

三、ShardingSphere分库分表实战

1. 依赖配置

xml 复制代码
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core</artifactId>
    <version>5.3.0</version>
</dependency>

2. 分库分表配置

yaml 复制代码
spring:
  shardingsphere:
    datasource:
      names: ds0,ds1
      
      ds0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://192.168.1.100:3306/order_db_0
        username: root
        password: password
        
      ds1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://192.168.1.101:3306/order_db_1
        username: root
        password: password
    
    rules:
      sharding:
        tables:
          order:
            actual-data-nodes: ds${0..1}.order_${0..3}
            
            # 分库策略(按user_id取模)
            database-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: db-inline
            
            # 分表策略(按order_id取模)
            table-strategy:
              standard:
                sharding-column: order_id
                sharding-algorithm-name: table-inline
            
            # 分布式ID
            key-generate-strategy:
              column: order_id
              key-generator-name: snowflake
        
        sharding-algorithms:
          db-inline:
            type: INLINE
            props:
              algorithm-expression: ds${user_id % 2}
          
          table-inline:
            type: INLINE
            props:
              algorithm-expression: order_${order_id % 4}
        
        key-generators:
          snowflake:
            type: SNOWFLAKE
            props:
              worker-id: 1
    
    props:
      sql-show: true

3. 自定义分片算法

java 复制代码
@Component
public class OrderShardingAlgorithm implements StandardShardingAlgorithm<Long> {
    
    @Override
    public String doSharding(Collection<String> availableTargetNames, 
                              PreciseShardingValue<Long> shardingValue) {
        Long userId = shardingValue.getValue();
        
        // 按user_id取模
        int index = (int) (userId % availableTargetNames.size());
        
        return availableTargetNames.stream()
            .filter(name -> name.endsWith(String.valueOf(index)))
            .findFirst()
            .orElseThrow(() -> new RuntimeException("找不到分片"));
    }
    
    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, 
                                          RangeShardingValue<Long> shardingValue) {
        // 范围查询,返回所有分片
        return availableTargetNames;
    }
    
    @Override
    public Properties getProps() {
        return new Properties();
    }
    
    @Override
    public void init(Properties props) {
    }
    
    @Override
    public String getType() {
        return "ORDER_SHARDING";
    }
}

4. 绑定表配置

yaml 复制代码
# 绑定表:order和order_item使用相同的分片键,避免跨库JOIN
spring:
  shardingsphere:
    rules:
      sharding:
        binding-tables:
          - order,order_item
        
        tables:
          order:
            actual-data-nodes: ds${0..1}.order_${0..3}
            table-strategy:
              standard:
                sharding-column: order_id
                sharding-algorithm-name: table-inline
          
          order_item:
            actual-data-nodes: ds${0..1}.order_item_${0..3}
            table-strategy:
              standard:
                sharding-column: order_id
                sharding-algorithm-name: table-inline

5. 广播表配置

yaml 复制代码
# 广播表:在所有分片中都有完整数据(如字典表)
spring:
  shardingsphere:
    rules:
      sharding:
        broadcast-tables:
          - dict_data
          - region

四、分库分表的挑战

1. 跨库JOIN

问题: 分库后无法直接JOIN

解决方案:

java 复制代码
// 方案1:应用层JOIN
public OrderDetail getOrderDetail(Long orderId) {
    // 查询订单
    Order order = orderMapper.selectById(orderId);
    
    // 查询用户(可能在不同库)
    User user = userMapper.selectById(order.getUserId());
    
    // 应用层组装
    return new OrderDetail(order, user);
}

// 方案2:冗余字段
// 在order表中冗余user_name字段,避免跨库查询

2. 分布式事务

问题: 跨库操作无法使用本地事务

解决方案:

java 复制代码
// 使用Seata分布式事务
@GlobalTransactional
public void createOrder(OrderRequest request) {
    // 扣减库存(商品库)
    inventoryService.deductStock(request.getProductId(), request.getQuantity());
    
    // 创建订单(订单库)
    orderService.createOrder(request);
    
    // 扣减余额(用户库)
    accountService.deductBalance(request.getUserId(), request.getAmount());
}

3. 分页查询

问题: 跨分片分页查询性能差

解决方案:

java 复制代码
// 方案1:禁止深度分页
// 只允许查询前N页

// 方案2:游标分页
public List<Order> getOrdersByUserId(Long userId, Long lastOrderId, int pageSize) {
    // 使用游标分页,避免OFFSET
    return orderMapper.selectByUserIdAndLastId(userId, lastOrderId, pageSize);
}

// 方案3:ES辅助
// 将需要分页的数据同步到ES,通过ES分页

4. 全局唯一ID

java 复制代码
// 使用雪花算法生成全局唯一ID
@Service
public class OrderService {
    
    @Autowired
    private SnowflakeIdGenerator idGenerator;
    
    public Order createOrder(OrderRequest request) {
        Order order = new Order();
        order.setOrderId(idGenerator.nextId());  // 全局唯一ID
        order.setUserId(request.getUserId());
        // ...
        return order;
    }
}

五、数据迁移方案

1. 双写迁移

复制代码
阶段1:双写(写新库+旧库,读旧库)
阶段2:双写(写新库+旧库,读新库)
阶段3:单写(只写新库,读新库)

代码实现:

java 复制代码
@Service
public class OrderService {
    
    @Value("${migration.phase:1}")
    private int migrationPhase;
    
    public Order createOrder(OrderRequest request) {
        Order order = buildOrder(request);
        
        // 阶段1和2:双写
        if (migrationPhase >= 1) {
            newOrderMapper.insert(order);
        }
        if (migrationPhase <= 2) {
            oldOrderMapper.insert(order);
        }
        
        return order;
    }
    
    public Order getOrder(Long orderId) {
        // 阶段1:读旧库
        if (migrationPhase == 1) {
            return oldOrderMapper.selectById(orderId);
        }
        
        // 阶段2和3:读新库
        return newOrderMapper.selectById(orderId);
    }
}

2. 数据校验

java 复制代码
@Component
public class DataMigrationValidator {
    
    @Scheduled(fixedRate = 60000)
    public void validate() {
        // 抽样校验
        List<Long> sampleIds = getSampleOrderIds(1000);
        
        int mismatchCount = 0;
        for (Long id : sampleIds) {
            Order oldOrder = oldOrderMapper.selectById(id);
            Order newOrder = newOrderMapper.selectById(id);
            
            if (!Objects.equals(oldOrder, newOrder)) {
                mismatchCount++;
                log.error("数据不一致: orderId={}", id);
            }
        }
        
        double mismatchRate = (double) mismatchCount / sampleIds.size();
        if (mismatchRate > 0.001) {
            alertService.alert("数据迁移不一致率超过0.1%");
        }
    }
}

六、总结

分库分表是解决海量数据存储的有效方案:

  • 垂直分库:按业务模块拆分
  • 水平分表:按数据量拆分
  • ShardingSphere:成熟的分库分表中间件
  • 挑战:跨库JOIN、分布式事务、分页查询

实施建议:

  1. 优先考虑单库优化(索引、缓存)
  2. 确实需要时再分库分表
  3. 选择合适的分片键
  4. 做好数据迁移方案

思考题:你们系统有没有做分库分表?遇到了哪些挑战?


个人观点,仅供参考

相关推荐
Qiuner1 小时前
Pico 重塑Agent时代人与数据交互方式
windows·docker·ai·架构
心之伊始4 小时前
MySQL EXPLAIN 执行计划实战:从 type、Extra 到慢 SQL 定位与优化
java·架构·源码分析·csdn
国科安芯4 小时前
国科安芯推出商业航天级抗辐照全双工 RS485/422 收发器 ASC491S2Y
网络·分布式·单片机·架构·安全性测试
一切皆是因缘际会5 小时前
AI智能新时代
数据结构·人工智能·ai·架构
微三云、小叶6 小时前
新型消费积分商业模式拆解:盈利架构、衰减铸造模型与项目风控要点
架构·软件开发·商业模式·本地生活·商业思维·私域运营
SilentSamsara6 小时前
Python 微服务全链路:gRPC + 链路追踪 + 服务网格接入
开发语言·分布式·python·微服务·架构
candyTong6 小时前
Claude Code 的工具延迟加载机制
架构
葫芦和十三7 小时前
执行拓扑|Agent 不只是会什么,还要怎么跑
架构·agent·ai编程
国科安芯7 小时前
国科安芯推出商业航天级抗辐照半双工 RS485 收发器 ASC485S2Y
前端·单片机·嵌入式硬件·架构·安全性测试
小小龙学IT7 小时前
Go 后端开发实战:从单机千QPS到十万级微服务架构的演进之路
微服务·架构·golang