场景:营销账户余额被高频扣减,行锁等待导致 P99 飙升。请给出至少三套方案(串行化/拆分行/异步聚合),比较一致性、吞吐与对账成本,并说明如何防止少卖。
题目:热点账户余额扣减单行QPS上万,如何在保证不超卖的前提下提升并发?
难度:high
标签:mysql/lock,db/high-concurrency
评分:⭐⭐⭐☆☆(3/5)
单行 QPS 上万代表单个账户更新频繁,可以借鉴组提交等批处理方案:前置缓存更新,顺序创建订单流水,异步发消息到 MQ,再批量更新数据库。方案 1 用 Redis 存临时库存,更新后记录流水,直接更新 MySQL 会因行锁排队导致吞吐下降,所以转批量更新,比如聚合一秒数据更新一次;前面有缓存保证不超卖,中间有流水保证持久化用于对账,最后用 MQ 和批量操作缓解数据库压力。方案 2 是单行 QPS 上万不会来自一个大区,可以按大区划分库存分别扣减,缓解单行压力,但会出现余缺不均,仍用数据库事务保证一致性,对账内容比方案 1 少。
优点:两套方案方向都成立,且主动补了缓存防超卖、流水持久化、对账兜底三段闭环。
不足:题目要三套,缺「同账户单写者 / 余额分桶」;关键正确性没交代——Lua 原子扣减 + requestId 幂等、先落消息表再扣缓存、MQ 幂等消费与死信;批量 SQL 必须带 balance>=? 条件。
补充:对账要有差异处理(长款/短款挂账、冲正、超时未支付库存回收),并给出三方案的一致性/吞吐/对账成本对比。
热点账户扣减是「正确性 + 并发度」的双目标问题。三套主流解法:① Redis 预扣 + 流水 + MQ 聚合批量落库(最终一致,吞吐最高);② 库存/余额分桶或按大区分片(一致性靠 DB 事务,需处理余缺调拨);③ 同账户单写者串行化或余额分桶求和(无跨行锁竞争,吞吐取决于单写者)。无论哪套,防超卖都依赖原子扣减条件与幂等键。
【一句话结论】 热点账户不能靠单行硬扛,要用「原子预扣 + 流水 + 批量落库」或「拆分 + 排队」把串行变成可控。
【三点展开】 第一,Redis 侧用 Lua 原子扣减加请求号幂等,先落消息表再扣缓存,保证不超卖也不丢凭证。第二,MQ 聚合同账户一秒内的扣减,批量执行带 balance>=? 的条件更新,落库仍然幂等。第三,如果要求强一致,就按账户或大区拆分库存,用数据库事务扣减,再处理余缺调拨。
【一句边界】 异步方案只保证最终一致,必须配流水对账和差异冲正;资金类业务不能接受无对账的最终一致。