商品详情服务用 Redis 缓存商品 JSON,读 QPS 峰值约 8 万,缓存 TTL 统一设为 30 分钟。大促时发现某个爆款商品的缓存 key 每到过期瞬间,就有 2 万左右 QPS 直接穿透到 MySQL,把数据库连接池打满。请说明你会怎么改缓存层的读逻辑,以及你选这个方案放弃了什么。
面试官在考什么
判断候选人能否区分缓存穿透/击穿/雪崩,并针对单热点 key 给出带取舍的方案,而不是只会说「加随机过期时间」。
回答框架 · 前两步
- 定性准确:这是单热点 key 的击穿(不是穿透,也不是雪崩)。雪崩是大量 key 因同一时刻失效或实例故障集体回源;穿透是查不存在的数据每次都落库;随机 TTL 只对雪崩有效,对单个热点 key 没用。
- 方案一:逻辑过期(物理上不设 TTL,value 里带 expire 字段),发现逻辑过期时直接返回旧值,同时丢一个任务异步重建;稳定性最好,代价是允许一段短暂的数据陈旧。
还有 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?如果要建复合索引,字段顺序怎么定,为什么这样定?
- 库存表 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 会不会超卖?为什么?
- MySQL InnoDB,隔离级别 REPEATABLE READ。下单扣库存的代码是这样写的:先在事务里执行 SELECT stock FROM product WHERE id=1(普通 SELECT,不带 FOR UPDATE),应用层判断返回值 > 0,然后执行 UPDATE product SET stock = 5 WHERE id=1 把算好的值写回去。压测时这个逻辑出现了超卖。请解释为什么在 RR 下还会超卖,并给出至少两种修复方案,分别说明它们的代价。