+− THE DAILY DIFFdev & AI news
SHIP IT

Shopify 将库存预留移至 MySQL

Shopify 将其库存预留系统从 Redis 迁移到已经包含库存分类账的 MySQL 数据库。

Shopify 将其库存预留系统从 Redis 迁移到已经包含库存分类账的 MySQL 数据库。一个有界池中的独立可锁定单位行允许并发结账使用 SKIP LOCKED 来选择不同的合格单位。该设计还依赖于事务边界、主键布局、补货规则和观察整个结账过程中的连接保持时间。

阅读书面版(英文) ↗

本视频涵盖的内容

  • 旧的 Redis 数量计数器模型处理了并发,但预留清理和 MySQL 分类账更新无法共享一个单一的本地原子事务。
  • 替代方案对每个可用单位使用一行,每个商品/位置组合的池上限为 1,000。空池可以触发内联补货,并发请求在补货锁后面等待。
  • 复合主键是 (shop_id, inventory_item_id, inventory_group_id, id)。Shopify 在其原型中减少了锁开销,并使用 READ COMMITTED 来避免阻塞补货的间隙锁。
  • 预留先删除池行,然后插入预留记录。提交会释放数据库锁,同时存储的保留会在支付过程中持续;成功支付会在后续的原子事务中认领分类账并移除预留。
  • MySQL 将 SKIP LOCKED 描述为一个不一致的视图,它会省略锁定的行。它既不能建立完整的库存计数,也不能公平轮流,仅适用于行级锁,并且对基于语句的复制不安全。
  • 源示例包含 expires_at,但 Shopify 没有记录 MySQL 的过期清理算法。视频中的释放/过期分支是一个解释性的生命周期要求。
  • 其他结账代码中的连接保持时间是最终的吞吐量瓶颈。Shopify 对两个系统进行影子写入,比较结果,并逐步切换,同时设置了 Redis 的紧急停止开关作为回退。

翻译的文字记录

译自英文原版旁白。可用音频和字幕由 YouTube 控制。

为什么将预留移至 MySQL?

0:00 结账需要 Redis 来保持快速。 Shopify 将库存预留移至 MySQL,在有界池中为每个单元使用一行。 为什么选择 MySQL? 如何安全地跳过锁? 这是 The Daily Diff,幕后揭秘。 为什么快速查询仍然触及天花板? Shopify 是一个用于在线和实体销售的商务平台。 Redis 是一个内存数据存储。

0:21 MySQL 是一个关系型数据库,Shopify 已经将库存分类账保存在 那里。预留在买家付款时短暂保留库存。

为什么两个存储系统有风险?

0:29 他们的 Redis 模型会递减一个商品计数器。 认领已付款订单意味着更新 MySQL 分类账并清理 Redis。 这些独立写入可能导致库存重复销售或在应该可售时却不可用。 旧模型也缺乏位置感知。 替代方案必须从某个可以履行订单的地方选择库存。 一个在错误大陆的仓库是一个出色的数据库条目,但一个糟糕的 配送承诺。 早期的 MySQL 尝试使用数量行,因此竞争的结账会在同一个锁处排队。

什么成为可锁定单元?

0:56 想象一下电子表格单元格周围的一条天鹅绒绳索。 增加更多工人只会延长队列。 Shopify 改变了可锁定对象。 每个可用单元都有自己的一行。 一个锁定读取会跳过其他事务 持有的单元,并选择其他合格的 单元。不同的工人可以获取不同的行, 同时热点计数器不会妨碍他们。

当池耗尽时会发生什么?

1:15 该池对每个商品和位置限制为一千行。 补货从分类账中抽取。 如果它耗尽,预留 路径会内联补货,竞争请求 在补货锁后面等待。 空池不代表空仓库。 预留会删除选定的池行,然后在事务中插入预留记录。 提交会释放数据库锁。

1:35 回滚会撤销更改。 预留作为存储状态在支付处理过程中存活。 成功支付会原子性地认领分类账并移除预留。 数据库锁永远不需要看管支付表单。 他们的复合主键以商店、商品、 组,然后是单元标识开始。 匹配查找减少了其原型中的索引锁定。 他们还使用已提交读(READ COMMITTED)来避免阻塞池

1:57 补货的间隙锁,并使用一致的表顺序来防止循环等待。 发布的示例记录了过期时间。 废弃的支付最终需要释放库存,否则购物车就会变成 一个房东。Shopify 的帖子没有明确说明该清理算法, 所以这张图显示了生命周期要求。 问题来了。

SKIP LOCKED 遗漏了什么?

2:14 SKIP LOCKED 排除锁定的行,所以手册称其结果为不一致的 视图。它既不能提供完整的库存计数,也不能公平轮流。 保持可用性决策和补货规则围绕它。

真正的瓶颈在哪里?

2:26 那个瓶颈呢? 其他结账代码持有数据库连接时间过长。 Shopify 标记了调用者并测量了连接保持时间, 然后清理了结账路径并重新审视了线程并发。 快速查询仍然可以在查询之外排队。

我为什么要推出这个设计?

2:38 他们对两个系统进行了影子写入,以 Redis 为权威, 比较结果,然后通过紧急停止开关逐步切换。 我的判断是 SHIP IT。 我将推出共享事务边界和可回滚的推出方案, 同时对整个结账路径进行监测。 对此有什么问题吗? 请在评论中提出。 这就是今天的 The Daily Diff。

2:54 我是来自 Axrisi 的 Niko。 负责任地合并。

来源

  1. We replaced Redis with MySQL for inventory reservations—and it scaledShopify Engineering — Emilie Noel
  2. Simplified reservation SQL embedded in Shopify's articleShopify Engineering / CourtneySymons on GitHub Gist
  3. MySQL 8.0 — Locking ReadsOracle / MySQL Reference Manual
  4. MySQL 8.0 — Transaction Isolation LevelsOracle / MySQL Reference Manual
  5. What Is Shopify and How Does It Work?Shopify
  6. Redis quick startsRedis documentation
  7. What is MySQL?Oracle / MySQL Reference Manual

相关视频

under-the-hood · zh-CN · 2026年9月24日

SAML 内部揭秘:信件里的签名

SAML 让你登录几乎所有工作应用,其签名存在于它签名的 XML 内部。深入探讨:应用、浏览器和身份提供商之间的登录流程,断言的样子,为什么规范化必须在每个字节上达成一致,以及为什么相同的错误类别从 2012 年到 2025 年不断重现。受 Trail of Bits 的“SAML:糟糕设计的碎片” (在 Hacker News 上获得 312 分) 启发。

3:02 ↗
under-the-hood · zh-CN · 2026年9月23日

模型“死亡”的幕后

人工智能模型不会死亡——它会有一个关闭日期,第二天早上,你的 API 调用将返回 404:“该模型已被弃用,点击此处了解更多信息。”幕后:四状态管道(活跃 → 遗留 → 弃用 → 退役)、通知窗口(OpenAI ≥ 6 个月 / 3 个月 / 预览版约 2 周;Anthropic ≥ 60 天)、开发者会遇到的问题(响亮的 404、静默的评估漂移、Claude 4.7+ 新的温度参数 400 错误

3:15 ↗