上亿订单如何做分库分表?

随着业务的快速增长,传统单库单表架构在订单系统中逐渐显现性能瓶颈。当订单量达到亿级甚至10亿级时,单库单表将面临:

  • 查询性能下降:SELECT操作越来越慢,索引命中率降低
  • 写入压力大:高并发下,表锁导致吞吐量瓶颈
  • 备份恢复困难:数据量大,备份、迁移、恢复耗时长
  • 扩展性差:无法水平扩展,服务器成本高

为了解决这些问题,分库分表 Sharding成为必然选择。本文结合 Java 技术栈,详细讲解如何设计和实现一个支撑 上亿订单的分库分表方案。

一、分库分表策略

分库分表策略主要有两类:

  • 水平分表:将同一库的大表按字段拆分为多张表
  • 水平分库:将数据拆到不同数据库实例中,减轻单库压力

常用策略是 先分库再分表,保证每个数据库表数量可控,并支持高并发访问。

1. 分表设计

假设订单表结构如下:

CREATE TABLE `orders` (
    `order_id` BIGINT NOT NULL,
    `user_id` BIGINT NOT NULL,
    `product_id` BIGINT NOT NULL,
    `status` INT NOT NULL,
    `create_time` DATETIME NOT NULL,
    PRIMARY KEY(`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

分表规则:

总表数:128 张,表名 orders_0 ~ orders_127

分表公式:

table_index = order_id % 128
2. 分库设计

每库最多存放 32 张表,4 个库,命名规则:order_db_0 ~ order_db_3

分库公式:

db_index = table_index / 32

分库分表示意

order_id -> table_index = order_id % 128
table_index -> db_index = table_index / 32

order_id = 1000000001
table_index = 1000000001 % 128 = 17
db_index = 17 / 32 = 0
结果: 存在 order_db_0.orders_17

二、Java 路由实现

1. 分库分表工具类
public class ShardingUtil {

    private static final int TOTAL_TABLE = 128;
    private static final int TABLE_PER_DB = 32;

    public static String getDbName(long orderId) {
        int tableIndex = (int) (orderId % TOTAL_TABLE);
        int dbIndex = tableIndex / TABLE_PER_DB;
        return "order_db_" + dbIndex;
    }

    public static String getTableName(long orderId) {
        int tableIndex = (int) (orderId % TOTAL_TABLE);
        return "orders_" + tableIndex;
    }

    public static String getFullTableName(long orderId) {
        return getDbName(orderId) + "." + getTableName(orderId);
    }

    public static void main(String[] args) {
        long orderId = 1000000001L;
        System.out.println(getFullTableName(orderId)); // order_db_0.orders_17
    }
}
2. MyBatis 动态表名示例
<select id="selectOrderById" resultType="Order">
    SELECT * FROM ${tableName}
    WHERE order_id = #{orderId}
</select>

调用方法:

public Order getOrderById(long orderId) {
    String tableName = ShardingUtil.getTableName(orderId);
    return orderMapper.selectOrderById(tableName, orderId);
}

三、SQL 可执行脚本

假设创建 4 个库,每个库 32 张表:

-创建数据库

CREATE DATABASE IF NOT EXISTS order_db_0 DEFAULT CHARSET=utf8mb4;
CREATE DATABASE IF NOT EXISTS order_db_1 DEFAULT CHARSET=utf8mb4;
CREATE DATABASE IF NOT EXISTS order_db_2 DEFAULT CHARSET=utf8mb4;
CREATE DATABASE IF NOT EXISTS order_db_3 DEFAULT CHARSET=utf8mb4;

创建分表

DELIMITER //
CREATE PROCEDURE create_tables()
BEGIN
    DECLARE i INT DEFAULT 0;
    DECLARE db_index INT DEFAULT 0;
    WHILE i < 128 DO
        SET db_index = FLOOR(i / 32);
        SET @sql_text = CONCAT(
            'CREATE TABLE IF NOT EXISTS order_db_', db_index, '.orders_', i % 32,
            ' (order_id BIGINT NOT NULL, user_id BIGINT NOT NULL, product_id BIGINT NOT NULL, ',
            'status INT NOT NULL, create_time DATETIME NOT NULL, PRIMARY KEY(order_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;'
        );
        PREPARE stmt FROM @sql_text;
        EXECUTE stmt;
        DEALLOCATE PREPARE stmt;
        SET i = i + 1;
    END WHILE;
END //
DELIMITER ;

CALL create_tables();

四、分库分表流程图
在这里插入图片描述

五、分库分表查询优化策略

  • 优先按主键查询
  • 避免跨库查询,提高性能
  • 表内索引优化
  • 建立 user_id、create_time 等索引支持常用查询
  • 聚合表或缓存表
  • 对统计报表类查询,维护单独聚合表或缓存数据
  • 异步写入 / MQ削峰
  • 使用 Kafka、RabbitMQ 等缓冲高并发写入

六、实战经验与注意事项

  • 表数量控制:单库 < 50 张表,每张表 < 1 亿行

  • 分库分表规则稳定:上线后尽量不修改

  • 全局 ID 生成器:推荐 Snowflake 分布式 ID

  • 跨库事务谨慎:尽量避免分布式事务,使用最终一致性策略

  • 监控告警:定期统计各库表行数,防止单表过大

七、总结

  • 单库单表在 10 亿订单量面前性能瓶颈明显
  • 解决方案:分库分表 + Java 路由 + 动态 SQL
  • 优化策略:索引、聚合表、异步写入、监控告警
  • 关键点:ID生成、事务设计、表数量控制

通过合理规划分库分表方案,系统可以从百万级订单平滑扩展到十亿级,支持高并发、低延迟业务场景。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云起川南

小主的鼓励就是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值