Skip to Content
Shopify
  • Start
    • Start your business.
      Build your brand
    • Create your website.
      Online store editor
    • Customize your store.
      Store themes
    • Find business apps.
      Shopify app store
    • Own your site domain.
      Domains & hosting
    • Explore free business tools.
      Tools to run your business
    Sell
    • Sell your products.
      Sell online or in person
    • Check out customers.
      World-class checkout
    • Sell online.
      Grow your business online
    • Sell across channels.
      Reach millions of shoppers and boost sales
    • Sell globally.
      International sales
    • Sell wholesale & direct.
      Business-to-business (B2B)
    Market
    • Market your business.
      Reach & retain customers
    • Market across social.
      Social media integrations
    • Chat with customers.
      Shopify Inbox
    • Nurture customers.
      Shopify Messaging
    • Know your audience.
      Gain customer insights
    Manage
    • Manage your business.
      Track sales, orders & analytics
    • Measure your performance.
      Analytics and Reporting
    • Manage your stock & orders.
      Inventory & order management
    • Automate your business.
      Shopify Flow
    • Shopify Developers.
      Build with Shopify's powerful APIs
    • Plus.
      A commerce solution for growing digital brands
    • All Products.
      Explore all Shopify products & features
  • Pricing
  • Help and support
    • Help and support.
      Get 24/7 support
    • Business courses.
      Learn from proven experts
    Popular topics
    • What is Shopify?.
      How our commerce platform works
    Essential tools
    • Business name generator .
    • Logo maker.
    • Stock photography.
    • QR code generator.
    • Changelog.
      Your source for recent updates
    • Newsroom.
      All company news and press releases
Shopify
  • Engineering Blog
  • AI & Machine Learning
  • Mobile
  • Infrastructure
  • Culture
  • Latest
    • Security
    • Developer Tooling
    • Data Science Engineering
Type something you're looking for
Log in
Start for free
blog博客|Infrastructure基础设施

We replaced Redis with MySQL for inventory reservations—and it scaled我们用 MySQL 取代了 Redis 进行库存预留——而且实现了规模化

How we used SKIP LOCKED, composite primary keys, and connection visibility to hit our scale targets.我们如何利用 SKIP LOCKED、复合主键和连接可见性来达到规模化目标。

Updated on May 12, 2026
Redis to MySQL for inventory

Engineering at Shopify

We’re hiring

See open roles

During checkout, when a buyer clicks "Complete purchase," we need to guarantee the items they're buying are still available. If we get this wrong in one direction, two buyers purchase the same last unit: the merchant has to cancel an order, send an apology email, and eat the support cost. If we get it wrong in the other direction, we tell a buyer something is sold out when it isn't, and the merchant loses a sale they should have made.在结账时,当买家点击“完成购买”时,我们需要保证他们购买的商品仍然有货。如果我们在一个方向上出错,两个买家会购买同一个库存的最后一件商品:商家必须取消订单、发送道歉邮件并承担客服成本。如果我们在另一个方向上出错,我们会告诉买家商品已售罄,而实际上并非如此,商家因此失去了一笔本应完成的销售。

At Shopify's scale, either failure compounds fast. On Black Friday 2025, merchants on our platform hit a record $5.1 million in sales per minute at peak. Every one of those transactions touches inventory.在 Shopify 的规模下,任何一种失败都会迅速恶化。2025 年“黑色星期五”,我们平台上的商家在高峰期每分钟创下了 510 万美元的销售记录。每一笔交易都涉及库存。

Our oversell protection system handles this by reserving inventory during payment processing—a short hold that prevents two concurrent checkouts from claiming the same unit. For years, this ran on Redis. When we moved toward a unified database strategy, we had to answer a hard question: could MySQL handle the same scale?我们的超卖保护系统通过在支付处理期间预留库存来处理这个问题——这是一种短暂的锁定,防止两个并发结账流程抢占同一件商品。多年来,该系统一直运行在 Redis 上。当我们转向统一数据库策略时,我们必须回答一个难题:MySQL 能否处理同样的规模?

Earlier attempts had failed. A single row with a quantity column couldn't handle the contention. MySQL 8's SKIP LOCKED feature introduced a different design: one row per inventory unit instead of one row per item. Inspired by 37signals' approach to database-backed load distribution, we rebuilt reservations on MySQL and hit our high-throughput targets during peak 2025 traffic.早期的尝试均以失败告终。包含数量列的单行记录无法应对竞争。MySQL 8 的 SKIP LOCKED 功能引入了不同的设计:每个库存单位占用一行,而不是每个商品占用一行。受 37signals 数据库驱动负载分配方法的启发,我们在 MySQL 上重建了预留系统,并在 2025 年流量高峰期间达到了高吞吐量目标。

But the hardest lesson wasn't about database design. It was discovering that the real bottleneck wasn’t what we were observing and measuring. This post walks through the solution and what we found along the way.但最深刻的教训并非关于数据库设计。我们发现,真正的瓶颈并非我们所观察和衡量的内容。本文将介绍解决方案以及我们在过程中发现的问题。

The challenge挑战

What is oversell protection?什么是超卖保护?

Oversell protection has two main operations:超卖保护主要有两个操作:

  • Reserve: When payment starts, we mark items as reserved (a short hold, e.g. several minutes).预留(Reserve):当支付开始时,我们将商品标记为已预留(短暂锁定,例如几分钟)。
  • Claim: When payment succeeds, we permanently deduct quantity from the inventory ledger (source of truth).认领(Claim):当支付成功时,我们从库存账本(事实来源)中永久扣除数量。

Checkout completion depends on this being fast and correct. Slow reservations trigger throttling and a worse buyer experience. Mistakes mean overselling (angry customers) or underselling (lost revenue).结账完成依赖于此过程的快速与准确。预留缓慢会触发限流,导致买家体验变差。出错则意味着超卖(客户愤怒)或少卖(收入损失)。

Scale and correctness requirements规模与准确性要求

Scale here is not abstract: Shopify powers over 14% of U.S. ecommerce, and on Black Friday 2025 we saw an 11% increase in sales per minute at peak over the prior year. Reservations run on every checkout that touches inventory, so the system must handle that burst without dropping requests or breaking consistency.这里的规模并非抽象概念:Shopify 支撑着美国 14% 以上的电子商务,在 2025 年“黑色星期五”,我们观察到高峰期每分钟销售额比上一年增长了 11%。预留流程运行在每一个涉及库存的结账过程中,因此系统必须在不丢弃请求或破坏一致性的情况下处理这种爆发式流量。

We needed to:我们需要:

  • Support the platform's high-performance throughput targets during peak traffic在流量高峰期间支持平台的高性能吞吐量目标
  • Respect multi-location inventory (only reserve from locations that can fulfill)支持多地点库存(仅从能够履约的地点进行预留)
  • Keep ACID guarantees between reservations and the inventory ledger保持预留与库存账本之间的 ACID 特性
  • Prioritize correctness: no overselling and no lost reservations优先保证准确性:不超卖,不丢失预留

The Redis model and its limitsRedis 模型及其局限性

The previous system stored reservations in Redis. Each item had a quantity key, and reserving meant DECR, releasing meant INCR. Redis handled concurrency fine, but reservations and the inventory ledger lived in two different systems.之前的系统将预留存储在 Redis 中。每个商品都有一个数量键,预留意味着执行 DECR,释放意味着执行 INCR。Redis 处理并发表现良好,但预留和库存账本位于两个不同的系统中。

The claim step (payment processed, permanently deduct inventory) required updating MySQL and cleaning up Redis, and those two operations couldn't be wrapped in a single atomic step. Depending on the order, this could cause overselling (item sold but was never deducted from the ledger) or underselling (item deducted and still marked reserved).认领步骤(支付处理完毕,永久扣除库存)需要更新 MySQL 并清理 Redis,这两个操作无法封装在单个原子步骤中。根据操作顺序的不同,这可能导致超卖(商品已售出但未从账本中扣除)或少卖(商品已扣除但仍被标记为预留)。

On top of that, the Redis model had no multi-location awareness and added the operational cost of a separate cluster to maintain. Moving reservations into the same MySQL database as the ledger meant we could wrap everything in ACID transactions and eliminate these failure modes entirely.此外,Redis 模型没有多地点感知能力,并增加了维护独立集群的运营成本。将预留迁移到与账本相同的 MySQL 数据库中,意味着我们可以将所有操作封装在 ACID 事务中,并彻底消除这些故障模式。

The solution: SKIP LOCKED解决方案:SKIP LOCKED

Core idea: one row per unit, bounded by design核心理念:每单位一行,设计上进行边界限制

Instead of one row per item with a quantity column, we use one row per sellable unit. An item with 10 units has 10 rows. Reserving three units means selecting and moving three rows in a single transaction. By keeping reservations and the inventory ledger in the same database, we get ACID across reserve and claim—fixing classes of bugs that were possible with Redis (e.g. payment succeeds but inventory isn't claimed, or the reverse).我们不再使用每个商品带有一个数量列的单行记录,而是为每个可售单位使用一行。一个拥有 10 个单位的商品会有 10 行记录。预留三个单位意味着在单个事务中选择并移动三行。通过将预留和库存账本保持在同一个数据库中,我们实现了预留和认领之间的 ACID 特性——修复了 Redis 可能导致的一类错误(例如支付成功但库存未扣除,或反之)。

A simplified reserve flow looks like this:简化的预留流程如下: