不能只靠定时任务直接用该DELETE语句,因未加索引易全表扫描、大表删多行会锁表阻塞服务、缺乏事务保护与删除前验证,且可能误删未支付但有效的待处理订单。

为什么不能只靠定时任务删过期订单
直接用 DELETE FROM orders WHERE status = 'pending' AND created_at 定时跑,看似简单,但会漏掉关键状态流转逻辑:比如订单可能已被支付、已锁定库存、关联了预占优惠券或风控拦截中。硬删会破坏数据一致性,还可能让下游服务(如库存系统)因状态不匹配而报错。
UPDATE ... WHERE 必须加状态双重校验
自动取消不是“删”,而是状态变更,且必须确保只改该改的单——既未支付、也未被人工干预。典型安全写法是:
pay_status = 'unpaid'
- 用
SELECT ... FOR UPDATE先锁住订单行和关联的order_items行,避免并发重复处理 - 查出该订单占用的
sku_id和数量,再对inventory表做原子加回操作 - 检查
coupon_usage表是否存在该订单的已核销记录,有则调用undo_coupon_use()过程 - 最后插入一条
order_events记录,类型为'auto_cancelled',供消息队列消费
调度时机和事务边界容易被低估
MySQL 存储过程本身不支持后台定时,得靠外部调度(如 Linux crontab 调 mysql -e "CALL auto_cancel_expired_orders();"),但要注意两点:
- 过程内必须用
START TRANSACTION显式包裹,否则默认自动提交,一旦中间某步失败(如库存回滚报错),前面的UPDATE orders已生效,状态就脏了 - 每次最多处理 100–500 条,用
LIMIT+ 循环,避免长事务锁表;同时加SELECT COUNT(*)前置判断,空结果直接退出,省资源
真正难的不是写 SQL,是把“取消”这件事拆解成可验证、可回滚、可审计的原子动作——状态字段改了,库存没加回去?那整个过程就算失败。











