用户只点了一次提交,后台日志里却出现两条一样的下单请求,先查哪一端?
用户只点了一次提交,后台却收到两条一样的请求,多数不是网络慢,而是从点击到入库这条链路上缺了一层兜底。前端按钮置灰只能挡住同一台手机上的连续手点;弱网重试、支付回调重发、用户返回再点一次,都可能绕过它。按 2026 年项目交付经验,可靠的下单、支付、领券接口通常做三层:客户端防抖、服务端幂等键、数据层唯一约束。缺哪一层,就会在对应场景里漏单或重单。
只点一次为什么会有两条请求:断点一般在这四段
一次提交从手指落下到数据落库,会经过四段:客户端点击、网络传输、服务端处理、数据库写入。只要四段里没有任何一处承认「这两次请求其实是同一件事」,第二次就会被当成新业务处理。很多团队给按钮加了 loading 就以为解决,结果弱网下手机自动重试,服务端仍然收到两条几乎一样的请求。
排查时不用猜。拿一笔重复订单去对日志,看两次请求的时间差、幂等键或订单号是否相同,基本能定位断点:时间差在毫秒级多是并发穿透,时间差在几秒以上多是重试或用户二次点击。2026 年常见情况是客户端有防抖、服务端没幂等,弱网一重试就漏;或者服务端有幂等、数据层没有唯一键,并发下仍然穿透。
- 客户端段:点击事件没加锁,返回上一页再进来还能提交,改动小但覆盖窄。
- 网络段:超时重试、用户手动重发、底层自动重放,这一段多数团队控制不了,只能靠服务端兜住。
- 服务端段:没有幂等判断,两个线程同时进来都能通过校验,属于常见并发缺口。
- 数据层:没有唯一约束或状态机,末端那道闸门就是敞开的。
三层防重各管什么:客户端、服务端、数据层
按 2026 年交付习惯,下单、支付、领券、提交工单这类写操作一般按三层来分,而不是指望某一层包打天下。三层失效的场景不一样:客户端挡连续手抖,服务端挡超时重试与并发,数据层挡前面都漏掉之后的末端兜底。每层只解决自己那一段,把边界写清楚,客户端和服务端两拨人才不会互相推责。
- 客户端防抖:按钮点击后置灰、显示 loading,成功或失败后再恢复。失败恢复要有超时上限,常见区间是 3~10 秒,按接口实际耗时调整;否则网络断了按钮一直灰着,用户会以为 App 卡死。
- 服务端幂等键:客户端进入提交页时生成一个 requestId,随请求带上;服务端用「用户 + 业务类型 + 幂等键」去重,命中就返回上一次的处理结果,而不是直接报错。有效期按业务定,经验区间是 10~30 分钟;太长占存储,太短挡不住用户返回后再次提交。
- 数据层唯一约束:在流水号、幂等键或「用户 + 业务 + 时间窗」上建唯一索引,或用状态机限制订单只能从「待支付」流转到「已支付」一次。这一层是末端拦截,重复请求会写失败,所以接口仍要捕获这个错误并返回友好提示,而不是把数据库报错抛给用户。
三层都做,合格线可以这样定:同一个幂等键连续发 10 次,业务表里只应有一条记录;不同幂等键但业务上确实重复的,靠状态机或日终对账兜。开工前把幂等键的字段名、生成时机、有效期三件事写进接口文档,能减少联调时两端理解不一致。
可核对对比:只加前端置灰到三层齐全差在哪
不同项目的预算和工期不一样,不必一步到位,但要知道自己砍掉的到底是哪一段风险。
- 只做前端置灰:改动量小,常见区间是半天到一天;能挡连续点击,挡不住弱网重试和回调重发,风险落到服务端与后续对账。
- 前端 + 服务端幂等键:需要两端约定字段名与生成时机,经验区间是 1~3 天;能覆盖大多数重试与并发场景,是 2026 年比较常见的落地档位。
- 再加数据层唯一约束或状态机:多一轮表结构或流程调整,可能涉及历史脏数据清洗;覆盖更宽,但先评估已有重复数据会不会导致建索引失败。
- 只做数据层唯一约束:开发省事,但用户会看到冷冰冰的报错,也不区分「重复提交」和「真实业务冲突」,客服解释成本会上升。
取舍建议:涉及资金或库存的链路按三层做;点赞、埋点上报这类可以容忍重复的,做一层前端防抖加一次服务端快速去重通常就够,把省下的工期留给核心链路。
适用与不适用边界
适合上三层防重的是「重复一次就产生实际代价」的写操作:下单、支付、退款、领券、抢购、提交工单、余额与积分变动。这些场景里重复提交会直接带来资损或人工对账成本,补防重的投入通常在半天到几天之间,属于性价比高的一类改动。
不必照搬的场景也很明确:纯查询接口、埋点上报、内容浏览这类天然幂等的操作,加幂等键只会增加存储与复杂度;内部后台的批量导入更该用「任务号 + 状态」整体幂等,而不是逐条加锁。反过来,如果业务本身允许同一用户对同一商品下多笔订单,比如不同规格、不同收货人,那幂等键的维度就要拆细到规格或收货信息,简单按用户去重会把正常订单也挡掉。
- 适合:资金、库存、权益变动,以及会触发线下履约动作的提交。
- 不适合:只读查询、日志与埋点上报、可容忍重复的统计类写入。
- 谨慎:业务上允许重复但又有频次要求的常规下单,需要把幂等键维度和限频分开设计。
交付现场:约定写晚一步的代价
有一类现场约束很常见:周期只剩两三周,客户端和服务端还是两拨团队,接口文档只写了字段,没说幂等键什么时候生成、失效后怎么办,联调时各自跑通就收了。做法上,至少应在联调前把幂等键的生成时机、去重维度、重复命中后的返回文案定下来,并补一轮弱网与并发验证。代价是,如果漏掉这一步,上线第一周遇到弱网重试,可能多出十几笔重复订单,只能人工退款加对账,返工量往往比一开始写清楚大得多。按经验区间看,前置约定通常只占半天到一天,事后对账与补丁往往要按周算。
常见问题
只在按钮上做置灰,是不是就够了?
不够。置灰只能挡同一台手机上的连续点击,弱网重试、返回再进、支付回调重发都能绕过。涉及资金或库存的接口,建议至少再补一层服务端幂等。
幂等键一般用什么值生成比较好?
常见做法是进入提交页时由客户端生成不重复随机串,随请求带上并在服务端落库校验。不要用「用户 ID + 秒级时间戳」,同一秒连点会穿透。
支付回调被重发好几次,算不算重复提交?
算,而且比用户手快更常见。第三方回调本身会重试,服务端要按订单号或流水号做幂等,重复回调只更新状态,不重复发货、不重复加积分。
已经产生重复订单了,一般怎么收尾?
常见做法是先按幂等键或时间窗捞出疑似重复单,核对支付流水后走退款或合并,同时把服务端幂等和唯一约束补上,避免下一批再冒出来。
如果只做一件事:先给下单、支付、领券这类写接口补上服务端幂等键,再补数据层唯一约束,前端置灰放在后面做体验优化即可。适用边界也要说清楚:纯查询、埋点上报、可容忍重复的统计类写入,加防重收益有限。上线前用并发脚本和弱网场景各跑一轮,是 2026 年把风险压到可接受区间比较实际的做法。
-
App 下单页按钮连点两下就多出一条订单,为什么测试时没复现、上线后用户却碰上了?
日期:2026年9月24日 阅读:136
-
用户下单成功页都弹了,埋点报表却比后台订单少一截,真是客户端漏埋了,还是上报被系统掐了?
日期:2026年10月14日 阅读:41
-
用户刷商品图滑到几十屏就发烫掉帧,真是手机太旧,还是图片加载链路没对齐?
日期:2026年10月13日 阅读:77
-
用户在国外下单,订单列表上的日期比实际早一天,真是手机时区设置错了吗?
日期:2026年10月12日 阅读:102
-
用户从相册选的照片预览是正的,一上传到 App 就躺倒,是不是压缩那一步把方向弄丢了?
日期:2026年10月10日 阅读:115




