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

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

单库单表的瓶颈:

问题 表现 阈值参考
单表数据量过大 查询变慢,索引失效 单表>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. 做好数据迁移方案

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


个人观点,仅供参考

相关推荐
生成论实验室14 分钟前
生命降U:从分子共鸣到觉知涌现
人工智能·科技·架构·生活·信息与通信
四方云28 分钟前
基于大模型的AI外呼系统:架构演进与企业落地实践
人工智能·架构
珠海西格电力1 小时前
零碳园区管理系统如何守护能源与数据安全?
大数据·人工智能·分布式·架构·能源
Wave8451 小时前
嵌入式底层核心架构详解 (Cortex-M3)
stm32·架构
图导物联1 小时前
智能场馆导览系统核心技术架构解析
架构·智能场馆导览系统·智慧场馆导览系统
SilentSamsara1 小时前
Kubernetes 网络模型:CNI 插件与 Pod 间通信的底层实现
网络·云原生·容器·架构·kubernetes·k8s
techdashen1 小时前
用自家产品构建自家产品:Cloudflare Images 的工程架构解析
开发语言·架构·rust
roman_日积跬步-终至千里2 小时前
【案例题-知识点(2)】架构风格上(五大类详解)
数据库·架构·系统架构
SamDeepThinking2 小时前
秒杀系统怎么区分真实用户和黄牛脚本?
java·后端·架构
AI_大白2 小时前
让 Cursor 帮你搞定美股 4 个时段:AI Agent 的时段感知实战
python·架构