外卖小程序存储方案设计:订单日志、商品库高效存储实现思路
外卖小程序业务持续运行过程中,商品、订单、操作日志会不断产生海量数据。如果全部数据混存于单张数据表,随着业务增长会出现查询慢、写入阻塞、数据库压力大等问题。合理设计存储方案,区分商品库、订单、操作日志的存储策略,搭配MySQL持久化、Redis缓存、冷热数据归档,兼顾查询性能、数据一致性与存储成本。
本文将从存储整体架构、商品库存储设计、订单存储方案、操作日志存储、关键代码示例、冷热归档策略、统计查询、技术选型、后期扩展方向讲解外卖小程序存储落地方案。
一、外卖小程序存储整体架构设计
采用关系数据库+分布式缓存+归档存储的多层存储架构。MySQL承担核心业务持久化,Redis承担热点数据缓存,历史冷数据做归档处理;区分热数据(正在交易、热点商品)、冷数据(历史订单、过期日志),避免冷数据拖累在线业务性能。
1. 业务应用层
用户小程序、商户端、骑手端、管理后台,业务服务不直接裸访问数据库,优先读取缓存。
2. 缓存层(Redis)
- 热点商品、商家基础信息缓存
- 库存计数器、骑手地理位置
- 购物车、接口分布式锁,减轻MySQL读压力
3. 主数据库层(MySQL)
- 商品、订单、用户、骑手、商户等热业务数据
- 正在进行中的订单、近期业务日志
4. 归档存储层
- 历史归档订单、过期操作日志,归档表/对象存储,用于对账、审计追溯,不参与线上实时业务查询
整体架构示意:
用户/商户/骑手端
|
业务服务 → Redis缓存 → MySQL主库(热数据)
↓
归档定时任务 → 归档库(历史订单、过期日志)
二、商品库存储模块设计
商品库包含商品主表、规格子表;高频访问的商家商品列表使用Redis做热点缓存,数据库保存真实源数据,缓存失效自动回源。
商品主数据表:
CREATE TABLE goods (
id INT PRIMARY KEY AUTO_INCREMENT,
shop_id INT COMMENT '所属商户ID',
name VARCHAR(100) COMMENT '商品名称',
image VARCHAR(255) COMMENT '商品图片',
price DECIMAL(10,2) COMMENT '售价',
stock INT COMMENT '库存',
status TINYINT DEFAULT 1 COMMENT '1上架 0下架',
update_time DATETIME COMMENT '更新时间'
);
商品规格SKU子表,处理多规格商品:
CREATE TABLE goods_sku (
id INT PRIMARY KEY AUTO_INCREMENT,
goods_id INT COMMENT '关联商品主表ID',
sku_name VARCHAR(100) COMMENT '规格名称',
sku_price DECIMAL(10,2) COMMENT '规格价格',
sku_stock INT COMMENT '规格库存',
status TINYINT DEFAULT 1
);
商品查询接口(缓存+数据库回源示例 Node.js)
router.get('/goods/list', async(req,res)=>{
const shopId = req.query.shopId;
const cacheKey = `goods:shop:${
shopId}`;
//优先读取Redis缓存
let cacheData = await redis.get(cacheKey);
if(cacheData){
return res.json({
code:200,data:JSON.parse(cacheData)});
}
//缓存未命中,回源数据库
const goodsList = await Goods.findAll({
where:{
shop_id:shopId,
status:1
}
})
//写入缓存,设置过期时间,商户更新商品主动删除对应缓存key
await redis.set(cacheKey,JSON.stringify(goodsList),'EX',1800);
res.json({
code:200,
data:goodsList
})
})
业务要点:商户修改商品、上下架时,主动删除对应shopId的缓存key,保证缓存与数据库一致性,避免脏缓存。
三、订单存储方案设计
订单是外卖系统核心交易数据,区分热订单和历史冷订单。热订单保存主库(待支付、已支付、配送中、近90天已完成订单);超过90天已完结订单迁移至归档表,减轻主库数据体量。
订单主表(热订单):
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(64) UNIQUE COMMENT '订单编号',
user_id INT COMMENT '用户ID',
shop_id INT COMMENT '商户ID',
rider_id INT COMMENT '骑手ID',
goods_amount DECIMAL(10,2),
delivery_fee DECIMAL(10,2),
pay_amount DECIMAL(10,2),
status TINYINT COMMENT '订单状态',
create_time DATETIME,
finish_time DATETIME COMMENT '完成时间'
);
订单归档表,表结构和主表保持一致:
CREATE TABLE orders_archive (
id INT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(64),
user_id INT,
shop_id INT,
rider_id INT,
goods_amount DECIMAL(10,2),
delivery_fee DECIMAL(10,2),
pay_amount DECIMAL(10,2),
status TINYINT,
create_time DATETIME,
finish_time DATETIME
);
创建订单函数示例,订单写入主库,不写入缓存,交易数据以MySQL为准,缓存仅用于查询加速:
async function createOrder(userId,cart,shopId,deliveryFee){
//计算金额
let goodsTotal = 0;
cart.forEach(item=>{
goodsTotal += item.price * item.number;
})
const payTotal = goodsTotal + deliveryFee;
const order = await Orders.create({
user_id:userId,
shop_id:shopId,
goods_amount:goodsTotal,
delivery_fee:deliveryFee,
pay_amount:payTotal,
status:1,
create_time:new Date()
})
return order;
}
订单业务存储流转:
新订单写入orders主热表
↓
订单完结,保留在主表90天
↓
定时归档任务夜间低峰执行,迁移至orders_archive归档表
↓
线上业务查询优先查主表;需要查询久远历史订单再访问归档表
重要:交易订单不能把Redis当做数据库,Redis只做查询缓存,所有事务、支付、状态变更全部落盘MySQL。
四、操作日志存储模块设计
订单变更日志、商户操作日志、骑手操作日志,日志属于追加写入数据,数据量增长很快,不适合和业务表混合。日志只做查询追溯,极少修改,同样执行冷热分离策略。
订单操作日志表:
CREATE TABLE order_oper_log (
id INT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(64) COMMENT '关联订单号',
before_status TINYINT COMMENT '变更前状态',
after_status TINYINT COMMENT '变更后状态',
operator_id INT COMMENT '操作人ID',
operator_type TINYINT COMMENT '1用户 2商户 3骑手 4平台',
remark VARCHAR(500) COMMENT '操作备注',
create_time DATETIME
);
归档过期日志表:
CREATE TABLE order_oper_log_archive (
id INT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(64),
before_status TINYINT,
after_status TINYINT,
operator_id INT,
operator_type TINYINT,
remark VARCHAR(500),
create_time DATETIME
);
写入操作日志示例:
async function saveOrderOperLog(orderNo,beforeStatus,afterStatus,operatorId,operatorType,remark){
await OrderOperLog.create({
order_no:orderNo,
before_status:beforeStatus,
after_status:afterStatus,
operator_id:operatorId,
operator_type:operatorType,
remark:remark,
create_time:new Date()
})
}
日志存储策略:
- 近期日志保存在
order_oper_log,用于线上订单问题排查; - 定时任务将超过90天日志迁移到
order_oper_log_archive; - 日志只INSERT,业务逻辑禁止UPDATE、DELETE,保证审计追溯。
五、定时冷热归档任务实现
归档任务放在业务低峰凌晨执行,分批迁移数据,禁止一次性大批量迁移,避免锁表影响线上业务。
伪SQL逻辑示例:
-- 分批取出已经完成超过90天的订单
SELECT * FROM orders
WHERE finish_time < DATE_SUB(NOW(),INTERVAL 90 DAY) LIMIT 500;
-- 插入归档表,删除主表已迁移数据,小批量循环执行
INSERT INTO orders_archive SELECT ... ;
DELETE FROM orders WHERE id IN (...);
业务约束:归档任务不能在午晚外卖高峰执行,凌晨2‑4点窗口运行;迁移过程中限制每次批次数量,防止数据库IO打满。
六、后台数据统计查询实现
统计报表尽量不直接查询业务主表,优先使用从库、归档表做统计分析,避免统计SQL抢占线上主库性能。
统计每日订单数量(查询从库):
SELECT
DATE(create_time) AS day, COUNT(*) AS order_count
FROM orders
GROUP BY day;
统计商品销量:
SELECT shop_id,COUNT(*) AS sale_num
FROM orders o LEFT JOIN order_item oi ON o.id=oi.order_id
WHERE o.status=5
GROUP BY shop_id;
七、开发技术选型
小程序端
- 微信小程序原生开发
- UniApp
- Taro
后端
- Java Spring Boot
- Node.js
- PHP Laravel
存储组件
- MySQL 8.0:业务主库,支持读写分离,一主多从
- Redis:热点商品缓存、库存、分布式锁
- 对象存储:可用于导出归档CSV文件存储
服务部署
- Linux服务器
- Nginx
- Docker
SpringBoot项目目录参考
takeout‑storage‑demo
├── goods 商品库模块
├── order 订单业务模块
├── order‑log 订单操作日志模块
├── archive‑task 冷热归档定时任务
├── cache‑manage Redis缓存管理
└── admin 管理后台
八、后期扩展方向
基础存储架构搭建完成,可迭代增强存储能力。
商品库增强
- 商品大流量场景引入多级缓存(本地缓存+Redis)
- 商品搜索同步至ES搜索引擎,提升商品检索速度
订单存储增强
- 数据量持续上涨,引入分表策略,按时间分月表
- 海量统计场景,同步数据至ClickHouse做离线分析报表,释放MySQL压力
日志与审计
- 重要操作日志同步至ES,支持全文检索,快速排查工单纠纷
- 敏感字段数据脱敏存储,满足隐私合规
运维保障
- 定时全量备份+binlog增量备份,归档数据也需要备份
- 增加存储监控,监控主库慢SQL、缓存命中率、归档任务运行状态
总结
外卖小程序存储设计核心,商品靠缓存加速查询,订单做好冷热分离,操作日志只追加不修改。不能把所有数据全部堆积在业务主表。
商品库使用Redis缓存热点数据,同时保证更新时缓存失效;交易订单以MySQL作为唯一可信源,Redis只做查询加速;订单、操作日志按照时间窗口做定时归档,保证主库维持较小数据体量,保障线上业务读写性能。在此基础上,业务增长后可以继续演进分表、分析型存储,适配更大规模外卖平台业务。
