订单状态变更必须通过事务原子化记录日志,包含order_id、from_status、to_status、trigger、operator_id和created_at字段,且日志表需建(order_id, created_at)联合索引并按时间范围查询。

订单状态变更时必须写日志,不能只更新数据库
只改 order.status 字段却不留痕迹,等于没留审计依据。一旦出现“用户说已付款但系统显示待支付”,或财务对账不平,你拿不出变更时间、操作人、前值后值,问题就变成甩锅大会。日志不是可选项,是订单表的影子副本。
用 insert 而不是 update 记录日志,且字段要带上下文
日志表设计必须支持追溯:谁在什么时间、因什么动作(如 pay_callback、admin_force_ship)、把订单从 pending 改成 paid。常见错误是只记新状态,不记旧状态和触发源。
推荐日志表结构关键字段:
-
order_id(关联订单) -
from_status(变更前状态) -
to_status(变更后状态) -
trigger(触发来源,如wechat_pay_notify、cli_refund_job) -
operator_id(操作人 ID,后台操作填 admin_id,接口调用可填system或空) -
created_at(务必用数据库当前时间,别用 PHPtime(),避免时区/服务器时间不准)
INSERT INTO order_status_log (order_id, from_status, to_status, trigger, operator_id, created_at) VALUES (:order_id, :from_status, :to_status, :trigger, :operator_id, NOW())
PHP 中记录日志的时机必须包裹在事务内
状态变更和日志写入必须原子化。如果先改订单再写日志,中间崩了,状态已变但无据可查;如果先写日志再改订单,订单没改成功,日志就成了脏数据。唯一安全做法是两者同属一个 DB 事务。
立即学习“PHP免费学习笔记(深入)”;
示例逻辑(PDO):
$pdo->beginTransaction();
try {
$pdo->prepare("UPDATE orders SET status = ? WHERE id = ?")->execute([$newStatus, $orderId]);
$pdo->prepare("INSERT INTO order_status_log (...) VALUES (...)")->execute([...]);
$pdo->commit();
} catch (Exception $e) {
$pdo->rollback();
throw $e;
}注意:trigger 值应由调用方明确传入,不要在日志逻辑里自动猜(比如根据 URL 路径判断),容易误判;from_status 必须从数据库查出原值,不能靠 PHP 变量缓存——并发修改下变量可能已过期。
查询日志时别只用 order_id 做条件,加 created_at 范围更稳
订单可能一天内变更多次状态,只查 WHERE order_id = ? 会返回一堆记录,但运营或排查时往往只想看最近 2 小时的变更。线上查日志命令里漏加时间范围,容易拖慢数据库、卡住监控页面。
常用查法:
SELECT * FROM order_status_log WHERE order_id = ? AND created_at >= DATE_SUB(NOW(), INTERVAL 2 HOUR) ORDER BY created_at DESC LIMIT 20
生产环境日志表建议给 (order_id, created_at) 加联合索引;如果日志量极大(日增百万级),要考虑按月分表或归档到历史库——但归档前确保所有依赖日志的对账脚本已适配新路径。











