下单接口先按用户和商品查一遍,没有记录才插入,压测 200 并发跑出 3 条重复订单,日志里一条报错都没有。日志里能对上时间点,这种反而好排。
先查后插在并发下不是原子操作,两个事务都在对方提交前查到了空结果,随后各自插入。数据库层没有唯一约束,重复数据就这么留下来了,事后只能靠人工清理。这一点先确认清楚,后面就不用反复试。
ALTER TABLE orders ADD CONSTRAINT uk_user_sku UNIQUE (user_id, sku_id); INSERT INTO orders(user_id, sku_id) VALUES (1, 1001) ON CONFLICT (user_id, sku_id) DO NOTHING;
围绕「唯一索引幂等」排查时,命令行跑一遍比在面板里点十次有用,面板会替你做掉不少隐式操作。线上出问题先对时间点,跟发布、备份、任务执行的时刻排一排,能省掉一半功夫。
处理上按这个顺序:在业务唯一字段上建唯一索引 让数据库来兜底;捕获唯一键冲突的错误码 转成已存在的正常响应 不要抛 500;把查存在和插入合并成一条 insert on conflict 语句;建索引前先跑一遍去重 否则加约束会直接失败。
结果对不对很好判断,同样 200 并发再跑一遍 订单表里每个用户和商品组合只有一条 接口没有 500。
