库存表 stock(sku varchar 主键, qty int),隔离级别为 RR。扣减写法 A:UPDATE stock SET qty = qty - 1 WHERE sku = ? AND qty >= 1,然后判断 affected rows 是否为 1。写法 B:先 SELECT qty FROM stock WHERE sku = ?,在应用层判断 qty >= 1,再执行 UPDATE stock SET qty = qty - 1 WHERE sku = ?,两个操作都在同一个事务里。现在库存只有 1,两个并发请求各扣 1,分别用 A 和 B 会不会超卖?为什么?
面试官在考什么
判断候选人是否分得清快照读与当前读,是否知道『先检查再更新』的问题在于检查与动作之间没有原子性,而不是『有没有加事务』。
回答框架 · 前两步
- 写法 A 不会超卖:UPDATE ... WHERE qty >= 1 是当前读,会对命中行加排他锁,后到的事务必须等前一个提交后才能读并重新判断条件,扣减和判断在同一条语句里完成。
- 写法 B 可能超卖:RR 下普通 SELECT 是快照读、不加锁,两个事务可能都读到 qty=1、都判断通过,随后两个 UPDATE 串行执行,把库存扣成 -1。
还有 3 条答案要点、 3 层追问、 5 个常见坑, 以及针对你简历的追问路径,在定制报告里。
同一能力域的其他题
- 订单表 orders 约 2000 万行,字段有 id(主键)、user_id、status(只有 3 个取值:待支付/已支付/已取消)、created_at、amount,现已有单列索引 idx_user_id(user_id)。线上核心接口执行:SELECT * FROM orders WHERE user_id = ? AND status = ? ORDER BY created_at DESC LIMIT 20; EXPLAIN 显示 key=idx_user_id、rows≈3 万、Extra 里有 Using filesort。你会怎么调整索引?为什么建 (user_id, status, created_at) 而不是 (user_id, created_at, status)?
- 一张 2000 万行的订单表 orders(id PK, user_id, status, created_at, amount),status 只有 5 个取值,其中 'pending' 约占 3%。已有索引 PRIMARY(id)、(user_id, status)、(created_at)。现在有这样一条查询:SELECT * FROM orders WHERE status='pending' ORDER BY created_at DESC LIMIT 20。它在生产上要跑 3 秒左右,DBA 说它在全表扫描。请你判断为什么会走全表扫描,并给出你的索引方案,说明为什么是它。
- 订单表 3 亿行,MySQL 8.0 + InnoDB。核心查询是 `SELECT ... FROM orders WHERE merchant_id=? AND status IN (1,2) ORDER BY created_at DESC LIMIT 20`,QPS 约 800,P99 从 30ms 涨到 1.2s。现有联合索引是 idx_mch(merchant_id, status, created_at),主键 order_id。你接手这个慢查询,要求说明:你第一步看什么来确认瓶颈、为什么现有索引没能避免排序、你改什么(索引还是查询)、怎么评估改动对线上写入和主从延迟的影响,以及如果业务不愿意改查询语义你怎么办。
- 表 orders(id BIGINT PRIMARY KEY, user_id BIGINT, status VARCHAR(16), created_at DATETIME, amount INT),已有索引 idx_user(user_id),表里 2000 万行,绝大多数订单 status='paid'。线上查询:SELECT * FROM orders WHERE user_id=? AND status='paid' ORDER BY created_at DESC LIMIT 20; EXPLAIN 结果是 type=ref、key=idx_user、rows≈18000、Extra='Using where; Using filesort'。你会怎么优化这条 SQL?如果要建复合索引,字段顺序怎么定,为什么这样定?
- MySQL InnoDB,隔离级别 REPEATABLE READ。下单扣库存的代码是这样写的:先在事务里执行 SELECT stock FROM product WHERE id=1(普通 SELECT,不带 FOR UPDATE),应用层判断返回值 > 0,然后执行 UPDATE product SET stock = 5 WHERE id=1 把算好的值写回去。压测时这个逻辑出现了超卖。请解释为什么在 RR 下还会超卖,并给出至少两种修复方案,分别说明它们的代价。
- 一个电商下单链路:订单服务写订单库后通过 RPC 调库存服务扣减,两个库在不同实例上。线上每天出现几十笔“订单创建成功但库存未扣减”,也偶发反向的“库存扣了但订单回滚”。你被要求主导修复。请说明:你怎么先止血和定位根因、你选哪种一致性方案及理由、怎么保证幂等和补偿、怎么设计对账兜底,以及怎么说服业务方接受“准实时一致”而不是强一致。