【中】十亿数据的订单分表
2025/12/31大约 5 分钟
当数据量超过千万,达到亿级的时候,基本的MySQL已无法满足需求。想解决这个问题,从硬件层面可以升级更好的服务服务器比如云服务器或换更好的数据库比如postgresql,从软件层面就需要分库分表了。
这篇文章以十亿的数据量,订单表的形式来进行分库分表的学习,需要解决以下问题
- 订单的插入
- 订单的修改
- 订单查询(基于id查询、基于订单号查询、基于用户查询)
一、技术选型
MySQL的分库分表,首选的技术组件是 Apache ShardingSphere,其它的就是常规组件了
- Apache ShardingSphere 5.2.1
- JDK 17
- SpringBoot 3.0.3
- MyBatis 3.5.14
- MySQL 5.8
二、分库分表策略
- 2库 * 64表 = 128表
- 十亿 / 128表 ≈ 781万
提示
基于实际情况可以增加为4库 * 32表,这样可以承担的并发更大一些,但这里只是为了演示功能,少一点数据库更方便操作
三、项目搭建
分库分表的项目和普通项目几乎一模一样,唯一不同的就是 application.yaml 的配置
项目目录

3-1、一个简单的例子(基于id的查询)
主要是看看分库分表的写法和常规写法有啥不一样的(其实完全一样)
Controller
@GetMapping("/{orderId}")
public Result<OrderVO> getOrderById(@PathVariable Long orderId) {
OrderVO order = orderService.getOrderById(orderId);
return Result.success(order);
}ServiceImpl
@Override
public OrderVO getOrderById(Long orderId) {
log.info("查询订单详情, orderId: {}", orderId);
// 根据orderId查询,ShardingSphere自动路由到对应分片(单片查询,性能最优)
Order order = orderMapper.selectById(orderId);
if (order == null) {
log.warn("订单不存在, orderId: {}", orderId);
return null;
}
// 查询订单明细(因为是绑定表,也会路由到同一分片)
List<OrderItem> items = orderItemMapper.selectByOrderId(orderId);
return convertToVO(order, items);
}Mapper.xml
<select id="selectById" resultMap="BaseResultMap">
SELECT
order_id,
order_no,
user_id,
total_amount,
pay_amount,
status,
pay_status,
receiver_name,
receiver_phone,
receiver_address,
create_time,
update_time,
pay_time,
finish_time,
remark
FROM t_order
WHERE order_id = #{orderId}
</select>3-2、application.yaml 解析
application.yaml 里面还有一些其他的配置比如SpringBoot、MyBatis的配置,这里只看 ShardingSphere的配置
application.yaml
spring:
# ShardingSphere 分片配置
shardingsphere:
# 数据源配置(2个MySQL实例)
datasource:
names: ds0,ds1
# 数据源 0 (端口 3307)
ds0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3307/order_db_0?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: root123456
hikari:
maximum-pool-size: 50
minimum-idle: 10
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
connection-test-query: SELECT 1
# 数据源 1 (端口 3308)
ds1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3308/order_db_1?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: root123456
hikari:
maximum-pool-size: 50
minimum-idle: 10
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
connection-test-query: SELECT 1
# 分片规则配置
rules:
sharding:
# 分片表配置
tables:
# 订单主表
t_order:
# 实际数据节点:2个库 × 64张表 = 128个分片
actual-data-nodes: ds$->{0..1}.t_order_$->{0..63}
# 分库策略
database-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: db-mod-algorithm
# 分表策略
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: table-mod-algorithm
# 分布式主键生成策略
key-generate-strategy:
column: order_id
key-generator-name: snowflake-generator
# 订单明细表(绑定表,与订单表使用相同分片键)
t_order_item:
actual-data-nodes: ds$->{0..1}.t_order_item_$->{0..63}
database-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: db-mod-algorithm
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: table-mod-item-algorithm
# 绑定表配置(避免笛卡尔积)
# SELECT * FROM t_order o
# JOIN t_order_item i ON o.order_id = i.order_id
# WHERE o.order_id = 123456;
# -- 问题:
# -- t_order 在 ds0.t_order_15
# -- t_order_item 可能被认为在所有分片
# -- 会尝试128次JOIN!
# -- 使用绑定表:智能路由
# -- 知道 t_order 和 t_order_item 使用相同分片键
# -- 只在 ds0.t_order_15 和 ds0.t_order_item_15 JOIN
# -- 只1次JOIN!
binding-tables:
- t_order,t_order_item
# 广播表配置(数据字典表,每个库都有完整数据)
# 可以理解为不同分库的表,如果不是每个库都存一份的话,链接查询的时候会很困难
# 对广播表进行写操作时,ShardingSphere 会自动将这个操作"广播"到所有数据库
broadcast-tables:
- t_order_status_dict
# 分片算法定义
sharding-algorithms:
# 分库算法:(order_id % 128) / 64 得到 0-1
db-mod-algorithm:
type: INLINE
props:
algorithm-expression: ds$->{(order_id % 128) / 64}
# 分表算法:order_id % 64 得到 0-63
table-mod-algorithm:
type: INLINE
props:
algorithm-expression: t_order_$->{order_id % 64}
# 订单明细表分表算法:order_id % 64 得到 0-63
table-mod-item-algorithm:
type: INLINE
props:
algorithm-expression: t_order_item_$->{order_id % 64}
# 分布式主键生成器
key-generators:
snowflake-generator:
type: SNOWFLAKE
props:
# 工作机器ID (0-1023),多实例部署时需要不同
worker-id: 1
# ShardingSphere 属性配置
props:
# 是否打印SQL日志(开发环境开启,生产环境关闭)
sql-show: true
# 是否打印简单SQL
sql-simple: false
# SQL执行器线程池大小
executor-size: 16
# 每次查询最多使用的连接数
max-connections-size-per-query: 2四、查询解析
如果认真看了上面的 application 的话,就知道分表的规则是基于 orderId去hash计算的
- (order_id % 128) / 64 得到 0-1
- order_id % 64 得到 0-63
4-1、有orderId的时候
比如新增或基于orderId查询和更新的时候,通过上面的算法会路由到唯一的库和唯一的表,这个时候和单表查询是一样的,效率最高
4-2、没有orderId的时候
如果使用orderNo(非orderId来操作呢?),又或者基于 userId来查询呢?(一个用户的的订单数据会路由到无数张表里面去)
答案是没有好的办法,扫描所以的表,然后做数据汇总。当基于 orderNo去查询的时候,可以看到控制台把每个库的每个表都扫描了一遍,但结果很快,因为它是多线程去执行的