应采用查表驱动的动态权重计算,通过weight_rule表配置规则,结合指数衰减与四舍五入控制精度;订单分配需先筛选可用骑手再按权重排序,避免超载。

存储过程里怎么写动态权重计算逻辑
直接在 INSERT 或 UPDATE 语句里硬编码权重值,很快就会失控。真正可行的做法是把权重拆成可配置的因子,在存储过程中用变量逐层叠加计算。比如:客户等级权重 × 区域饱和度系数 × 骑手历史履约率 × 当前时间衰减因子。
关键点在于别用 IF...ELSE IF... 堆叠判断——分支一多就难维护。改用查表驱动:建一张 weight_rule 表,字段含 rule_type、condition_sql(存WHERE片段)、weight_value,然后在存储过程中拼接执行 EXECUTE IMMEDIATE(Oracle)或 PREPARE + EXECUTE(MySQL 8.0+)。这样规则调整不用改存储过程代码。
- 避免在循环中反复查同一张权重配置表,先用
SELECT ... INTO一次性载入变量 - 时间衰减因子建议用
POWER(0.98, FLOOR(HOUR(TIMEDIFF(NOW(), order_time))/2))这类指数衰减,比线性更符合业务实际 - 所有浮点权重运算后加
ROUND(..., 4),防止小数精度累积导致排序错乱
订单分配时怎么保证骑手不超载又兼顾权重优先级
单纯按权重 ORDER BY weight DESC LIMIT 1 会忽略骑手当前负载。必须把「可用性」和「优选性」拆成两步:先筛出负载未超阈值的骑手(例如 current_orders_count ),再在该子集中按权重排序。
难点在于「负载」数据往往来自另一张实时表(如 rider_active_orders),而存储过程默认事务隔离级别下可能读到过期快照。MySQL 要加 SELECT ... FOR UPDATE 锁住候选骑手行;PostgreSQL 则推荐用 SELECT ... FOR NO KEY UPDATE 减少锁冲突。
- 别在
WHERE子句里写(SELECT COUNT(*) FROM rider_active_orders ...)—— 每行都触发子查询,性能断崖式下跌 - 把骑手负载缓存进内存表(如 MySQL 的
MEMORY引擎临时表),每秒异步刷新一次,存储过程只查这张轻量表 - 权重相同时,强制追加
ORDER BY rider_id,避免因索引顺序不同导致分配结果不可重现
如何让存储过程支持灰度发布和快速回滚
上线新权重策略最怕全量跑飞。必须让存储过程能识别运行模式:weight_version 参数控制走哪套规则,而不是直接删旧过程、建新过程。
具体做法是在过程开头加判断:IF weight_version = 'v2' THEN ... ELSE ... END IF。所有权重计算分支都包裹在这之下。这样运维只需改调用方传参,就能切流;出问题立刻把参数切回 v1,无需数据库 DDL 操作。
- 每个权重版本对应的 SQL 片段必须独立封装为函数(如
calc_weight_v2(rider_id, order_id)),便于单元测试 - 在过程内记录关键决策日志到
assign_log表,字段至少含order_id、rider_id、applied_weight_version、final_weight,否则出问题根本没法对账 - 禁止在过程中做跨库写操作(如更新其他微服务的数据库),这类动作应交给下游消息队列异步处理
MySQL 存储过程中调用 UUID() 或 NOW() 为什么结果不一致
这是被问得最多也最容易踩的坑:UUID() 和 NOW() 在存储过程里不是“执行时求值”,而是“编译时固化”。比如你在循环里多次调用 SET v_uuid = UUID(),实际得到的是同一个值。
根本原因是 MySQL 对这些函数做了内部优化,认为它们无副作用、可缓存。解决方法只有两个:一是显式加 SELECT UUID() INTO v_uuid(触发真实执行),二是换用 SYS_GUID()(Oracle)或 gen_random_uuid()(PostgreSQL)这类明确设计为每次调用都生成新值的函数。
-
NOW()同样适用此规则——需要毫秒级变化时,必须用SELECT NOW(3) INTO v_time - 如果过程里要生成多个唯一ID,别用
CONCAT('ORD_', UUID()),改用REPLACE(UUID(), '-', '')避免横杠引发后续解析问题 - 所有时间相关计算统一用 UTC 时间(
UTC_TIMESTAMP()),别依赖服务器本地时区,否则跨机房部署时序逻辑直接崩

















