谁说秒杀必须用 Redis?Shopify 用 MySQL 扛住黑五每分钟 510 万美元!
深度图解 Shopify Engineering 2026 最新重磅实践:彻底告别 Redis 与 MySQL 跨系统分布式事务之痛!借助 MySQL 8 SKIP LOCKED 机制,颠覆传统“一行存数量”锁竞争,独创“一物一行(One row per unit)+ 1000行有界缓冲池”;破除间隙锁死锁,排查真正瓶颈“连接池耗尽”,在黑五巅峰写操作 CPU 稳居 50% 以下!
⚡ 一句话看懂这篇技术复盘的精髓
过去业界总有一个思维定势:“做高并发秒杀和库存预留,必须用 Redis 的 DECR/INCR!”
但在 Shopify 撑起全美 14% 电商的体量下,跨系统双写成了致命梦魇——支付成功后扣 MySQL 总账本(Ledger)与清理 Redis 预留无法放入同一个严格 ACID 事务,极易酿成超卖(多卖给顾客引发退款纠纷)或少卖(误报售罄损失真金白银)。
Shopify 团队返璞归真,基于 MySQL 8 SKIP LOCKED 跳过被锁行的特性,把一个商品的多件库存拆为多行独立记录,用 1000 行有界池杜绝长表扫描;排查出真正的性能杀手并非数据库 CPU 而是 ProxySQL 长事务连接泄漏!最终以纯 MySQL 统一全栈架构,完美承载了黑五巅峰每分钟 $5,100,000 的狂暴交易洪峰!
库存预留(Reserve)与真实核销(Claim)在同一个数据库内完成,零跨系统数据不一致风险。
并发事务取货时自动跳过其他事务正在锁定的行,消除热点行竞争,配合 1000 行有界池保障极速扫描。
通过 SQL 注释打标追踪业务长连接,消灭非必要长事务,把珍贵的连接池留给秒级并发事务。
Redis 与 MySQL 状态分裂、一行库存排队互锁、空表间隙锁(Gap Lock)导致的死锁风暴。
⛓️ 十级演进思想链:从跨系统双写到单库并发巅峰
演进 01
超卖与少卖的噩梦:Redis 独立集群的致命裂痕
前代痛点:过去库存预留跑在 Redis 上,结账成功后永久扣减写在 MySQL 总账本。两套系统无法包裹进同一个 ACID 事务,遇到网络超时,要么付了款没扣库存(超卖),要么没付款却锁死库存(少卖)。
【双系统分裂:数据无法原子提交】
跨系统调用网络抖动导致状态严重错位
【同舟共济:统一收敛至单数据库】
单库单事务彻底消除两阶段不一致风险
解决了什么:把秒杀预留(Reserve)与真实扣账(Claim)收敛到同一个 MySQL 数据库内,完全依赖成熟的 ACID 事务保障数据绝对一致。
代价是什么:MySQL 必须独自承担以往由内存 Redis 吸收的恐怖秒杀并发。
演进 02
单行数量的死锁牢笼:传统 UPDATE 排队瓶颈
前代痛点:如果按传统设计,每个商品在表里只有一行记录(`quantity = 100`),几千个并发结账请求同时执行 `UPDATE ... WHERE id = 1`,全部堵在同一行行锁上,耗时直奔几秒甚至死锁崩盘。
【千军过独木桥:单行排队卡死】
热点单行数据引发严重的锁等待与超时
【化整为零:一物一行,并发无争】
将单行标量拆分成独立行记录消除锁竞争
解决了什么:颠覆传统“一商品一行存库存”的设计,借鉴 37signals Solid Queue 思想,转变为“一件可售库存对应一行记录(One row per unit)”。
代价是什么:数据库记录行数显著膨胀,需要精密的表容量治理机制。
演进 03
跳过拥堵行:MySQL 8 SKIP LOCKED 释放无锁并发
前代痛点:即使用了多行记录,如果两个买家都执行 `SELECT ... FOR UPDATE LIMIT 1`,第二个事务依然会停下来苦等第一个事务提交,性能依然上不去。
【苦苦等待:遇锁即阻塞】
普通锁定读取会导致并发线程剧烈阻塞
【绕道直取:被锁直接跳过】
自动跳过已被锁定的行实现零等待并发
解决了什么:引入 MySQL 8 的 `SKIP LOCKED` 特性,事务遇到已被其他并发请求持锁的行直接忽略,顺延锁定下一行空闲记录,完全没有阻塞等待。
代价是什么:该语法依赖 MySQL 8.0+ 现代化内核,低版本数据库无法支持。
演进 04
防御长表扫描膨胀:1000 行有界缓冲池设计
前代痛点:如果一个爆款商品在 10 个仓库有 50,000 件库存,全拆成行就是 50 万行!执行 `SKIP LOCKED` 扫描 50 万行直接把数据库 IO 与 CPU 拖垮。
【全量膨胀:几十万行深渊扫描】
全量拆分导致扫描表体积过大延迟飙升
【有界缓冲:上限 1000 行轻装上阵】
严格设定千行有界池保障扫描始终微秒级
解决了什么:设立“有界缓冲池(Bounded Pool)”,每个商品/仓库只在预留表维护最多 1000 行记录,消耗后由补充进程向总账异步批量 refill,扫描极快。
代价是什么:需要设计当 1000 行瞬间被秒光时的同步排队补仓兜底机制。
演进 05
主键与锁的微观战争:复合主键砍掉一半行锁
前代痛点:原型阶段使用自增 ID 主键,用 `SHOW ENGINE INNODB STATUS` 抓包惊呆了:每预留 1 行居然触发了 2 个行锁(锁定二级索引 + 聚簇索引主键)!
【双重加锁:自增主键累赘】
一次锁定两个索引结构导致锁数量翻倍
【精准命中:复合主键单锁合一】
复合主键让过滤条件与聚簇索引完美合体
解决了什么:将过滤条件(shop_id, inventory_item_id 等)直接做进复合主键,让 InnoDB 在定位数据时仅需在聚簇索引上加 1 个行锁。
代价是什么:复合主键长度变长,二级索引(如有)的存储占用会有轻微增加。
演进 06
破除幽灵间隙锁:切换 READ COMMITTED 终结死锁
前代痛点:在 MySQL 默认的 REPEATABLE READ(可重复读)下,当池子被秒空执行锁定查询时,InnoDB 会加上恶名昭彰的间隙锁(Gap Lock/Supremum),导致后面的补货 INSERT 无法写入并频繁死锁!
【间隙封死:空表死锁风暴】
默认隔离级别的间隙锁阻止了补货写入
【读已提交:无缝安全补仓】
降为读已提交消除间隙锁杜绝补货死锁
解决了什么:破天荒在该事务中将隔离级别改为 READ COMMITTED,消除了空表上的 Gap 间隙锁,让后台补货能立即写入。
代价是什么:应用框架必须支持为单特定事务动态定制隔离级别。
演进 07
锁顺序标准化:杜绝跨表循环死锁
前代痛点:预留(Reserve)先往已预留表 INSERT 再从可用池 DELETE;而核销(Claim)又在动已预留表,两个并发事务如果反向加锁,瞬间构成环路死锁。
【方向冲突:跨表交叉等待】
不同业务路径加锁顺序不一致产生环路死锁
【严格单向:标准加锁铁律】
严格规范所有路径加锁次序消灭环路死锁
解决了什么:规范所有业务路径必须遵循一致的加锁顺序:统一先 DELETE 可用池表,再 INSERT 预留记录表;核销只动预留表,物理切断死锁闭环。
代价是什么:团队所有编写库存相关 SQL 的工程师必须严格执行该工程纪律。
演进 08
购物车批量打包:UNION ALL 击碎网络往返延迟
前代痛点:结账购物车里经常有 5 件不同商品,如果发起 5 次独立的数据库网络往返(RTT),数据库连接被长久占据,累积延迟难以承受。
【来回折腾:多次单件往返】
单购物车多次往返极度消耗连接时间
【一网打尽:UNION ALL 一次搞定】
合并为单次SQL批量获取多商品预留
解决了什么:采用 `UNION ALL` 拼接多个商品的 SKIP LOCKED 子查询,一次网络往返锁定整辆购物车的全部库存。
代价是什么:拼接 SQL 相对较长,需由底层框架层封装生成。
演进 09
抓出真正元凶:ProxySQL 注释打标追踪连接泄漏
前代痛点:所有优化做完后压测依然受挫:CPU 还没跑满,线程却开始排队,ProxySQL 连接池全面告急!到底是谁占着茅坑不拉屎?
【两眼一抹黑:连接池耗尽不知情】
只知道连接用尽却无法归因到具体业务
【精准破案:/* conn_tag:xxx */】
SQL打标溯源让非必要长连接无处遁形
解决了什么:在 SQL 中注入 `/* conn_tag:业务名 */`,在中间件 ProxySQL 解析聚合;震惊地发现库存预留根本不慢,是结账路径其他非核心模块长久霸占连接!清理后消除了主库 50% 读与 33% 事务!
代价是什么:需要在应用层统一集成 SQL 注释注入与中间件日志管道。
演进 10
黑五终极大考:影子双写验证与纯 MySQL 胜利
前代痛点:直接在黑五秒杀前把几十万商家的核心库存从 Redis 切到新架构,一旦翻车就是数亿美元的灾难。
【豪赌式切换:风险不可控】
传统一刀切迁移面临极高业务翻车风险
【暗流验证:影子双写 + 灰度切流】
影子双写万无一失纯MySQL抗住黑五峰值
解决了什么:通过“影子模式(Shadow Mode)”双写验证真实业务结果一致性,平滑灰度切流,黑五巅峰每分钟 $510 万美元销售额下,写入 CPU 稳居 50% 以下!
代价是什么:双写过渡期需要承担少量的双倍系统资源开销。
⚖️ Shopify 库存预留架构的四大核心黄金天平
颠覆思维定势的背后,是极其清醒的工程权衡哲学:
一致性底线 (ACID Atomicity)
宁要单库不要分布式:坚决杜绝跨系统无法单步提交的 Redis+MySQL 分裂架构,宁可优化关系库,也要把预留与总账焊进同一个事务。
有界缓冲防御 (Bounded Contention)
限制有界大小:一物一行虽好,但表不能无限膨胀。1000 行有界池完美平衡了瞬间秒杀吸收力与微秒级扫描性能。
锁颗粒度极致克制 (Lock Minimization)
消灭冗余锁:复合主键砍掉二级索引行锁,READ COMMITTED 消除间隙锁,严格单向加锁次序杜绝死锁。
连接管道生命线 (Connection Stewardship)
做优秀的数据库邻居:真正的瓶颈往往是连接池而非 CPU,杜绝长事务占用,把毫秒级连接让渡给核心并发结账链路。
📋 旧方案 (Redis + MySQL) vs 新方案 (纯 MySQL SKIP LOCKED) 终极全景对照
| 对比维度 |
旧架构 (Redis 预留 + MySQL 异步总账) |
新架构 (纯 MySQL 8 SKIP LOCKED 有界池) |
| 🛡️ 事务一致性保证 |
跨系统分布式双写,无法实现单步原子事务,易超卖/少卖。 |
单库强 ACID 保证,预留与核销同一事务,超卖/少卖概率彻底归零。 |
| 📍 多仓位感知能力 (Multi-Location) |
Redis 键值模型难以直接表达复杂的跨多仓库库存履约规则。 |
原生关系型关联,复合主键直接携带 location_id,轻松多仓分流。 |
| ⚡ 锁竞争与等待模型 |
Redis 依靠单线程内存原子操作,但向 MySQL 同步时面临排队。 |
SKIP LOCKED 无等待穿透,多事务并发取不同行,互不干扰。 |
| 🏗️ 运维基础设施成本 |
必须维护昂贵的高可用 Redis 集群、哨兵与数据同步中继。 |
统一收敛至已有 MySQL 集群,无额外跨系统架构运维负担。 |
| 🔥 真实极端峰值表现 (黑五) |
容易在高峰期由于双写状态漂移引发客服大量人工退单纠纷。 |
黑五每分钟 $510 万美元巅峰下,主库写 CPU 稳居 50% 以下! |