结论:CAS 我赞成,但「卡在 processing」这条的兜底条件别挂在 updated_at 上,换成独立的 locked_at + lock_token,否则这条兜底会静默失效。
① updated_at 如果带 ON UPDATE CURRENT_TIMESTAMP,任何一次无关写入(补备注、查单回写、后台改状态)都会刷新它,你的 5 分钟超时判定就被无限续期,那批订单永远扫不出来。CAS 那句里显式写 locked_at = NOW(),并且这一列不要自动更新,从约束上就别给自动更新机会。
② OR 跨列条件基本走不了索引,扫全表。拆成两条独立 SQL——一条扫 pending,一条扫 status='processing' AND locked_at < NOW() - INTERVAL 5 MINUTE,各自配 (status, locked_at) 复合索引;数据量大时干脆在状态枚举里加个 processing_timeout,由补偿任务先 UPDATE 打标再处理,两条路径彻底分开。
③ affected_rows 我不建议全局开 CLIENT_FOUND_ROWS,它会改掉所有 UPDATE 的语义,别处拿 affected_rows 做乐观锁判定会跟着静默失效。更稳的是 CAS 里塞一个每次新生成的 lock_token(UUID),值必变、返回值恒为 1,还顺手拿到归属凭证:失败回滚或超时清理时用 WHERE id=? AND lock_token=? 精准释放,不会误清掉别的节点刚抢到的那一行。
④ 落地顺序:CAS 和入账放同一个事务(都是本地写库,没理由拆),只有入账里要调外部接口才拆;入账成功显式置 paid,失败要分可重试/不可重试——签名不符、金额对不上直接置 failed 不重试,超时扫描只当兜底通道,别让它成为正常路径。
结尾补一句:owner 和 lock_token 一起加上,出问题时一句 SQL 就能定位「哪台机器、哪一次抢的锁」,比翻日志快得多。

正式会员
认证极客










