RSS
菜单
全部文章快讯开发科技深度热点

第 32 章 美团系MySQL真题精讲

内容摘要

美团系(本地生活/外卖/到店)面试的 MySQL 题围绕"交易履约"——订单状态机、库存扣减、优惠券、超时关单、对账、幂等、热点商家数据倾斜。本篇精选 20 道公开高频题型,覆盖状态机设计、防超卖、抢券防刷、本地消息表、超时任务、逆向退款、数据倾斜。题目均为公开面经常见类型,解析讲通用原理,不涉及任何公司未公开信息。前置知识:第 3、5、17、18、23 章;本系列示例基于 MySQL 8.0。

📖 本篇简介:美团系(本地生活/外卖/到店)面试的 MySQL 题围绕"交易履约"——订单状态机、库存扣减、优惠券、超时关单、对账、幂等、热点商家数据倾斜。本篇精选 20 道公开高频题型,覆盖状态机设计、防超卖、抢券防刷、本地消息表、超时任务、逆向退款、数据倾斜。题目均为公开面经常见类型,解析讲通用原理,不涉及任何公司未公开信息。前置知识:第 3、5、17、18、23 章;本系列示例基于 MySQL 8.0。

目录

  • 1 本篇考情与知识地图
  • 题目 1:订单状态机怎么设计
  • 题目 2:库存扣减如何防超卖
  • 题目 3:优惠券红包系统怎么设计
  • 题目 4:高并发抢券如何防刷
  • 题目 5:商户商品多条件查询表怎么设计
  • 题目 6:超时未支付订单怎么自动取消
  • 题目 7:外卖订单状态流转如何保证幂等
  • 题目 8:对账系统怎么设计
  • 题目 9:分库分表按用户还是按商家
  • 题目 10:本地消息表如何保证最终一致
  • 题目 11:秒杀库存如何预热
  • 题目 12:动态多条件查询索引怎么建
  • 题目 13:退款逆向流程数据怎么设计
  • 题目 14:大促前DB压测怎么做
  • 题目 15:读多写少怎么用缓存
  • 题目 16:MQ和DB双写一致性
  • 题目 17:订单表归档策略
  • 题目 18:重复下单如何幂等
  • 题目 19:热点商家数据倾斜怎么办
  • 题目 20:O2O场景MySQL常见瓶颈点
  • 速查速记
  • 常见坑与注意事项
  • 复习清单
  • 练习题

1 本篇考情与知识地图

美团系面试围绕"一笔外卖订单从下单到送达再到退款"展开,核心是状态机 + 幂等 + 最终一致。先建地图:

| 环节 | 数据特点 | 关键技术 |

|---|---|---|

| 下单 | 状态多、并发高 | 幂等 + 原子扣减 |

| 支付 | 状态机、回调 | 状态单向流转 |

| 履约 | 长时间状态 | 超时任务 |

| 退款 | 逆向 | 逆向流水 |

| 对账 | 钱要对平 | T+1 对账 |

主线:每一次状态流转都要幂等、可追踪、可对账。

题目 1:订单状态机怎么设计

考点

  • 状态枚举;单向流转;防乱序。

参考答案

1. 状态枚举清晰:待支付、已支付、履约中、已完成、已取消、退款中、已退款;

2. 单向流转:状态只能按约定路径前进,不能跳变;

3. 条件更新:UPDATE order SET status=新 WHERE id=? AND status=旧,受影响 0 行说明状态已变;

4. 状态流水:每次流转记一条状态变更日志,便于追踪;

5. 并发安全:靠 where 旧状态条件保证并发不会乱序覆盖。

-- 状态机条件更新,本示例基于 MySQL 8.0
UPDATE t_order SET status = 2, pay_time = NOW()
 WHERE id = 88001 AND status = 1;  -- 仅当仍是待支付(1)时才置为已支付(2)

追问

  • 问:重复回调怎么办?答:where status=1,已支付后再回调影响 0 行,天然幂等。
  • 问:要回退状态(如支付失败)?答:状态机允许明确的逆向边,但要记流水。

题目 2:库存扣减如何防超卖

考点

  • 原子更新;条件扣减。

参考答案

1. 原子 SQL:UPDATE stock SET count=count-1 WHERE sku=? AND count>0;

2. 别先查后扣:并发间隙会超卖;

3. 预占库存:下单先冻结库存,支付确认再真正扣;

4. 超时释放:未支付超时自动解冻回补;

5. 对账:库存与订单对账兜底。

追问

  • 问:预占和实扣区别?答:预占防超卖+支持超时释放,实扣是最终扣减。
  • 问:高并发预占?答:热点库存前置 Redis,异步落 DB。

题目 3:优惠券红包系统怎么设计

考点

  • 券模板 + 用户券;库存。

参考答案

1. 券模板表:定义券类型、面额、库存、有效期;

2. 用户券表:谁领了哪张券、状态(未用/已用/过期);

3. 领券原子扣库存:模板库存 WHERE count>0 原子扣;

4. 用券幂等:一张券只能用一次,WHERE status=未用;

5. 过期处理:定时任务把过期券置为已过期。

-- 领券:扣模板库存 + 写用户券,本示例基于 MySQL 8.0
START TRANSACTION;
UPDATE t_coupon_tpl SET remain = remain - 1 WHERE tpl_id = 5 AND remain > 0;
INSERT INTO t_user_coupon(user_id, tpl_id, status, created_at)
VALUES (1001, 5, 0, NOW());  -- 0=未使用
COMMIT;

追问

  • 问:一人限领一张怎么保证?答:UNIQUE(user_id, tpl_id),重复领冲突报错。
  • 问:券超发了?答:模板 remain 原子扣 + 对账,兜底。

题目 4:高并发抢券如何防刷

考点

  • 限流;去重;前置拦截。

参考答案

1. 限领规则:一人一张,唯一索引兜底;

2. 限流:按用户/IP 限流,防脚本刷;

3. 验证码/风控:领券前过人机校验;

4. 库存前置 Redis:先扣 Redis,异步发券落库;

5. 黑名单:识别薅羊毛批量拦截。

追问

  • 问:Redis 扣了但 DB 发券失败?答:异步补偿 + 对账,失败回补 Redis 库存。
  • 问:库存怎么同步到 Redis?答:活动开始前预热全量,扣减后异步持久化。

题目 5:商户商品多条件查询表怎么设计

考点

  • 组合索引;ES 兜底。

参考答案

1. 核心条件走索引:商户 ID、分类、状态等高频过滤列建联合索引;

2. 动态条件:条件组合多,单靠联合索引覆盖不全;

3. ES 兜底:复杂多条件/排序/距离检索走 ES;

4. 冗余字段:高频展示字段冗余进商品表;

5. 避免全表扫:任何查询必须带商户/区域等强过滤条件。

追问

  • 问:多个可选条件怎么建索引?答:把固定条件建索引,动态条件交给 ES。
  • 问:距离排序?答:地理空间索引或 ES geo,别在 DB 里算距离。

题目 6:超时未支付订单怎么自动取消

考点

  • 延迟任务;状态机。

参考答案

1. 定时任务扫单:扫"待支付且超时"订单,置为取消并释放库存;

2. 延迟队列:下单时发延迟消息,到点检查是否仍未支付,更实时;

3. 条件取消:UPDATE order SET status=取消 WHERE id=? AND status=待支付 AND create_time<超时;

4. 幂等:已支付的单取消不生效(where 状态拦截);

5. 库存释放:取消同时回补预占库存。

-- 超时关单:仅取消仍处于待支付的单,本示例基于 MySQL 8.0
UPDATE t_order
SET status = 5, cancel_time = NOW()
WHERE status = 1 AND created_at < DATE_SUB(NOW(), INTERVAL 30 MINUTE);

追问

  • 问:扫单会不会扫到刚支付的?答:where status=1,已支付不受影响;且到点再查支付状态。
  • 问:延迟队列比定时扫好在哪?答:实时、不扫全表;但要可靠投递。

题目 7:外卖订单状态流转如何保证幂等

考点

  • 状态机幂等;重复消息。

参考答案

1. 条件更新:每次流转 WHERE status=旧,重复流转影响 0 行;

2. 事件去重:MQ 消费按事件 ID 去重;

3. 状态流水:每次流转记流水,可重放入口;

4. 单向:已完成状态不允许回退;

5. 对账:状态与支付/履约系统对账。

追问

  • 问:骑手状态和订单状态不一致?答:以履约系统事件为准,驱动订单状态流转。
  • 问:重复推送事件?答:事件 ID 去重表拦截。

题目 8:对账系统怎么设计

考点

  • 多方对账;差异处理。

参考答案

1. 三方对账:订单、支付、商户账单逐笔核对;

2. T+1 全量:夜间跑昨日数据比对;

3. 差异清单:自动输出金额/状态不符的单;

4. 自动补偿:可自动修复的自动补,复杂的进工单;

5. 准实时:资金类关键链路近实时对账。

追问

  • 问:对账跑在线库?答:迁到从库/离线库,别压在线。
  • 问:长短款?答:记差异账,财务介入处理。

题目 9:分库分表按用户还是按商家

考点

  • 查询模式决定分片键。

参考答案

1. 用户高频看自己订单:按 user_id 分片最自然;

2. 商家高频看自己接单:按 merchant_id 分片;

3. 两难:两个维度都高频——主分片按一个,另一维度建路由表/同步 ES;

4. 权衡:C 端用户量大、QPS 高,通常按 user_id 分片;

5. 运营检索:多维分析全走 ES/数仓。

追问

  • 问:选 user_id 后商家怎么查?答:merchant_order 路由索引表,或 ES。
  • 问:大商家订单量巨大?答:本身就是热点,单独处理。

题目 10:本地消息表如何保证最终一致

考点

  • 同事务写消息;异步投递。

参考答案

1. 业务表与消息表同事务:要么都成功;

2. 后台扫表投递 MQ:成功标记,失败重试;

3. 消费幂等:按消息 ID 去重;

4. 死信 + 人工:超限进死信;

5. 清理:已发消息定期删。

追问

  • 问:消息表和业务表必须同库?答:是,本地事务才有效;跨库用事务消息。
  • 问:投递重复?答:消费端幂等兜底。

题目 11:秒杀库存如何预热

考点

  • Redis 预热;异步落库。

参考答案

1. 活动前把库存加载进 Redis;

2. 用户请求先在 Redis 原子扣(Lua 保证原子);

3. 下单成功异步扣 DB;

4. Redis 与 DB 对账:异步核对差异;

5. 活动结束落盘:最终库存以 DB 为准。

追问

  • 问:Redis 扣减怎么原子?答:Lua 脚本把"判断+扣减"放一个脚本里执行。
  • 问:Redis 挂了?答:降级到 DB 限流扣减,或直接停止活动保 DB。

题目 12:动态多条件查询索引怎么建

考点

  • 组合索引覆盖;ES。

参考答案

1. 固定条件建联合索引:如 (merchant_id, status);

2. 可选条件多:不可能每个组合都建索引;

3. 高频组合建专属索引,低频走 ES;

4. 避免索引过多:写放大;

5. 强制带过滤:查询必须带商户/区域强过滤列。

追问

  • 问:可选条件拼 SQL?答:动态 SQL 拼装,配合索引;拿不准就 ES。
  • 问:索引建多了?答:单表索引建议 5 个以内。

题目 13:退款逆向流程数据怎么设计

考点

  • 逆向流水;状态机。

参考答案

1. 退款单表:关联原订单,记录退款金额、原因、状态;

2. 逆向流水:每笔退款记一条资金流水;

3. 状态机:退款申请 → 退款中 → 退款成功/失败;

4. 幂等:同一退款单重复处理不重复退款;

5. 对账:退款与支付对账。

追问

  • 问:部分退款?答:退款单支持多次,累计不超过原单金额。
  • 问:库存要回退吗?答:退款成功回补对应库存。

题目 14:大促前DB压测怎么做

考点

  • 压测方法;容量。

参考答案

1. 流量回放:用历史真实流量压测,混合读写;

2. 压测目标:找到单库 TPS/QPS 上限与拐点;

3. 容量规划:按预估峰值 × 安全系数配资源;

4. 预热:热点数据进 Buffer Pool/缓存;

5. 预案演练:限流、降级、切换演练;

6. 压测后调优:根据瓶颈加索引/分库/缓存。

追问

  • 问:怎么压才真实?答:别纯 select,按线上读写比例混合,带真实数据量。
  • 问:压测发现 CPU 打满?答:先优化慢 SQL/索引,再考虑扩容。

题目 15:读多写少怎么用缓存

考点

  • 缓存策略;一致性。

参考答案

1. 热点读挡 Redis:商户信息、商品详情等读多写少;

2. Cache-Aside:读 miss 查库回填,写后删缓存;

3. 写少:可接受缓存 TTL 兜底;

4. 防穿透:空值缓存/布隆过滤;

5. 防击穿:热点 key 互斥重建;防雪崩:TTL 加随机。

追问

  • 问:写频繁的对象也缓存?答:写频繁缓存失效频繁,权衡命中率。
  • 问:缓存和 DB 不一致?答:延迟双删 + TTL。

题目 16:MQ和DB双写一致性

考点

  • 本地消息表;可靠投递。

参考答案

1. 先写库再发消息:用本地消息表绑事务;

2. 可靠投递:消息落库后异步投递,不丢;

3. 消费幂等:重复消费无副作用;

4. 失败重试 + 死信;

5. 对账:MQ 消息与业务状态对账。

追问

  • 问:先发消息再写库?答:可能消息发了库没写,消费方处理不存在的业务,不推荐。
  • 问:事务消息?答:MQ 半消息也可,本地消息表更通用。

题目 17:订单表归档策略

考点

  • 冷热分离;分区。

参考答案

1. 在线只留近 N 月:更早迁归档库;

2. 按时间分区:DROP 老分区快速归档;

3. 归档校验后删:行数/checksum 一致再删在线;

4. 归档可查:提供只读入口;

5. 备份兜底。

追问

  • 问:归档后历史订单查不到?答:归档库提供查询入口,业务按时间路由。
  • 问:多久归档一次?答:按月/按季,在线库控制在千万级。

题目 18:重复下单如何幂等

考点

  • 幂等键;唯一约束。

参考答案

1. 前端/客户端带幂等键:如 token,每次下单唯一;

2. 唯一索引:(user_id, 业务幂等键) 唯一约束;

3. 重复请求拦截:重复下单直接返回已有订单;

4. 支付幂等:支付回调按支付单号去重;

5. 状态机兜底:状态已变则不重复处理。

追问

  • 问:用户连点两下?答:幂等键 + 唯一索引,第二次冲突返回同一单。
  • 问:幂等键谁生成?答:前端进下单页时申请一个一次性 token。

题目 19:热点商家数据倾斜怎么办

考点

  • 分片倾斜;热点处理。

参考答案

1. 分片键别让大商家集中:按 user_id 分片时某大商家订单分散到多片是好事;

2. 单独热点处理:超大商家订单单独路由/扩容;

3. 读热点:商家接单界面挡 Redis;

4. 写热点:接单更新合并异步化;

5. 监控倾斜:分片 QPS 不均及时发现。

追问

  • 问:按商家分片遇到大商家?答:单个商家占满一片,要拆分或单独路由。
  • 问:读热点商家?答:商户信息缓存,接单列表分页走索引。

题目 20:O2O场景MySQL常见瓶颈点

考点

  • 瓶颈识别。

参考答案

1. 下单写并发:扣库存 + 建订单,热点商品行锁竞争;

2. 订单列表深分页:用户翻历史订单慢;

3. 多条件检索:商户/位置/分类组合查询索引难覆盖;

4. 状态流转频繁:履约状态更新多;

5. 大表归档:订单表持续膨胀;

6. 对应手段:扣减前置、游标分页、ES 检索、状态机条件更新、分区归档。

追问

  • 问:最容易先扛不住的?答:热点商品扣库存和订单列表查询。
  • 问:怎么提前发现?答:压测 + 大促大盘监控。

速查速记

| 主题 | 核心手法 | 关键 SQL/动作 |

|---|---|---|

| 状态机 | 条件更新 | WHERE status=旧 |

| 防超卖 | 原子扣减 | WHERE count>0 |

| 超时关单 | 延迟队列/扫单 | 条件取消+释放库存 |

| 抢券 | Redis前置+限流 | 一人一单唯一索引 |

| 幂等 | 业务唯一键 | 唯一约束去重 |

| 对账 | T+1三方核对 | 差异自动补偿 |

| 多条件 | ES兜底 | 固定条件建索引 |

常见坑与注意事项

  • 坑 1:先查库存再扣——并发超卖,必须一条 SQL 原子完成;
  • 坑 2:状态流转无条件更新——重复回调导致状态错乱,必须带旧状态条件;
  • 坑 3:超时关单扫全表——无索引扫超时订单拖库,按时间+状态索引分批;
  • 坑 4:退款不幂等——重复退款造成资金损失,退款单唯一约束;
  • 坑 5:分片键只考虑一个查询维度——上线才发现另一维度查不动;
  • 注意:本篇均为公开题型与通用交易系统设计,不代表任何公司真实方案。

复习清单

| 题目 | 核心结论 | 易错点 |

|---|---|---|

| 状态机 | 单向+条件更新 | 别无条件改状态 |

| 防超卖 | 原子扣减 | 别先查后扣 |

| 优惠券 | 模板+用户券 | 一人一单唯一索引 |

| 超时关单 | 延迟队列 | 已支付别误关 |

| 幂等 | 业务唯一键 | 支付回调要去重 |

| 分片键 | 查询模式决定 | 别只看一个维度 |

| 对账 | 三方T+1 | 别压在线库 |

| 数据倾斜 | 热点单独处理 | 监控分片均衡 |

练习题

练 1:写一条"待支付 30 分钟未支付自动取消并保证不误伤已支付订单"的 SQL。

<details>

<summary>参考答案</summary>

UPDATE t_order
SET status = 5, cancel_time = NOW()
WHERE status = 1
  AND created_at < DATE_SUB(NOW(), INTERVAL 30 MINUTE);

status=1(待支付)作为条件,已支付(status=2)的订单不受影响;再配合库存回补和库存预占释放,完成超时关单。

</details>

练 2:用户下单时连点两次产生两笔订单,从 DB 层怎么杜绝?

<details>

<summary>参考答案</summary>

前端进入下单页申请一次性幂等 token,下单时把 token 随请求带上;订单表对 (user_id, idempotent_token) 建唯一索引,第二次提交触发唯一冲突,应用捕获后返回已有订单而非新建;支付回调同样按支付单号幂等。

</details>

下一篇预告:第 33 章 京东系MySQL真题精讲

— 全文完 —回到顶部 ↑
下载推广海报

文章推广海报

《第 32 章 美团系MySQL真题精讲》完整推广海报
DISCUSSION

文章回复

0 条公开回复
未登录回复需要审核后公开
还没有回复,欢迎参与讨论。