随着业务的快速增长,传统单库单表架构在订单系统中逐渐显现性能瓶颈。当订单量达到亿级甚至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生成、事务设计、表数量控制
通过合理规划分库分表方案,系统可以从百万级订单平滑扩展到十亿级,支持高并发、低延迟业务场景。
798

被折叠的 条评论
为什么被折叠?



