优惠券使用日志必须在订单状态为confirmed或paid时,与优惠券核销操作同事务写入,discount_amount须为实际减免额而非面值,并建coupon_id与order_id联合索引保障查询性能。

订单创建时同步写入优惠券使用日志
优惠券是否被使用,必须在订单状态确认为 confirmed 或 paid 的那一刻落库,不能依赖前端传参或事后补录。PHP 后端在调用 createOrder() 逻辑中,一旦确定优惠券 ID($coupon_id)已核销,就应立即插入日志记录。
- 日志表建议包含字段:
order_id、coupon_id、discount_amount(实际减扣金额,不是面值)、used_at(date('Y-m-d H:i:s'))、created_at - 避免用
INSERT IGNORE或ON DUPLICATE KEY UPDATE处理重复——优惠券只能被一个订单使用,重复写入本身就是业务异常,应抛出LogicException并回滚事务 - 不要把优惠券日志和订单主表放同一事务里“顺手插入”,而要确保它和优惠券核销操作(如更新
coupons.used_count)处于同一数据库事务中,否则会出现“券扣了但没留痕”
用 PDO 事务安全写入日志的最小可行示例
以下代码假设你已开启 PDO 的异常模式($pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)),且所有操作都在一个事务内完成:
$pdo->beginTransaction();
try {
// 1. 更新优惠券使用次数
$stmt = $pdo->prepare("UPDATE coupons SET used_count = used_count + 1 WHERE id = ? AND status = 'active' AND remaining > 0");
$stmt->execute([$coupon_id]);
if ($stmt->rowCount() === 0) {
throw new Exception('Coupon not available or already used');
}
// 2. 插入优惠券使用日志
$stmt = $pdo->prepare("INSERT INTO coupon_usage_log (order_id, coupon_id, discount_amount, used_at, created_at) VALUES (?, ?, ?, NOW(), NOW())");
$stmt->execute([$order_id, $coupon_id, $actual_discount]);
// 3. 创建订单主记录(略)
createOrderRecord($pdo, $order_data);
$pdo->commit();} catch (Exception $e) {
$pdo->rollback();
throw $e;
}
日志字段 discount\_amount 必须是实际减免值,不是 coupon\_amount
用户领到一张“满 200 减 50”的券,但订单实付 180 元,此时该券根本无法使用;若订单是 220 元,系统可能只减免了 45 元(因部分商品不参与活动),那么日志里的 discount_amount 必须记 45.00,而不是券面额 50.00。
- 这个值必须由优惠券计算引擎返回,不能从
coupons.amount字段直接读取 - 前端传来的
coupon_amount不可信,需服务端重新校验并计算真实减免额 - 后续做财务对账、券效分析时,依赖的是这个真实值,而非配置值
查日志时别漏掉 order\_id 和 coupon\_id 的联合索引
线上查某张券用了哪些订单,或查某订单用了哪张券,都是高频操作。如果只对 coupon_id 或 order_id 单独建索引,MySQL 在 WHERE coupon_id = ? + ORDER BY used_at DESC 场景下仍可能触发 filesort。
立即学习“PHP免费学习笔记(深入)”;
- 必须建联合索引:
ALTER TABLE coupon_usage_log ADD INDEX idx_coupon_order (coupon_id, order_id) - 如果常按时间范围查(如“最近 7 天券使用明细”),可考虑
(used_at, coupon_id),但优先保障coupon_id在前 - 没有索引的
SELECT * FROM coupon_usage_log WHERE coupon_id = 123在百万级数据下可能秒变慢查询
优惠券日志看似简单,真正难的是“一致性”——券扣了、钱少了、日志没写,这种缺口在线上几乎无法追溯。所以核心不是怎么记,而是记的时机、事务边界和字段语义是否和业务规则完全咬合。











