数据库存:仓储系统团队老板版清单:高并发秒杀需要检查哪些环节
高并发秒杀最容易被误判成“数据库扛不住”问题。实际项目里,我见过库存表只有几十万行,却在几秒内被锁等待拖垮;也见过数据库 CPU 还不到 50%,订单接口却因为连接池耗尽整体超时。秒杀系统真正要检查的,不是某一个数据库参数,而是从流量进入、资格校验、库存扣减、订单落库、仓内履约到数据对账的完整链路。
本文以仓储系统团队老板的视角,给出一份可以拿去开会、压测和验收的检查清单。这里讨论的“库存”不是展示页面上的一个数字,而是可售库存、锁定库存、已分配库存、已出库库存、退货库存之间的业务约束。只要其中一个环节口径不一致,秒杀结束后就可能出现超卖、少卖、重复扣减、订单悬挂和仓库无法执行等问题。
我建议团队不要一上来就讨论“要不要换数据库”或“要不要加机器”,而是先把验收指标写成一张结果表。老板真正关心的通常不是某一条 SQL 执行了多少毫秒,而是活动期间有多少用户成功、多少订单有效、库存是否准确、仓库能否按承诺发货。
| 验收维度 | 建议关注指标 | 老板要追问的问题 | 常见失真方式 |
|---|---|---|---|
| 流量承接 | 峰值请求数、有效请求占比、限流拒绝率 | 系统是否把无效流量挡在昂贵链路之前? | 只报平均 QPS,不报峰值和突发斜率 |
| 交易成功 | 下单成功率、支付转化率、订单超时率 | 用户看到成功后,是否真的形成有效订单? | 把“返回成功”当成“订单完成” |
| 库存准确 | 可售库存差异、重复扣减数、超卖数、少卖数 | 数据库、订单、仓库三套数字是否一致? | 只检查页面库存,不检查账实差异 |
| 履约稳定 | 出库延迟、波次积压、异常订单比例 | 秒杀订单是否会把仓内作业冲垮? | 只压在线接口,不压仓内任务链路 |
| 恢复能力 | 回滚时长、消息积压恢复时间、数据对账耗时 | 故障后能否找到并修正问题? | 只演示正常路径,不演练中断和重试 |
我会把“秒杀成功”定义为四个条件同时满足:用户请求在承诺时间内得到明确结果;库存扣减不超卖;订单状态可追踪;仓库后续能够执行。只满足第一个条件,最多算页面响应快,不能算仓储系统成功。

秒杀期间,所有用户争抢的不是普通查询能力,而是极少量的库存资源。假设某商品可售库存只有 5000 件,却同时有 20 万个请求到达,系统要解决的不是“如何让 20 万个请求同时修改库存”,而是“如何让只有 5000 个合法请求进入最终扣减,其余请求快速、可解释地结束”。
这就是我判断架构是否健康的第一条经验:系统应当尽可能把竞争从数据库行锁,前移到资格校验、令牌、队列或分片库存层。如果所有请求最终都在数据库里执行一次库存更新,那么无论数据库多强,热点行都会成为天然的排队点。
数据库最适合做强一致的最终事实记录,例如库存账本、订单状态、扣减流水和唯一约束。它不适合承担大量重复点击、活动资格判断、验证码校验、黑名单判断和页面库存轮询。
这不是说缓存、队列可以取代数据库,而是要明确职责边界。缓存或令牌层可以快速拒绝明显无效的请求,队列可以把瞬时峰值摊平,数据库则负责最终扣减和可追溯记录。任何“只在缓存里减库存、数据库异步慢慢补”的方案,都必须额外回答丢消息、重复消费、缓存回滚和故障切换后的账实一致问题。
在仓储系统中,“库存”至少可能包含物理库存、可用库存、锁定库存、已分配库存和在途库存。电商活动页面通常只关心可售库存,但仓库系统还要关心库位、批次、效期、质检状态和拣货任务。
如果营销团队把“仓库里有 1000 件”直接当成“秒杀可卖 1000 件”,风险就已经发生了。因为其中可能有 100 件冻结、50 件待质检、80 件已被普通订单锁定,剩余库存还要留出损耗和售后替换量。
| 库存口径 | 计算示例 | 适合谁使用 | 必须避免的误解 |
|---|---|---|---|
| 物理库存 | 仓库实物盘点数量 | 仓储、盘点、财务 | 不等于可直接销售数量 |
| 可用库存 | 物理库存-冻结库存-已分配库存 | 库存服务、订单服务 | 需要明确是否扣除安全库存 |
| 活动库存 | 可用库存×活动放量比例 | 营销、活动运营 | 不能绕过仓储约束单独扩大 |
| 锁定库存 | 已创建订单但未完成支付的数量 | 订单、支付、库存 | 必须有释放和超时机制 |
| 可履约库存 | 已支付且可分配到仓库的数量 | 仓内作业、客服 | 不等于页面即时显示库存 |
我建议老板要求团队在活动前提交“库存口径说明”,写清楚每个字段的来源、更新时机、是否允许回滚、由哪个系统作为最终事实源。只要这张表说不清,压测通过也不能代表真实业务安全。

以仓储团队常见的限时低价活动为例:活动前 30 分钟开始预热,用户持续刷新商品页;活动开始后 1 秒内请求集中到达;商品详情服务读取活动库存;用户提交请求后进行登录、限购、风控和地址校验;通过后进入库存扣减;扣减成功再创建订单;支付成功后生成仓内分配任务。
这条链路中至少有四种不同的压力。页面查询制造读压力,库存竞争制造写压力,订单创建制造事务压力,仓内任务生成制造消息和批处理压力。把它们全部归因于数据库,通常会导致错误优化:读库加缓存了,热点写锁和消息积压却仍然存在。
我在活动复盘时会特别看结束后的 5 个时间窗口:结束后 1 分钟、10 分钟、30 分钟、2 小时和次日对账。很多系统能扛住开始瞬间,却在支付回调、库存释放、失败重试、订单补偿和仓内批量分配时出现第二波拥堵。
尤其是支付超时释放。如果 4 万个订单在 15 分钟内集中超时,库存服务可能同时执行大量更新;如果补偿任务没有随机退避,原本已经恢复的数据库会再次被打满。因此,活动结束后的恢复策略必须和开始前的限流策略同等重要。
数据库 CPU 低,不代表系统健康。连接池等待、锁等待、磁盘写入延迟、复制延迟、网络队列和应用线程池都可能先于 CPU 成为瓶颈。实际排查时,我会把接口耗时拆成连接获取、SQL 执行、锁等待、序列化、消息发送和下游调用,而不是只看一个平均响应时间。
例如,库存更新 SQL 执行时间可能只有 4 毫秒,但连接池平均等待 80 毫秒,锁等待峰值达到 600 毫秒。此时继续加数据库 CPU 或增加只读副本都没有意义,因为热点更新仍然集中在同一行。
下面这种写法看起来合理,但并发下很危险:
SELECT available_stock FROM inventory WHERE sku_id = 1001; -- 应用层判断 available_stock > 0 UPDATE inventory SET available_stock = available_stock - 1 WHERE sku_id = 1001;
多个请求可能同时读到相同库存,然后分别执行扣减。即使后续通过事务包住查询和更新,也可能因为锁范围、隔离级别和异常重试处理不当,造成重复扣减或长时间等待。
更可靠的基础写法是把条件放进同一条原子更新,并检查受影响行数:
UPDATE inventory SET available_stock = available_stock - 1, version = version + 1, updated_at = CURRENT_TIMESTAMP WHERE sku_id = 1001 AND available_stock > 0 AND version = 27;
但这条 SQL 也不是万能方案。它只能保证一次扣减的原子性,不能自动解决重复请求、订单创建失败、支付超时释放、消息重复消费和跨仓分配。原子扣减是底线,不是完整方案。
缓存预扣库存可以显著降低数据库压力,但它会引入新的事实源问题。缓存节点重启、网络抖动、集群切换或消费者异常时,缓存中的数量与数据库可能不一致。若系统直接依据缓存返回“抢购成功”,而后续落库失败,用户就会拿到一个没有订单事实支撑的成功结果。
我更倾向于把缓存库存理解为“流量闸门”或“临时令牌池”,而不是完整库存账本。每个令牌都应能追溯到活动、SKU、批次和请求标识,最终要落到数据库库存流水或订单事实中。
固定 1 万并发、每秒均匀发送请求,和活动开始后 1 秒内涌入 20 万请求,完全是两种问题。前者测试吞吐能力,后者测试突发吸收、排队、连接建立、线程调度和限流策略。
压测脚本还应模拟重复点击、同一用户多设备、不同 SKU 热点程度、支付失败、消息重复、数据库主节点切换和仓库接口延迟。如果脚本只发送一次“成功请求”,得到的结果往往过于乐观。

系统繁忙是技术团队最省事的错误提示,却是业务最难处理的状态。库存不足、资格不符、重复下单、数据库超时、消息未确认和订单待补偿,用户看到的结果可能完全不同,但系统如果都返回同一个错误,客服、运营和财务就无法判断后续动作。
我建议至少区分“明确失败”“处理中”“需要查询”三类结果。明确失败可以立即结束;处理中必须提供订单查询或状态轮询;需要查询则要通过请求号、幂等键和订单流水确定最终事实。这样既减少用户重复点击,也降低补偿难度。
库存总量很大,并不代表没有热点。如果 10 万个 SKU 分散承接流量,数据库可能相对平稳;如果 90% 的请求都集中到一个爆款 SKU,单行锁就可能成为最先失效的资源。
我会要求团队提供“SKU 请求集中度”数据:排名第一的 SKU 占总请求比例、排名前十的 SKU 占比、单个 SKU 的库存扣减速率,以及库存行平均锁等待和 P99 锁等待。集中度越高,越应该考虑令牌分片、库存分桶或按仓拆分。
| 热点等级 | 前1个SKU请求占比 | 建议方案 | 主要代价 |
|---|---|---|---|
| 低 | 低于10% | 原子扣减、索引优化、连接池和事务治理 | 架构改造少,但仍需压测 |
| 中 | 10%至40% | 活动库存预分配、队列削峰、热点读缓存 | 需要处理排队和库存释放 |
| 高 | 高于40% | 库存令牌分片、分桶扣减、按仓或分片隔离 | 对账、补偿和分配复杂度上升 |
不是所有字段都值得用同样的强一致成本。库存扣减、订单唯一性和支付结果通常需要明确事实;商品详情、已售数量、排行榜和页面展示库存则可以接受短暂延迟。
我会让团队画出“强一致边界图”,至少标出四个节点:库存令牌发放、库存账本扣减、订单创建、仓库任务生成。每个节点都要说明失败时是回滚、重试、补偿还是进入人工处理。没有边界的最终一致,最后通常会变成无人负责的延迟一致。
| 业务对象 | 建议一致性级别 | 可接受延迟 | 异常处理 |
|---|---|---|---|
| 库存扣减流水 | 强约束、可追溯 | 原则上不允许丢失 | 幂等重试、人工对账 |
| 订单主状态 | 强约束 | 秒级 | 状态机校正、补偿任务 |
| 页面展示销量 | 最终一致 | 数秒至数分钟 | 异步刷新、定时校准 |
| 仓内波次统计 | 最终一致 | 分钟级 | 批处理重算 |
| 营销排行榜 | 最终一致 | 分钟级或活动结束后 | 离线汇总和复核 |
事务可以保护一致性,但事务持有时间越长,热点竞争越严重。把库存扣减、订单创建、优惠计算、地址校验、积分写入和仓库任务生成全部包进一个大事务,看起来“一次完成”,实际上会让一个慢下游拖住库存锁。
我的判断原则是:库存扣减事务只保护库存事实和扣减流水;订单创建通过幂等键和状态机衔接;仓内任务通过可靠消息或可重放事件生成。这样做会增加补偿逻辑,但不会让仓库接口的延迟直接持有数据库库存锁。
任何方案都可能失败,老板不应该要求团队证明“绝不会出错”,而应要求团队证明“出了错能发现、能定位、能止损、能恢复”。

库存表最怕两件事:热点行过度集中,以及更新条件无法使用有效索引。团队应检查 SKU、仓库、批次、库存状态和活动标识的组合方式,避免把所有仓库同一个商品的库存压在一行,也避免为了“查询方便”建立过多更新负担很重的索引。
我通常会要求提交以下检查结果,而不是只说“已经加索引”:执行计划、索引命中情况、更新影响行数、锁等待来源、慢 SQL 分布和高峰期表空间增长。索引不是越多越好,每增加一个二级索引,都可能增加写入和页分裂成本。
高并发扣减时,事务隔离级别不是越高越安全。更高隔离级别可能增加锁范围、回滚成本和等待时间;过低的隔离级别则可能带来读取异常。团队应结合数据库类型、写入模式和业务约束进行验证,不能照抄其他项目参数。
重点不是背诵某个隔离级别,而是明确每一类操作的锁行为:库存扣减锁住什么记录,订单查询是否会参与锁竞争,失败重试是否会重复持有锁,超时释放是否与新订单扣减并发发生。压测时应记录锁等待图,而非只看吞吐量。
有一个容易被忽略的细节:应用捕获数据库超时后,如果没有确认事务是否已提交,就直接重试,可能形成“第一次其实成功、第二次再次执行”的重复扣减。所有重试都必须带幂等键,并区分“明确失败”和“提交结果未知”。
数据库连接池并不是越大越好。连接数过大,会把应用层排队转移成数据库内部排队,导致上下文切换、锁竞争和内存压力增加。连接池大小应根据数据库可承受并发、事务平均时长、应用实例数量和下游耗时计算,再通过压测校准。
我会同时观察应用线程池、数据库连接池、消息消费者线程池和仓库接口连接池。如果订单服务线程池有 500 个线程,而数据库连接池只有 50 个,可能产生大量连接等待;如果反过来,数据库连接数过多,也可能把热点写入直接推向数据库。
| 资源池 | 需要观察 | 危险信号 | 建议动作 |
|---|---|---|---|
| Web线程池 | 活跃线程、排队长度、拒绝数 | 线程大量等待下游 | 缩短同步链路、设置超时 |
| 数据库连接池 | 活跃连接、等待时间、超时数 | 连接长期占满 | 检查慢事务和池大小 |
| 消息消费者池 | 消费速率、积压量、重试次数 | 积压只增不减 | 拆分队列、调整批量和并发 |
| 仓库接口连接池 | 调用延迟、超时、失败率 | 仓库接口拖慢订单事务 | 异步化、隔离舱、降级 |
数据库主从或集群部署并不等于业务可以无感切换。高并发活动中,最危险的窗口往往不是完全宕机,而是主节点不可写、复制延迟升高、连接还指向旧地址、应用重试风暴同时发生。
老板应要求团队演练至少四种故障:主库切换、只读副本延迟、网络短暂中断和连接池失效。演练要记录从故障发生到业务恢复的时间,还要确认切换期间是否出现重复订单、库存流水缺口和状态长时间不变。

秒杀活动必须具备按请求链路追踪的能力。最少要把活动编号、SKU、仓库、用户、请求号、幂等键、订单号、库存流水号和消息编号关联起来。没有这些关联字段,活动后只能看见“有异常”,无法回答“哪一批库存出了异常”。
监控指标要分层。入口层看请求速率和拒绝率;应用层看线程池、连接池和P99;数据库层看锁等待、事务时长和复制延迟;消息层看积压和重复消费;仓储层看分配、拣货、出库和异常单。每一层都有指标,但最终必须能关联到同一条业务链路。
在仓储团队规模较大、数据来自订单库、库存库、支付系统和仓内作业系统时,活动复盘最常见的问题不是没有数据,而是数据散落在多个系统里。团队往往拿着几张导出的表格,人工计算峰值、失败率和库存差异,最后只能得到一个模糊结论:这次活动大概没问题。
如果使用九数云这类数据分析工具,可以将订单、库存流水、支付结果、消息消费记录和仓内任务数据按活动编号、SKU、仓库和时间窗口进行关联,制作高峰请求趋势、库存变动瀑布、异常订单分布和仓库处理时延看板。它的价值不是代替数据库写入,而是把跨系统的事实拼接成同一张可审计的复盘视图。
官网信息可参考:九数云数据分析工具。在实际选型时,我建议重点验证连接数据源、权限控制、刷新频率、异常下钻和导出留痕,而不是只看图表模板数量。
我不会把看板做成几十个指标堆在一页上。老板版看板首先应回答“卖了多少、是否超卖、哪里拥堵、哪些订单还没落地、库存差异是否能解释”。运营版看板关注转化和活动规则,技术版看板关注延迟和错误,仓库版看板关注波次积压和出库承诺。
假设活动成功订单率从平时的 12% 降到 4%,不能直接得出“系统性能下降”。如果前置资格校验把大量不符合地区、会员等级或限购规则的请求拦截了,订单率下降可能是规则生效;如果有效请求的P99从 160 毫秒升到 2.4 秒,且连接池等待同步升高,才更像技术瓶颈。
同样,库存差异也要拆成多个来源。库存少了 100 件,可能是 70 件支付超时尚未释放,20 件仓库盘亏,10 件重复消费;如果只看最终库存数字,所有问题都会被混成一个“库存不准”。

我会给数据分析工具设计三个验证任务,而不是只看演示效果。第一,输入一个订单号,能否下钻到库存流水和仓内任务;第二,输入一个库存差异,能否按时间顺序还原扣减、释放和补偿;第三,把活动按仓库拆分后,能否定位是某个仓库延迟还是全链路拥堵。
如果工具只能展示汇总图,不能追溯到明细;只能导入静态表,不能按活动刷新;只能看结果,不能保留口径和权限,那么它更像报表展示工具,不足以承担高并发活动的经营复盘。此时团队仍然需要脚本、SQL或数据仓库补齐追溯能力。
如果峰值请求量不高,SKU较分散,活动库存也比较充足,我建议优先做好数据库原子扣减、订单幂等、索引、超时和监控。此时直接上复杂的缓存预扣、分布式事务和多级分片,可能让系统变得难以维护。
这类活动的重点不是追求极限吞吐,而是让每一个异常都能解释。对于仓储团队而言,简单、稳定、容易对账,往往比架构复杂但无人维护更有价值。
如果大部分请求都集中到少数几个 SKU,首先要做的是降低热点行竞争。可以把活动库存预先分配到多个库存桶,每个桶有独立扣减记录;请求通过哈希或令牌路由到不同桶,最终再汇总库存。
库存分桶不是简单把一个数字拆成十个字段。每个桶都要有分配规则、耗尽策略、回收策略和对账逻辑。某个桶先耗尽时,系统要决定是否切换到其他桶;活动结束后,要能汇总每个桶的发放、扣减、释放和异常数量。
| 方案 | 对热点的改善 | 实现复杂度 | 适用条件 |
|---|---|---|---|
| 单行原子扣减 | 有限 | 低 | 热点较低、活动规模可控 |
| 活动库存预分配 | 中等 | 中 | 库存可提前锁定、允许排队 |
| 库存分桶 | 较高 | 高 | 爆款集中、团队具备对账能力 |
| 按仓库拆分库存 | 较高 | 高 | 多仓发货、区域规则明确 |
| 令牌加异步订单 | 高 | 中高 | 可接受处理中状态和延迟确认 |
这类活动应把流量控制前移。活动开始前发放资格令牌或预约名额,活动开始时只允许持有有效令牌的请求进入订单链路。没有令牌的请求应在边缘或接入层快速结束,而不是让它们进入数据库。
队列削峰可以把瞬间流量变成稳定消费,但会改变用户体验。用户提交后可能看到“排队中”,而不是立即知道成功或失败。此时必须设计查询接口、过期时间、排队序号和最终状态,否则用户会不停刷新,形成新的流量峰值。
我建议设置明确的最大等待时间。超过时间仍未获得库存的请求,不要无限留在队列里;已经失效的令牌也要及时清理。队列不是垃圾桶,所有进入队列的请求都要有生命周期。
如果商品涉及批次、效期、冷链或区域仓,不能只按 SKU 做库存扣减。库存分配还要考虑先进先出、效期阈值、运输范围和仓库作业能力。否则订单虽然扣减成功,仓库却找不到满足规则的可发库存。
这类系统应在库存服务中区分“总可用库存”和“可分配库存”。秒杀扣减时可以先锁定 SKU 数量,但分配阶段还要校验批次和仓库。如果分配失败,必须有换仓、换批次或取消补偿规则,不能让订单停在“已付款但待人工处理”。
如果团队没有成熟的消息治理、数据对账和故障演练能力,我不建议直接采用多级缓存、分片库存和跨库事务。更合适的做法是控制活动规模、分批放量、降低单次库存承诺,并使用简单可追溯的数据库方案。
老板可以把预算优先投入三件事:压测环境、监控告警和对账工具。很多团队愿意花钱买更复杂的中间件,却不愿意投入一次完整故障演练,最后复杂度变成了新的故障来源。

秒杀方案通常要在三件事之间做取舍:立即响应、库存强一致和极限吞吐。要求用户立即得到确定结果,同时承诺绝不超卖,还要承受几十万突发请求,往往意味着更高的基础设施和开发成本。
| 优先级 | 典型选择 | 用户体验 | 风险与代价 |
|---|---|---|---|
| 库存准确优先 | 数据库强约束、同步确认、严格限流 | 成功结果更确定,但可能需要等待 | 峰值吞吐受限,排队明显 |
| 吞吐优先 | 令牌、缓存、异步落库、最终一致 | 可以快速进入处理中 | 补偿、对账和状态查询复杂 |
| 转化优先 | 预占库存、延长支付时间、失败后补单 | 用户更容易获得购买机会 | 库存锁定时间变长,释放压力增加 |
| 履约优先 | 按仓分配、保留安全库存、限制区域 | 下单范围和数量可能受限 | 营销规模受约束,但仓库更可控 |
我建议活动前至少提前 7 天完成方案评审,提前 3 天完成全链路压测,提前 1 天冻结配置并做最终核对。以下清单不追求形式完整,而是确保每一项都有负责人、验证证据和失败动作。
活动结束后不要只看销售额和支付金额。系统是否安全,要通过多个事实源互相校验。至少应完成数据库库存、库存流水、有效订单、支付订单、仓内任务和实盘库存之间的交叉核对。
平均响应时间很容易掩盖问题。秒杀期间 99%的请求可能在 100 毫秒内完成,但剩下 1%的请求可能等待 10 秒;如果这些请求恰好是支付回调、库存释放或仓内任务生成,就会影响整个业务结果。
我会要求压测报告至少包含P50、P95、P99、最大值、错误类型分布、锁等待分布和重试次数。同时随机抽取成功订单、失败订单、处理中订单和补偿订单进行链路核验。通过指标验收,只能证明系统看起来稳定;通过样本核验,才能证明业务事实没有断裂。

很多团队把秒杀成功定义为接口没有大面积报错,但仓储系统的成功标准更严格:流量被正确筛选,库存被准确扣减,订单状态可追踪,仓库能够执行,异常可以补偿,活动结束后还能完成对账。
因此,老板在评审时不要只问“数据库能承受多少 QPS”,还要问:热点在哪里?哪些请求应该在数据库之前被拒绝?哪些库存是真实可售?扣减失败后如何处理?订单成功后仓库是否有能力发货?活动结束后谁来对账?
如果团队现在还没有完整资料,我建议先用半天时间建立四张表:库存口径表、链路容量表、失败补偿表和活动对账表。每张表都写明数据来源、负责人、验证方式和异常处理人。
然后用一场最接近真实活动的压测验证三个问题:第一,数据库前面能否拦截无效请求;第二,热点 SKU 是否会造成锁等待和连接池耗尽;第三,活动结束后能否在规定时间内完成库存与订单对账。
如果这三个问题没有答案,先不要急着引入更复杂的技术。高并发仓储系统的真正护城河,不是把所有请求都处理成功,而是让有限库存、有限数据库资源和有限仓内产能,在最激烈的竞争中仍然保持可解释、可追踪、可恢复。
我准备给仓储系统做一次大促秒杀压测,但团队一直在争论到底该先扩容数据库,还是先改业务代码。我担心只看数据库 CPU 和连接数,会漏掉库存扣减、索引、锁等待这些更隐蔽的问题,想要一份老板能直接拿去开会的检查顺序。
我建议不要把“数据库扩容”当作第一步。秒杀故障通常不是单点容量不足,而是请求同时穿透缓存、事务、库存表和订单表后,形成一条锁等待链。先扩容,往往只是把故障从 30 秒推迟到 2 分钟。实际检查应按“请求是否进入数据库、进入多少请求、每次请求锁多久、失败后是否重试”四层推进。
下面这份顺序适合仓储系统团队在压测前逐项确认: 检查层级重点指标危险信号优先动作 入口层限流命中率、缓存命中率、请求排队长度大量请求直接落到数据库增加令牌桶、排队和热点缓存 事务层事务耗时、锁等待、死锁次数库存更新事务超过 100 毫秒缩短事务范围,拆分非必要逻辑 索引层慢查询、扫描行数、回表次数扫描行数远高于返回行数按查询条件重建联合索引 连接层活跃连接、连接池等待、超时率连接池耗尽但 CPU 不高修正连接池和超时配置 仓储秒杀最容易被忽视的是库存表的更新方式。
类似“先查询库存,再判断库存大于零,最后执行扣减”的三步逻辑,在高并发下会放大竞争窗口。更稳妥的做法是让数据库用一条带条件的原子更新完成扣减,例如将可用库存减一,同时要求可用库存大于零,再根据受影响行数判断成功还是售罄。
我会把数据库检查结果分成三档:单次请求平均耗时低于 30 毫秒且 P99 低于 100 毫秒,可进入更高并发测试;平均耗时尚可但 P99 超过 500 毫秒,说明存在锁等待或慢查询;错误率超过 0.5%,则不应继续单纯加压,而应先定位超时、死锁和连接池耗尽。
老板需要重点追问的不是“数据库能扛多少 QPS”,而是“在库存扣减成功、订单创建成功、重复请求被拦截这三个条件同时成立时,系统能稳定处理多少 QPS”。这个数字才是真正可用于大促容量规划的有效吞吐量。
我们现在的做法是请求进来后直接更新库存表,平时没有问题,但秒杀时锁等待明显增加。有人建议把库存全部放进缓存,有人建议使用消息队列异步下单,我担心改完之后出现超卖、少卖或者用户付款成功但订单没有生成的问题,应该怎样取舍?
这不是三选一的问题,而是要先区分“库存准确性”和“请求削峰”两个目标。数据库事务擅长保证最终账实一致,缓存擅长快速拦截无效请求,消息队列擅长把瞬时流量摊平。让其中任何一个组件单独承担全部责任,都容易留下缺口。我在设计秒杀链路时通常采用分层方案:入口先用缓存或令牌桶拦截明显超出库存上限的请求;
进入订单流程后,用数据库条件更新或库存流水表保证最终扣减;订单创建可以通过队列削峰,但必须配套幂等和补偿。
方案优点主要风险适合场景 直接数据库扣减一致性清晰,改造成本低热点行锁竞争严重并发量中等、库存分散 缓存预扣响应快,能挡住大量无效请求缓存与数据库不一致热点商品、允许异步确认 消息队列下单削峰明显,保护订单库重复消费、消息丢失、状态延迟订单允许排队处理 最危险的实现是“缓存扣减成功后直接返回购买成功”。
这只能说明用户拿到了排队资格,不能说明订单已经成立。更准确的状态应至少区分排队中、下单成功、库存不足、支付超时和系统补偿中,前端也不能把排队资格展示成最终购买成功。数据库侧建议保留库存流水,记录业务单号、商品编号、扣减数量、操作类型和处理状态。
即使缓存预扣失败或消息重复消费,也能通过业务单号唯一约束和流水状态进行校正。对关键商品,我宁愿让少量请求进入人工或自动补偿,也不会用没有审计记录的“直接改库存”方案。选择标准可以很实际:如果商品库存只有几百件、并发峰值不高,数据库条件更新通常足够;如果瞬时请求远超库存几十倍,应增加缓存拦截;
如果订单写入会拖慢库存响应,则把订单创建异步化,但库存确认和消息可靠性必须单独验收。
我们的仓储系统平时查询很快,但一到大促,库存查询、订单查询和拣货任务查询会互相影响。团队提出了分库分表、增加索引、把连接池调大三种方案,我想知道这些方案分别解决什么问题,怎样避免为了压测数据好看而把生产系统改得更不稳定。
这三个方案解决的是三类不同问题:索引主要减少单条查询扫描量,分库分表主要降低单库数据规模和写入集中度,连接池主要控制应用访问数据库的并发通道。把连接池调大并不会自动提升数据库吞吐,反而可能让数据库同时执行更多互相争抢的 SQL。
索引检查应从真实 SQL 出发,而不是看到某个字段常用于查询就盲目加索引。秒杀场景通常要重点看商品编号、仓库编号、库存状态、活动批次和业务单号的组合条件,并通过执行计划确认是否走对索引。若查询条件是仓库编号加商品编号,单独给商品编号建索引,未必比联合索引更有效。
问题表现常见误判应观察的证据处理方向 查询慢直接加大数据库规格扫描行数、排序、回表次数优化 SQL 和联合索引 写入互相等待继续增加连接数锁等待、事务持有时间缩短事务,拆分热点写入 单库接近容量上限临时清理历史数据表大小、增长速度、热点分布规划分区或分库分表 连接池排队无限调大连接池池内等待、数据库活跃会话设置合理上限和超时 分库分表不应作为秒杀前最后一周的应急动作。
它会引入跨分片查询、全局唯一编号、事务边界和数据迁移问题。若当前瓶颈只是库存热点行锁,分表后仍然可能因为同一个热门商品集中在一个分片中而无法解决,甚至增加排查难度。连接池建议通过压测寻找拐点,而不是照搬网上的固定数值。
可以固定数据库规格,分别测试 50、100、200 个应用连接,记录吞吐量、P99、锁等待和错误率。假设连接数从 100 增加到 200 后,吞吐只提升 3%,但 P99 从 180 毫秒升到 900 毫秒,那么 100 左右很可能已经接近合理上限。
仓储系统还要把秒杀查询与日常拣货、入库、盘点查询隔离。至少应在读请求、报表查询和核心库存写入之间设置不同资源池或只读副本,避免一个临时统计 SQL 把核心交易拖慢。真正有效的优化,不是让每个接口都更快,而是确保最关键的库存确认链路在资源紧张时仍有优先级。
我们以前压测只看平均响应时间,报告里数字很好看,但正式活动时仍然出现超时和重复下单。我想重新设计一次压测,除了并发用户数和 QPS,还应该关注哪些指标,什么结果才算达到可以上线的标准?
平均响应时间很容易掩盖秒杀故障,因为 99% 的请求可能很快,剩下 1% 的请求却在锁等待或连接池排队几十秒。压测报告至少要同时展示 P50、P95、P99、错误率、库存准确性、重复订单数和数据库锁等待,不能只放一张吞吐量曲线。压测流量也不能只模拟“所有人抢同一个商品”。
仓储系统更接近混合负载:热门商品秒杀写入、普通商品查询、订单状态轮询、支付回调、仓库拣货任务和运营后台查询会同时发生。只测单一接口,容易得出过于乐观的结论。
压测阶段模拟重点必须记录停止条件 基线测试正常业务流量各接口基准延迟出现慢查询或资源异常 阶梯加压逐步增加并发和热点比例P99、锁等待、连接池排队错误率持续超过 0.5% 突发测试数秒内流量瞬间放大队列长度、限流命中率、恢复时间核心链路无法自动恢复 故障测试缓存、消息、数据库节点异常丢单、重复单、补偿耗时库存和订单无法对账 我会特别安排“库存为 1、请求为 1000”的极端用例。
测试结束后,数据库库存、成功订单数、库存流水和支付记录必须能够对上。如果出现成功订单数大于库存、库存变成负数,或者订单状态长期停留在处理中,这次压测即使吞吐量很高,也不能判定通过。上线标准应包含恢复指标。例如数据库短暂拒绝连接后,系统是否能在 5 分钟内恢复;消息积压达到多少条会触发告警;
订单补偿任务每分钟能处理多少条;库存对账多久执行一次。没有恢复时间目标的压测,只是在测试理想状态下的性能。最后要做一次“压测数据清理和回滚演练”。秒杀测试经常留下缓存键、订单流水、消息和库存变更,若清理不彻底,下一轮测试结果会被污染,甚至影响生产。
我的判断标准是:核心链路在目标并发下 P99 可控、错误可解释、库存账实一致、故障后能自动恢复,这四项缺一不可。


读者评论
把秒杀成功定义为“可履约订单”而不只是页面返回成功,这个判断很实用。仓储场景确实不能只盯着接口响应,还要看库存流水、订单状态和仓内任务是否能接上。
文中提到数据库 CPU 不高但连接池和锁等待严重,很符合实际排查经验。压测时如果只看平均 QPS 和 CPU,容易漏掉热点行、线程池耗尽等真正瓶颈,建议把等待时间拆开统计。
库存口径拆分得比较清楚,尤其是安全库存、冻结库存和已分配库存不能直接相加。支付超时释放也要做幂等和重试控制,否则活动结束后的补偿任务可能造成重复加库存。