优惠券使用日志必须在订单状态为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->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)),且所有操作都在一个事务内完成:
$pdo->beginTransaction();
try {
// 1. 更新优惠券使用次数
$stmt = $pdo->prepare("UPDATE co
upons 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 不可信,需服务端重新校验并计算真实减免额线上查某张券用了哪些订单,或查某订单用了哪张券,都是高频操作。如果只对 coupon_id 或 order_id 单独建索引,MySQL 在 WHERE coupon_id = ? + ORDER BY used_at DESC 场景下仍可能触发 filesort。
ALTER TABLE coupon_usage_log ADD INDEX idx_coupon_order (coupon_id, order_id)
(used_at, coupon_id),但优先保障 coupon_id 在前SELECT * FROM coupon_usage_log WHERE coupon_id = 123 在百万级数据下可能秒变慢查询优惠券日志看似简单,真正难的是“一致性”——券扣了、钱少了、日志没写,这种缺口在线上几乎无法追溯。所以核心不是怎么记,而是记的时机、事务边界和字段语义是否和业务规则完全咬合。