mysql在食品配送系统中的订单和配送管理

4次阅读

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

mysql在食品配送系统中的订单和配送管理

订单表设计必须包含状态机和时间戳字段

食品配送对时效敏感,订单状态不能只靠 status 字符串硬编码。必须用整型枚举(如 0=待支付、1=已支付、2=配货中、3=骑手接单、4=配送中、5=已完成、6=已取消),并强制配套三个时间字段:created_atpaid_atdelivered_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 公式做粗筛:

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_latcenter_lng 加复合索引,加速范围初筛
  • 半径单位统一用米,避免出现 radius_kmradius_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 调用量暴涨三倍。

text=ZqhQzanResources