应先用自连接或窗口函数计算每个订单各状态的停留时长,再按状态分组用AVG()求平均,单位统一为秒,异常值需过滤。

用 AVG() 和 TIMESTAMPDIFF() 计算平均停留时长
订单状态流转通常记录在状态变更表里,比如 order_status_log,每条记录含 order_id、status、created_at。要算某个状态(如 'shipped')的平均停留时长,不能直接对时间字段求平均,得先算出每个订单在该状态下持续了多久。
常见错误是把 created_at 当成“进入时间”就完事——其实它只是日志时间点,不是状态起止时间。必须关联下一条同订单、更高时间戳的记录,才能得出本次状态的结束时间。
- 用自连接或窗口函数获取每个状态的
next_created_at - 对
status = 'shipped'的行,用TIMESTAMPDIFF(SECOND, created_at, next_created_at)算秒数(MySQL) - 再套一层
AVG()得平均秒数,必要时除以3600转小时
注意:若某订单停留在终态(如 'delivered'),next_created_at 为 NULL,需用 COALESCE(next_created_at, NOW()) 补当前时间,否则整行被 AVG() 忽略。
用 GROUP BY status 分组统计各状态时长分布
不同状态的业务含义不同,停留时长的合理范围也不同:'pending_payment' 可能几小时,'packed' 可能只有几分钟。必须按状态分组,否则平均值会失真。
关键点在于确保分组前已完成状态区间计算——即每个 order_id + status 对应一个明确的时长值,而不是原始日志行。
- 先用
ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY created_at)标序号 - 自连接匹配
r1.order_id = r2.order_id AND r1.rn + 1 = r2.rn得到相邻状态对 -
WHERE r1.status IN ('pending_payment', 'shipped', 'delivered')过滤目标状态 - 最后
GROUP BY r1.status,再套AVG()、MAX()或COUNT(*)
别漏掉 HAVING COUNT(*) > 10 这类过滤——订单量少的状态(如 'cancelled')算出的平均值波动大,参考价值低。
避免 TIMESTAMPDIFF() 在跨天/跨月时出错
TIMESTAMPDIFF() 第二个参数是“结束”,第一个是“开始”,顺序反了结果就是负数——而负数参与 AVG() 会拉低均值,但不会报错,极难排查。
更隐蔽的问题是单位选择:TIMESTAMPDIFF(MINUTE, a, b) 和 TIMESTAMPDIFF(SECOND, a, b) 数值差60倍,如果后续没统一单位,图表或告警阈值全乱。
- 始终用
SECOND作基础单位,便于精度控制和转换 - 写死单位字符串,别用变量拼接,防止 SQL 注入或语法错误
- 测试时手动挑几条订单,用
SELECT created_at, next_created_at, TIMESTAMPDIFF(...)查看原始时长是否符合业务直觉
PostgreSQL 用户注意:TIMESTAMPDIFF 不存在,改用 EXTRACT(EPOCH FROM (next_time - curr_time));SQL Server 用 DATEDIFF(second, curr_time, next_time) —— 函数名和参数顺序都不同,迁移时容易栽。
处理状态跳变和缺失日志导致的时长异常
真实系统里,状态不会完美线性流转。可能出现 pending → shipped → delivered 跳过 packed,或某次更新失败导致日志丢失。这时算出的 shipped 时长可能长达几天,实际只是因为 delivered 日志没写入。
这类异常值会让 AVG() 失去代表性,但简单 WHERE duration_sec (24小时)又可能误杀真实长流程订单(如国际物流)。
- 用
PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY duration_sec)(PostgreSQL/SQL Server)取 95 分位数作上限参考 - MySQL 8.0+ 可用
APPROX_PERCENTILE(duration_sec, 0.95)(需开启相关配置) - 更稳妥的是加业务规则:同一订单相邻状态间隔超过
7 * 24 * 3600秒,标记为is_duration_suspicious = 1,单独分析
真正麻烦的是“伪停留”:比如订单反复从 shipped 切回 packed 又切回去,导致多次计算同一段物理时间。这种必须靠业务方确认状态机定义,数据库层只能识别模式,无法自动修正逻辑。

















