优惠券主表需设status字段(推荐tinyint)和created_at、start_at、end_at、used_at四时间戳;code建唯一索引;多渠道加channel并索引;user_coupon表不可省,须含联合唯一索引及复合索引;核销用条件更新+row_count校验;过期判断应下沉至查询而非仅依赖定时任务。

优惠券主表怎么建:别漏掉 status 和 used_at
优惠券不是发出去就完事,核心是能精准控制“能不能用、谁用了、啥时候用的”。coupon 表必须包含明确的状态字段和时间戳,而不是靠 is_used 布尔值硬凑。
常见错误是只设 is_valid TINYINT(1),结果无法区分“未发放”“已过期”“已作废”“已核销”——这些状态在运营和对账时全要分开查。
-
status用TINYINT或ENUM('draft','active','expired','disabled','used'),推荐前者,方便后期加状态不改表结构 - 必须有
created_at、start_at、end_at、used_at四个时间字段;used_at允许为NULL,但不能省略 -
code字段建议设为唯一索引(尤其当支持用户自定义码或批量导入时),避免重复发券 - 如果券支持多渠道发放(如 App、小程序、短信),加
channel字段并建索引,方便按来源统计核销率
用户领券记录表为什么不能省:user_coupon 是状态流转的枢纽
一张优惠券可以被多个用户领取,一个用户可领多张同类型券,且每张券的使用状态相互独立——这就决定了不能把 user_id 直接塞进 coupon 表里。
很多团队早期图省事,把 user_id 和 used_at 加到主表上,结果一发裂变券就卡死,查询慢、更新冲突、逻辑混乱。
-
user_coupon表至少含:id、coupon_id、user_id、status(与主表一致但可独立变化)、received_at、used_at -
(user_id, coupon_id)联合唯一索引防重复领取;user_id + status要建复合索引,支撑“查某用户所有未用券”这类高频查询 - 不要在该表存券面额、门槛等冗余字段——这些应从
coupon表关联获取,避免数据不一致 - 如果要做“限领 N 张”,靠应用层校验不可靠,应在插入前用
SELECT COUNT(*)+ 事务兜底,或用 MySQL 8.0+ 的INSERT ... ON DUPLICATE KEY UPDATE配合计数器表
核销时怎么安全更新状态:别用 UPDATE ... SET status = 'used' 直接干
并发领券、重复提交核销请求、超时重试,都会导致同一张券被多次核销。直接更新状态等于裸奔。
必须用原子性条件更新,且依赖数据库行级锁保障一致性。
- 核销 SQL 必须带完整业务约束,例如:
UPDATE user_coupon SET status = 'used', used_at = NOW() WHERE id = ? AND status = 'active' AND user_id = ?;
执行后检查ROW_COUNT()是否为 1,不是则说明已被他人抢先核销或状态异常 - 不要用
coupon_id做 WHERE 条件更新——同一个coupon_id可能对应多条user_coupon记录,容易误更新 - 若需强一致性(如满减券叠加核销),建议在事务中先
SELECT ... FOR UPDATE锁住记录,再更新,但要注意锁范围和时长,避免拖慢整体性能 - 核销成功后,立刻异步写入日志表(含操作人、设备指纹、IP 等),便于事后审计和排查羊毛党
过期自动失效怎么做:别依赖应用层定时任务扫表
靠凌晨跑脚本扫全表更新过期券,随着数据量上涨会越来越慢,还可能卡住线上业务。
更稳的方式是把“是否有效”的判断下沉到查询逻辑,并辅以轻量清理机制。
- 查询可用券时,WHERE 条件必须同时校验:
status = 'active'且start_at 且 <code>end_at >= NOW();不要只查status - 建定时事件(
EVENT)每天凌晨执行轻量更新:UPDATE coupon SET status = 'expired' WHERE status = 'active' AND end_at 分批更新,避免锁表
- 对长期不用的老数据(如 1 年前的已过期券),定期归档到历史表,保持主表体积可控
- 如果业务允许“过期即删”,可用分区表按
end_at分区,到期自动DROP PARTITION,但需评估归档合规要求
user_coupon.status 和 coupon.status 的语义差异——前者管“这张券对这个用户是否可用”,后者管“这张券模板本身是否还在生命周期内”。两个状态要独立维护,交叉判断,少一个环节就容易出核销失败或资损。











