📖 本篇简介:京东系(自营电商/物流/大促)面试的 MySQL 题聚焦"电商交易链路与大促"——库存扣减、订单快照、SKU/SPU、购物车、支付回调、履约、热点商品、双写迁移。本篇精选 20 道公开高频题型,覆盖大促库存预占、超时关单、商品数据模型、跨分片商家查询、ES 同步、热点行优化。题目均为公开面经常见类型,解析讲通用原理,不涉及任何公司未公开信息。前置知识:第 3、5、17、18、23 章;本系列示例基于 MySQL 8.0。
目录
- 1 本篇考情与知识地图
- 题目 1:大促库存扣减方案怎么设计
- 题目 2:订单超时关单怎么实现
- 题目 3:购物车表怎么设计
- 题目 4:商品SPU和SKU数据模型
- 题目 5:分库分表后商家维度查询怎么办
- 题目 6:价格变动为什么要订单快照
- 题目 7:支付回调如何幂等
- 题目 8:大促前数据库要做哪些准备
- 题目 9:库存预占与回滚怎么设计
- 题目 10:海量评论晒单数据怎么存
- 题目 11:商品搜索和商品库怎么同步
- 题目 12:订单履约状态机怎么流转
- 题目 13:秒杀系统DB层怎么扛
- 题目 14:分库分表扩容双写怎么做
- 题目 15:热点商品行锁竞争怎么优化
- 题目 16:业务库和数仓怎么分工
- 题目 17:大促期间DB层限流降级
- 题目 18:订单多条件查询索引设计
- 题目 19:MySQL同步ES如何保证一致
- 题目 20:电商MySQL架构怎么演进
- 速查速记
- 常见坑与注意事项
- 复习清单
- 练习题
1 本篇考情与知识地图
京东系面试围绕"一笔自营订单从加购、下单、支付、发货到签收",核心是库存准、价格快照、大促扛得住。先建地图:
| 链路环节 | 数据问题 | 手段 |
|---|---|---|
| 加购 | 购物车 | 轻量表+缓存 |
| 下单 | 库存预占 | 原子扣减 |
| 支付 | 回调幂等 | 状态机 |
| 履约 | 状态流转 | 事件驱动 |
| 搜索 | 多条件 | ES 同步 |
主线:库存和价格这两样不能错,大促要提前把洪峰挡在数据库之外。
题目 1:大促库存扣减方案怎么设计
考点
- 预占;原子;Redis 前置。
参考答案
1. 下单预占:下单即冻结库存,WHERE stock>0 原子扣;
2. 支付确认:预转给实扣;未支付超时释放;
3. 大促前置 Redis:热点商品库存放 Redis,Lua 原子扣,异步落 DB;
4. 限流排队:网关限流,DB 按可承受速率消费;
5. 对账:Redis 与 DB 库存定时核对。
-- 预占库存:冻结而非实扣,本示例基于 MySQL 8.0
UPDATE t_sku_stock
SET locked = locked + 1, update_time = NOW()
WHERE sku_id = 66 AND stock - locked >= 1; -- 可用库存=stock-locked
追问
- 问:可用库存怎么算?答:
可用 = 总库存 - 已锁定,扣减条件用可用判断。 - 问:超卖了?答:条件原子判断 + 对账兜底纠正。
题目 2:订单超时关单怎么实现
考点
- 延迟任务;释放库存。
参考答案
1. 延迟队列:下单发延迟消息,到点检查是否支付;
2. 定时扫单兜底:扫超时未支付单;
3. 条件关单:WHERE status=待支付,已支付不动;
4. 释放预占库存:关单同时回补;
5. 幂等:重复关单影响 0 行。
追问
- 问:延迟队列丢了?答:定时扫单兜底。
- 问:关单瞬间用户付款?答:以支付为准,关单条件判断+支付回调抢先,靠状态机收敛。
题目 3:购物车表怎么设计
考点
- 轻量;合并。
参考答案
1. 一行一个购物项:(user_id, sku_id, quantity, selected);
2. 按 user_id 查询:进购物车一次拉出;
3. 登录/未登录合并:未登录存 Redis,登录后合并;
4. 价格实时算:购物车不存价格,展示时实时查商品;
5. 量大:购物车数据放 Redis,DB 持久化登录态。
追问
- 问:购物车要存商品快照吗?答:不存,下单那一刻才生成订单快照。
- 问:购物车太多商品?答:分页/折叠,Redis 扛。
题目 4:商品SPU和SKU数据模型
考点
- 商品建模;主从。
参考答案
1. SPU:标准产品单元,描述一款商品(如"某手机"),存公共属性;
2. SKU:最小存货单位,具体规格(颜色/内存),存价格、库存、图片;
3. 一对多:一个 SPU 多个 SKU;
4. 属性表:SPU 扩展属性用键值表或 JSON;
5. 索引:SKU 按 spu_id 查,商品搜索靠 ES。
-- SPU 与 SKU,本示例基于 MySQL 8.0
CREATE TABLE t_spu (spu_id BIGINT PRIMARY KEY, title VARCHAR(200), brand_id BIGINT);
CREATE TABLE t_sku (
sku_id BIGINT PRIMARY KEY, spu_id BIGINT, price DECIMAL(10,2),
stock INT, spec VARCHAR(100), KEY idx_spu(spu_id)
);
追问
- 问:规格属性怎么存?答:固定高频列建字段,长尾属性存 JSON。
- 问:商品详情怎么查?答:SPU + 多个 SKU 一次聚合,挡缓存。
题目 5:分库分表后商家维度查询怎么办
考点
- 路由表;ES。
参考答案
1. 订单按 user_id 分片(C 端量大);
2. 商家查订单:建 merchant_id → order_id 路由索引表,或同步 ES;
3. 商家后台多条件:全走 ES;
4. 避免跨分片大聚合:商家报表走数仓;
5. 冗余:订单表冗余 merchant_id 便于过滤。
追问
- 问:商家要实时看今天单量?答:Redis 计数 + ES,别跨分片 count。
- 问:路由索引表膨胀?答:按 merchant_id 分片或只留近期。
题目 6:价格变动为什么要订单快照
考点
- 快照设计;历史价。
参考答案
1. 商品价格会变:下单时必须把价格、名称、规格快照进订单;
2. 快照存订单明细行:订单明细表冗余商品当时信息;
3. 不联商品表:后续改价/下架不影响历史订单;
4. 退款按快照价算;
5. 商品主表仍存现价,订单存历史价。
追问
- 问:快照冗余不会不一致?答:下单那一刻固化,之后不改,天然一致。
- 问:快照字段太多?答:只冗余展示/结算必需字段。
题目 7:支付回调如何幂等
考点
- 回调重试;状态机。
参考答案
1. 支付单号唯一:一笔订单一个支付单号;
2. 条件更新:UPDATE order SET status=已支付 WHERE id=? AND status=待支付;
3. 回调重复:已支付后再回调影响 0 行,不重复入账;
4. 回调验签:校验支付平台签名,防伪造;
5. 对账兜底:与支付平台对账。
追问
- 问:回调乱序(成功通知比失败晚到)?答:状态机只允许前进,已支付不再回退。
- 问:本地还没收到回调用户已付款?答:主动查单补偿。
题目 8:大促前数据库要做哪些准备
考点
- 容量;预热;预案。
参考答案
1. 压测:按预估峰值压测,找出瓶颈;
2. 容量冗余:CPU 水位留缓冲,加只读副本;
3. 缓存预热:热点商品/库存进 Redis;
4. 索引审查:慢 SQL 提前优化;
5. 预案演练:限流、降级、切换、扩容演练;
6. 值守:大盘监控 + 值班 + 应急预案。
追问
- 问:临时加机器来得及吗?答:只读副本加了就能分担读,写扩容要提前。
- 问:大促中发现慢 SQL?答:先 kill 止血,再灰度优化。
题目 9:库存预占与回滚怎么设计
考点
- 预占流水;释放。
参考答案
1. 预占记录:下单写一条库存冻结记录;
2. 支付成功:冻结转实扣;
3. 超时/取消:冻结释放回可用;
4. 原子操作:冻结/释放都用条件更新防超卖;
5. 对账:冻结量与在途订单对账。
追问
- 问:预占和实扣两次更新?答:是的,预占防超卖,实扣是最终态。
- 问:并发释放?答:条件更新,避免重复释放多补库存。
题目 10:海量评论晒单数据怎么存
考点
- 评论量大;归档。
参考答案
1. 按 spu_id 分片:某商品评论落固定分片;
2. 分页游标:按评论 ID/时间游标翻页;
3. 热度评论缓存:热门评价挡 Redis;
4. 归档:老商品评论归档;
5. 晒图存 OSS:图片存 OSS,DB 存 URL。
追问
- 问:评论数怎么统计?答:Redis 计数 + 商品表冗余 comment_count。
- 问:差评过滤?答:在 DB/ES 按评分过滤,别全拉回内存筛。
题目 11:商品搜索和商品库怎么同步
考点
- binlog 同步;ES。
参考答案
1. 商品库是权威源:ES 是派生索引;
2. binlog 订阅:Canal 监听商品 binlog 异步更新 ES;
3. 全量 + 增量:定期全量校准,实时增量更新;
4. 一致性容忍:ES 短时间延迟可接受;
5. 对账:ES 文档数与商品数定期核对。
追问
- 问:ES 挂了搜索?答:降级到 DB 简单筛选,不可用则搜索暂不可用。
- 问:商品改了 ES 没更新?答:binlog 延迟,定期全量校准。
题目 12:订单履约状态机怎么流转
考点
- 履约节点;事件驱动。
参考答案
1. 节点:已付款 → 拣货 → 出库 → 配送 → 签收;
2. 事件驱动:物流事件驱动订单状态流转;
3. 条件更新:每个流转带旧状态条件;
4. 状态流水:每节点记一条,便于追踪;
5. 幂等:重复事件不重复推进。
追问
- 问:物流系统和订单系统不一致?答:以物流事件为准驱动,定期对账。
- 问:拒收退货?答:进入逆向退款分支状态机。
题目 13:秒杀系统DB层怎么扛
考点
- 分层拦截;DB 减压。
参考答案
1. 前端限流:按钮置灰、答题/验证码挡机器人;
2. Redis 预扣库存:DB 不直接扛洪峰;
3. MQ 排队下单:异步落库,DB 匀速消费;
4. DB 原子扣:最终在 DB 核对防超卖;
5. 降级:Redis/MQ 出问题直接限流停秒。
追问
- 问:DB 层做了什么?答:只在异步队列里做最终扣减,不被瞬时流量打穿。
- 问:超卖兜底?答:DB
WHERE stock>0条件 + 对账。
题目 14:分库分表扩容双写怎么做
考点
- 不停机扩容;校验切流。
参考答案
1. 新分片就绪;
2. 双写:新写同时写新旧;
3. 存量迁移 + binlog 追平;
4. 校验:count/checksum 一致;
5. 灰度切读切写:观察无误后全切,老库只读下线。
追问
- 问:双写两边都写成功?答:以一边为准,另一边补偿;不能让两分支不一致。
- 问:怎么切最稳?答:先切读流量灰度,再切写。
题目 15:热点商品行锁竞争怎么优化
考点
- 单行锁;分桶。
参考答案
1. 库存分桶:一个商品库存拆多行,请求随机路由,打散行锁;
2. 库存前置 Redis:热点直接在 Redis 扣;
3. 缩短事务:扣减不夹带其他操作;
4. 批量异步:计数类合并刷回;
5. 大促前隔离:热点商品单独资源池。
追问
- 问:分桶后怎么看总库存?答:
SUM或 Redis 维护总数。 - 问:还不够?答:热点库存全放 Redis,DB 只做对账快照。
题目 16:业务库和数仓怎么分工
考点
- OLTP vs OLAP。
参考答案
1. 业务库 MySQL:事务、点查、低延迟;
2. 数仓:报表、聚合、离线分析;
3. 数据同步:业务库 binlog → 数仓;
4. 别在业务库跑大报表:拖垮在线;
5. 宽表:分析用宽表在数仓建好。
追问
- 问:运营要实时报表?答:实时数仓(ClickHouse),别查 MySQL。
- 问:为什么不在 MySQL 做分析?答:行存 + 事务,聚合扫描慢。
题目 17:大促期间DB层限流降级
考点
- 自我保护。
参考答案
1. 限流:DB QPS/连接到阈值,网关/应用层限流;
2. 降级:非核心功能(推荐、评价)暂时关闭;
3. 读降级:读超时走缓存兜底值;
4. 写降级:超卖商品直接售罄,不进扣减;
5. 保核心:保下单/支付主链路。
追问
- 问:限流在 DB 层还是上游?答:尽量上游(网关),别让请求打到 DB 才拒绝。
- 问:降级开关?答:配置中心一键开关,提前演练。
题目 18:订单多条件查询索引设计
考点
- 用户侧查询;联合索引。
参考答案
1. 我的订单:(user_id, status, created_at) 联合索引;
2. 按时间倒序:索引顺序匹配排序,免 filesort;
3. 运营多条件:走 ES;
4. 深分页:游标分页;
5. 覆盖:列表展示字段尽量覆盖索引。
-- 我的订单列表索引,本示例基于 MySQL 8.0
-- 查询:WHERE user_id=? AND status=? ORDER BY created_at DESC LIMIT ?,20
-- 索引:(user_id, status, created_at)
追问
- 问:用户查全部状态订单?答:status 可选,把等值 user_id 放最前,created_at 排序。
- 问:按订单号查?答:order_no 唯一索引。
题目 19:MySQL同步ES如何保证一致
考点
- binlog;对账;延迟。
参考答案
1. binlog 订阅增量同步:商品变更实时进 ES;
2. 全量校准:定期全量重建 ES;
3. 容忍延迟:搜索结果短时间延迟可接受;
4. 失败重试:同步失败进队列重试;
5. 对账:ES 与 DB 文档数比对。
追问
- 问:ES 数据错了?答:全量重建对应商品。
- 问:同步和事务一致?答:做不到强一致,靠最终一致 + 对账。
题目 20:电商MySQL架构怎么演进
考点
- 演进路径。
参考答案
1. 初期:单库单表;
2. 增长:读写分离 + 缓存;
3. 瓶颈:垂直拆库(商品/订单/支付);
4. 再增长:订单分库分表 + ES;
5. 大促:库存前置 Redis、MQ 削峰、限流降级;
6. 原则:按需演进,别过度设计。
追问
- 问:什么时候分库分表?答:单表千万、单库 CPU/IO 瓶颈,纵向升配不划算。
- 问:演进中最大的坑?答:扩容迁移、跨库事务、数据一致性。
速查速记
| 环节 | 方案 | 关键点 |
|---|---|---|
| 库存 | 预占+原子扣 | 可用=总-锁 |
| 价格 | 订单快照 | 下单固化 |
| 回调 | 条件更新幂等 | 验签+对账 |
| 搜索 | binlog同步ES | 全量校准 |
| 热点 | Redis前置+分桶 | DB最终核对 |
| 大促 | 压测+预热+限流 | 保主链路 |
| 演进 | 单库→读写→拆库→分片 | 按需 |
常见坑与注意事项
- 坑 1:购物车/订单直接用商品现价——商品改价后历史订单价错乱,必须快照;
- 坑 2:支付回调无条件更新——重复回调重复入账,必须带状态条件;
- 坑 3:热点商品单行扣库存——行锁串行,大促必崩,前置 Redis;
- 坑 4:运营多条件检索压 MySQL——跨分片聚合慢,走 ES/数仓;
- 坑 5:ES 当权威源——ES 是派生索引,商品数据以 MySQL 为准;
- 注意:本篇均为公开题型与通用电商架构,不代表任何公司真实系统。
复习清单
| 题目 | 核心结论 | 易错点 |
|---|---|---|
| 库存 | 预占+原子 | 可用=总-锁 |
| 快照 | 下单固化价格 | 别联商品表 |
| 支付回调 | 条件更新+验签 | 别重复入账 |
| SKU/SPU | 一对多 | 属性用JSON |
| 热点 | Redis前置 | 别单行硬扣 |
| 搜索同步 | binlog+校准 | ES非权威 |
| 大促 | 压测预热限流 | 别临时加写 |
| 演进 | 按需拆分 | 别过度设计 |
练习题
练 1:设计下单时"预占库存"的 SQL,要求库存不足时不成功。
<details>
<summary>参考答案</summary>
UPDATE t_sku_stock
SET locked = locked + 1, update_time = NOW()
WHERE sku_id = 66 AND (stock - locked) >= 1;
可用库存 = stock - locked,条件保证不超卖;受影响 0 行即库存不足。支付成功后将 locked 转为实扣,超时/取消则 locked 减回。
</details>
练 2:商品改价后,老订单显示的价格被联动改了,问题出在哪?怎么修?
<details>
<summary>参考答案</summary>
订单明细没有做价格快照,而是实时联商品表取现价。修复:在订单明细表冗余下单时的商品名称、单价、规格,后续商品改价/下架不影响历史订单;老数据批量回填一次快照。
</details>
下一篇预告:第 34 章 百度华为系MySQL真题精讲
文章回复
0 条公开回复