取消订单日志必须同步记录 order_id、cancel_reason(校验枚举 +code/text 双字段)、operator_id(区分 user_id/admin_id),且与订单状态更新置于同一 PDO 事务中,并为 order_id 及 (operator_id, created_at) 建立索引。

取消订单时必须记录日志的三个关键字段
不记录 order_id、cancel_reason 和 operator_id,后续根本没法查清谁在什么时间因何原因取消了哪笔订单。尤其 cancel_reason 不能只存 前端 传来的字符串——得先校验是否在预设枚举里(如 'user_request'、'stock_shortage'、'fraud_risk'),否则容易被恶意注入或写入脏数据。
-
order_id必须与订单主表一致,建议用数据库外键约束或事务内二次查询确认存在 -
cancel_reason建议用整型字段存reason_code,同时冗余一个reason_text字段存原始描述(便于审计) -
operator_id要区分是用户主动取消(填user_id),还是客服后台操作(填admin_id),不能统一写 0 或空
用 PDO 事务记录日志,避免订单状态和日志不同步
常见错误是先更新订单表 status = 'cancelled',再单独 insert 日志表——万一 insert 失败,订单已变状态,日志却丢了,完全不可追溯。必须把两步包进同一 PDO::beginTransaction()。
$pdo->beginTransaction(); try { $stmt = $pdo->prepare("UPDATE orders SET status = ?, updated_at = NOW() WHERE id = ? AND status ='paid'"); $stmt->execute(['cancelled', $orderId]); if ($stmt->rowCount() === 0) {throw new Exception('Order not found or not in payable state'); } $logStmt = $pdo->prepare("INSERT INTO order_logs (order_id, action, reason_code, reason_text, operator_id, created_at) VALUES (?, ?, ?, ?, ?, NOW())"); $logStmt->execute([$orderId,'cancel', $reasonCode, $reasonText, $operatorId]); $pdo->commit();
} catch (Exception $e) {$pdo->rollback(); throw $e;}
日志表结构要支持快速按时间 + 订单号 + 操作人筛选
线上出问题时,DBA 最常跑的查询是:SELECT * FROM order_logs WHERE order_id = ? ORDER BY created_at DESC LIMIT 10 或 WHERE operator_id = ? AND created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR)。没索引会直接拖垮慢查询。
- 必须为
order_id单独建索引 - 复合索引
(operator_id, created_at)能加速客服自查操作流 - 避免在
reason_text上建全文索引——99% 的检索靠reason_code就够了
不要在 cancel 接口里调用异步队列记日志
有些团队为了“解耦”把日志写入扔给 Redis 队列再由消费者处理,结果消费者挂了、重试失败、消息堆积,日志就永远消失了。订单取消是强一致性操作,日志必须同步落库。异步只适合通知类动作(如发短信、推消息),不是日志。
立即学习“PHP 免费学习笔记(深入)”;
如果真要异步,至少保证:日志先同步写入一张临时表(order_logs_buffer),再由定时任务捞取并转正——但这增加了复杂度,多数业务没必要。