外卖霸王餐系统数据库分库分表策略:ShardingSphere实战案例
随着“霸王餐”活动用户量激增,单库单表的 participation_record(参与记录)表已突破千万级,写入延迟、查询超时频发。为提升系统扩展性与性能,采用 Apache ShardingSphere-JDBC 实现透明化分库分表。本文基于真实业务场景,展示按用户ID哈希分库、按活动ID范围分表的混合策略配置与代码实现。
业务场景与分片规则设计
- 数据规模:每日新增 50 万+ 参与记录;
- 查询模式:
- 用户维度:查某用户所有参与记录(高频);
- 活动维度:查某活动下所有参与者(运营后台);
- 分片策略:
- 分库:4 个库(ds0 ~ ds3),按 user_id % 4 路由;
- 分表:每个库内 8 张表(t_participation_0 ~ t_participation_7),按 activity_id % 8 路由。
Maven 依赖引入
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.3.2</version>
</dependency>
Spring Boot 配置分片规则
application.yml:
spring:
shardingsphere:
datasource:
names: ds0,ds1,ds2,ds3
ds0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://db0.baodanbao.com.cn:3306/baodan_part_0
username: root
password: xxx
ds1:
jdbc-url: jdbc:mysql://db1.baodanbao.com.cn:3306/baodan_part_1
# … 其他配置省略
ds2:
jdbc-url: jdbc:mysql://db2.baodanbao.com.cn:3306/baodan_part_2
ds3:
jdbc-url: jdbc:mysql://db3.baodanbao.com.cn:3306/baodan_part_3
rules:
sharding:
tables:
t_participation:
actual-data-nodes: ds$–>{0..3}.t_participation_$–>{0..7}
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: db–hash–mod
table-strategy:
standard:
sharding-column: activity_id
sharding-algorithm-name: table–hash–mod
sharding-algorithms:
db-hash-mod:
type: HASH_MOD
props:
sharding-count: 4
table-hash-mod:
type: HASH_MOD
props:
sharding-count: 8
props:
sql-show: true

实体类与 Mapper 定义
package baodanbao.com.cn.meituan.sharding.entity;
import lombok.Data;
@Data
public class ParticipationRecord {
private Long id;
private Long userId;
private Long activityId;
private String status;
private java.time.LocalDateTime createTime;
}
使用 MyBatis:
package baodanbao.com.cn.meituan.sharding.mapper;
import baodanbao.com.cn.meituan.sharding.entity.ParticipationRecord;
import org.apache.ibatis.annotations.*;
@Mapper
public interface ParticipationRecordMapper {
@Insert("INSERT INTO t_participation (user_id, activity_id, status, create_time) " +
"VALUES (#{userId}, #{activityId}, #{status}, #{createTime})")
void insert(ParticipationRecord record);
@Select("SELECT * FROM t_participation WHERE user_id = #{userId}")
java.util.List<ParticipationRecord> findByUserId(@Param("userId") Long userId);
@Select("SELECT * FROM t_participation WHERE activity_id = #{activityId}")
java.util.List<ParticipationRecord> findByActivityId(@Param("activityId") Long activityId);
}
关键查询行为验证
用户查询(精准路由)
List<ParticipationRecord> records = mapper.findByUserId(1001L);
ShardingSphere 日志显示:
Actual SQL: ds1 ::: SELECT * FROM t_participation_1 WHERE user_id = 1001
→ 1001 % 4 = 1 → 库 ds1;若 activity_id=2001,则 2001 % 8 = 1 → 表 t_participation_1。
活动查询(广播?不!)
注意:仅指定 activity_id 时,因未提供 user_id,无法确定库,ShardingSphere 会默认在所有库中查询对应表(即 4 个库 × 1 张表 = 4 次查询),非全表扫描但仍有性能损耗。
优化建议:运营后台查询应限定时间范围 + 分页,或建立异步同步到 OLAP 系统(如 Doris)。
分布式主键生成
避免自增 ID 冲突,使用 Snowflake:
spring:
shardingsphere:
rules:
sharding:
key-generators:
snowflake:
type: SNOWFLAKE
props:
worker-id: 1
实体类注解:
@TableId(type = IdType.ASSIGN_ID)
private Long id;
事务与批量插入
ShardingSphere 支持本地事务(非跨库):
@Transactional
public void batchInsert(List<ParticipationRecord> records) {
for (ParticipationRecord r : records) {
mapper.insert(r); // 自动路由到正确库表
}
}
若一批数据涉及多个库,需确保业务可接受最终一致性,或使用 Seata 等分布式事务方案。
运维注意事项
- 所有 DDL 需通过 ShardingSphere 执行,或手动在各库表同步;
- 监控各分片数据倾斜,必要时调整分片算法;
- 备份策略需覆盖所有物理库。
通过 ShardingSphere 的声明式配置,霸王餐系统在不修改核心业务逻辑的前提下,实现了水平扩展,支撑亿级参与记录的高效读写。
本文著作权归 俱美开放平台 ,转载请注明出处!

