题目:为什么不建议用数据库唯一性约束做幂等控制?
难度:medium
标签:arch/distributed/idempotent, architecture/idempotent, difficulty/medium
评分:⭐⭐⭐⭐☆(4/5)
所谓幂等,就是为了保证同样的请求过来多次,得到同样的结果,而不是多次请求之后得到不同的结果。这不仅仅是数据库层面要做的,而是在业务层也要做相应的处理。第一,你要确定相同,这个请求是相同的。你需要通过特定的业务模式或者什么的去保证了解这个请求跟上一个请求或者上几个请求是一样的,来判定这个请求已经来过了,而不再进行新的处理,或者处理之后得到一样的结果。用数据库唯一约束做索引,这种情况只适用于幂等的少数一些情况,而不会涉及所有,不能统一的处理幂等的逻辑。并不能真正保证请求的幂等。想要达到幂等,需要包括识别、特殊处理,然后保证。其实核心应该在识别跟处理两个项目上,然后通过这两项最终保证每次请求的结果是同样的。比如查询,你每次查询使用相同结果,在没有数据更新的情况下都会是一样的。但是更新操作就不一定是怎么回事了,比如扣减,每次扣减库存,如果连续的进行扣减,就会导致扣减多次,最终库存扣错。这时候的幂等你就没法通过数据库的唯一性约束去保证这个幂等。你需要通过请求的业务识别,识别到这个请求是跟之前重复的,才能保证你的幂等性,也就是说这次不会出现重复的扣减。这就是我理解的为什么不要用数据唯一性做幂等。
优点:抓住幂等本质(相同请求相同结果);区分业务层识别 vs 数据库约束;举例到位(库存扣减);三段式框架(识别→处理→保证)。
不足:未说清唯一约束的技术局限(只适用于写路径、异常处理侵入、锁竞争、无业务语义);未提主流幂等方案(去重表/Redis SETNX/Idempotency-Key/乐观锁);未区分"不能用" vs "不够用"。
补充:唯一约束可用于订单号防重等插入型场景,但读操作/更新操作/分布式场景都无效;主流方案:业务去重表、Redis SETNX+TTL、Idempotency-Key 请求头、乐观锁版本号、状态机单向流转。
核心考点定位:考察对幂等本质的理解 + 唯一约束的能力边界 + 多种幂等方案的选型能力。
知识点拆解:
PUT /user/1(覆盖式)未支付→已支付 不会重演)uk_order_no、支付流水 uk_trade_no)UPDATE stock -1 每次都会执行)、分布式(多实例并发写入)、无业务语义(不记录请求上下文)unique_key + request_id + status + result,简单可靠,适合强一致SET key value NX EX 5,高性能,适合短时防重复提交UPDATE WHERE version = ?,防并发更新按「本质定义 → 唯一约束边界 → 主流方案」递进。
考察方向:
延伸递进面试题:
msgId 做唯一键存入去重表;跨服务调用要串联幂等面试官您好,这个问题我先说结论:唯一约束能做幂等,但只覆盖很小的场景,不能作为统一方案。
先讲幂等本质:同一操作执行 N 次,结果和执行 1 次等价。它不是数据库能单独保证的,需要业务层识别重复请求、走幂等处理路径。
唯一约束的边界是:
uk_order_no 防止重复下单,这可以用;UPDATE stock -1,每次都会执行,不会冲突,会重复扣;而且用唯一约束还有几个副作用:冲突走异常路径,代码里到处 try-catch;唯一索引写入有锁竞争,热点 key 会拖慢写入;没有业务语义,不记录请求的上下文、时间窗、处理结果。
所以主流方案是分层的:
idempotent_key + status + result 存下来,简单可靠,适合强一致场景;SET key value NX EX 5,防短时重复提交,比如表单提交;UPDATE WHERE version = ?,防并发更新覆盖;实际项目中通常是组合使用——入口用 Redis 挡住短时重放,业务层用去重表 + 状态机保证最终一致性。唯一约束更多是作为最后一道兜底(订单号唯一),而不是主力方案。