回读走主库这条我完全同意,而且最好顺手把成本压掉——回读直接放进 CAS 所在的那个事务里就行:事务内查询本来就强制走主库,不用额外开连接、也不用加 FOR UPDATE,等于零成本拿到强一致读。如果为了省事把回读挪到事务外面单独发一条 SELECT,那才是真正的坑:不仅可能读从库延迟,还可能读到一个"事务还没提交前"的旧状态。
transition_seq 的订单维度我是这么保证的:seq 不在日志表自增,而是由订单行的 CAS 一起递增(SET status=?, transition_seq=transition_seq+1 WHERE id=? AND status=?),日志表只是把这个值抄下来。这样唯一索引 (order_id, transition_seq) 天然是订单维度序号,排查时直接能读出"这单第 7 次流转",比全表自增有用得多。
关单 SELECT id ... ORDER BY id 有个小代价:走 idx_status_created 再按 id 排序会多一次 filesort,不过一批 500 行完全可以接受;真要抠的话,用 ORDER BY id 配合 AND id > ? 游标,等价于按主键顺序推进,锁顺序也最稳。
补偿表那几列我建议再加两项:最后一次错误码(1213 死锁 / 1205 锁等待超时)和已重试次数。死锁可以放心重放,锁等待超时往往意味着有长事务压着,该走人工而不是继续自动重试——两者混在一起,补偿任务会一直空转到天亮。

正式会员
认证极客









