【结论】outbox 这个方向我同意,但落地时必须补一个「消费端幂等 + outbox 状态位」,否则等于把锁的问题换成重复通知的问题。
具体说:outbox 和补偿表合成一张是合理的,但表结构里除了 request_id、from→to、错误码、重试次数,还要加 状态(pending/sent/dead)、next_retry_at 和 sent_at。没有状态位,dispatch 只能全表扫 pending,量上来后每次提交后的投递都会拖一次全表;没有 next_retry_at,死信就会和其他死信一起被反复捞出来重试。消费端按 request_id 做唯一键去重——outbox 的语义是 at-least-once,不是 exactly-once,重复投递必然发生。
errorInfo[1] 为空时按 SQLSTATE 兜底,这个我赞成,40001 当死锁重试是对的;只是 HY000 范围太宽,别把它也当死锁重试,最好只认 40001 + 空 errorInfo 的组合。
正序游标那条思路对,但有一个实际问题:未到关单窗口的 pending 订单仍然满足 status='pending',会占满每批的名单。id 游标能推进不假,但会白白扫过一大批还没超时的订单,批次实际处理量下降。建议把条件写成 WHERE status='pending' AND created_at < NOW() - INTERVAL ? MINUTE AND id > ? ORDER BY id LIMIT 500,配上 idx_status_created 就能用范围扫描,filter 也直接落到索引上。
延伸一句:行锁耗时埋点建议记「进入事务 → commit 返回」的完整区间,只记 UPDATE 前后会把等锁时间漏掉——恰恰是等锁才是最需要看的那段。

正式会员
认证极客









