订单表须用整型枚举状态机+三个时间戳字段,骑手绑定需唯一联合索引,地理围栏校验应下推至sql层haversine计算。

订单表设计必须包含状态机和时间戳字段
食品配送对时效敏感,订单状态不能只靠 status 字符串硬编码。必须用整型枚举(如 0=待支付、1=已支付、2=配货中、3=骑手接单、4=配送中、5=已完成、6=已取消),并强制配套三个时间字段:created_at、paid_at、delivered_at。否则无法准确统计“从下单到出餐耗时”或“骑手平均接单延迟”。
常见错误是把所有时间都塞进一个 updated_at,导致状态跳变无法追溯。例如用户取消订单后又重新下单,两个行为在日志里会混成一次更新。
-
order_status建议设为TINYINT UNSIGNED,避免负值误写 - 所有状态变更必须通过事务 +
UPDATE ... WHERE id = ? AND order_status = ?实现乐观锁,防止并发超卖或重复派单 - 不要在应用层拼接状态逻辑,MySQL 8.0+ 可用
CASE WHEN在查询中直接计算“当前停留时长”
配送员与订单的实时绑定需用唯一联合索引
骑手接单不是简单 UPDATE orders SET rider_id = ? WHERE id = ? 就完事。必须保证同一订单不能被两个骑手同时抢到,也不能让一个骑手同时绑定多个进行中的订单(除非支持多单同送)。
核心解法是在 rider_orders 关联表上建联合唯一索引:
CREATE UNIQUE INDEX uk_rider_active ON rider_orders (rider_id, order_id) WHERE status IN (2, 3, 4);这样 MySQL 会自动拒绝重复插入。注意:WHERE 条件索引仅 MySQL 8.0+ 支持,低版本只能用触发器或应用层加分布式锁。
- 避免用
orders.rider_id直接存骑手 ID —— 无法记录历史轨迹,也无法支持“换骑手”场景 - 每次派单/转单必须插入新记录,并标记原记录
is_current = 0,新记录is_current = 1 - 查询某骑手当前配送单,直接
SELECT * FROM rider_orders WHERE rider_id = ? AND is_current = 1 AND status IN (3,4),不用 JOIN 订单主表
地理围栏与配送范围校验必须下推到 SQL 层
不能靠应用层调用高德/百度 API 判断“用户地址是否在配送范围内”,那会成为性能瓶颈和单点故障源。应在 MySQL 中预存每个门店的圆形围栏(center_lat, center_lng, radius_m),用 Haversine 公式做粗筛:
乐彼多用户商城系统,采用ASP.NET分层技术和AJAX技术,运营于高速稳定的微软.NET+MSSQL 2005平台;完全具备搭建超大型网络购物多用户网上商城的整体技术框架和应用层次LBMall 秉承乐彼软件优秀品质,后台人性化设计,管理窗口识别客户端分辨率自动调整,独立配置的菜单操作锁,使管理操作简单便捷。待办事项1、新订单、支付、付款、短信提醒2、每5分钟自动读取3、新事项声音提醒 店铺管理1
SELECT store_id FROM stores WHERE 6371 * acos( cos(radians(?)) * cos(radians(center_lat)) * cos(radians(center_lng) - radians(?)) + sin(radians(?)) * sin(radians(center_lat)) ) <= radius_m;
这个公式虽不精确到米级,但能快速过滤掉 95% 明显超距的订单。真要精算再交由应用层调用地理服务。
- 给
center_lat和center_lng加复合索引,加速范围初筛 - 半径单位统一用米,避免出现
radius_km和radius_m混用导致 1000 倍误差 - 不要在 WHERE 中对经纬度字段用函数(如
ABS(lat - ?) ),会导致全表扫描
订单超时自动关闭必须用事件调度器而非定时脚本
用 Python 或 Shell 脚本每分钟扫一遍 orders 表标记超时,容易漏执行、重复执行,且无法保证原子性。MySQL 自带的 EVENT 调度器更可靠:
CREATE EVENT ev_expire_unpaid_orders ON SCHEDULE EVERY 30 SECOND DO UPDATE orders SET order_status = 6, updated_at = NOW() WHERE order_status = 0 AND created_at < DATE_SUB(NOW(), INTERVAL 15 MINUTE);
注意:必须先开启全局事件调度 SET GLOBAL event_scheduler = ON;,且该事件只在当前 MySQL 实例生效,集群部署时每个节点都要创建。
- 避免用
SLEEP()或长事务模拟定时,会阻塞连接池 - 超时逻辑要区分场景:未支付超时(15 分钟)、配货超时(30 分钟)、配送超时(45 分钟),每种对应独立事件
- 事件内不要做复杂 JOIN 或子查询,防止锁表;超时处理尽量只改状态,后续补偿动作交给消息队列
实际跑通这四点,订单和配送数据才真正具备可分析、可追踪、可兜底的基础。最常被忽略的是状态变更的幂等性和地理计算的分层策略——前者导致财务对账不平,后者让高峰期 API 调用量暴涨三倍。









