📖 本篇简介:面试到了中高级,面试官不再问"索引怎么建",而是问"这个业务你怎么选型、怎么设计整体架构"。数据库选型没有银弹——OLTP 用 MySQL、缓存用 Redis、搜索用 ES、分析用 ClickHouse,各管一摊。本篇用 10 个场景,带你建立一套"按业务特征选型"的方法论:从早期别过度设计、MySQL vs NoSQL、搜索与分析选型,到完整架构、分库分表中间件、缓存设计、微服务拆库、分布式一致性,再到架构演进路径。示例基于 MySQL 8.0,学完你能系统回答架构设计题。
目录
- 1 本篇导读与数据库选型方法论
- 2 场景一:业务早期要不要直接上分布式数据库
- 3 场景二:什么场景用 MySQL 什么场景用 NoSQL
- 4 场景三:全文搜索和多维筛选该选什么数据库
- 5 场景四:海量分析报表该用什么数据库
- 6 场景五:一个完整业务系统怎么设计整体架构
- 7 场景六:分库分表中间件怎么选
- 8 场景七:缓存层怎么设计 Redis 该放什么
- 9 场景八:微服务下数据库该怎么拆
- 10 场景九:分布式下数据一致性怎么保证
- 11 场景十:从小厂到大厂的架构怎么演进
- 12 数据库选型与架构速查表
- 13 常见坑与注意事项
- 14 练习题与自测
1 本篇导读与数据库选型方法论
数据库选型的第一性原理:没有最好的数据库,只有最合适业务场景的数据库。面试时切忌上来就说"我用最先进的分布式数据库",那是不懂业务。
选型先问三个问题:
1. 读写模型是什么:是事务型 OLTP(频繁增删改查)还是分析型 OLAP(批量聚合统计)?
2. 数据模型是什么:结构化表(MySQL)还是文档/键值(NoSQL)?
3. 规模多大:单库能扛,还是要分布式?
各类数据库的分工:
| 类型 | 代表 | 适合干什么 |
|---|---|---|
| 关系型 OLTP | MySQL | 交易、订单、账务,强事务 |
| 缓存 KV | Redis | 热点数据、计数、分布式锁 |
| 搜索引擎 | Elasticsearch | 全文检索、多维筛选 |
| 分析型 OLAP | ClickHouse | 报表、大数据聚合 |
| 文档型 | MongoDB | 结构不固定的业务数据 |
下面 10 个场景,就是这套方法论的落地。
2 场景一:业务早期要不要直接上分布式数据库
场景描述:创业公司日活才几千,老板说"一步到位上分布式数据库,省得以后迁移"。你怎么看?
考察点:考察"不过度设计"的架构判断——早期 simplicity 优先。
解答思路:
这是典型的过度设计。早期业务量小,单库 MySQL 完全够用,分布式数据库(TiDB、OceanBase 等)带来的复杂度远超收益:运维成本高、排障难、团队学习曲线陡。
-- 本示例基于 MySQL 8.0
-- 早期就用最朴素的单库 MySQL,把精力放在业务上
-- 一张 orders 表,索引设计好,几百万行内根本不需要分布式
CREATE TABLE orders (
order_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
...
);
-- 等单库真的扛不住了(几千万行、写入 TPS 到顶),再考虑拆分
正确路径:单库 MySQL → 读写分离 → 分库分表 → (必要时)分布式数据库。一步步来,每一步都是被真实流量逼出来的,而不是预设的。
为了让"克制选型"更具体,下面这张表把"什么规模该上什么"列清楚,可以直接背:
| 单表/单库规模 | 建议架构 | 不该做的事 |
|---|---|---|
| < 500 万行 | 单库 MySQL + 合适索引 | 急着分库分表 |
| 500 万~2000 万 | 读写分离 + Redis 缓存 | 引入分布式数据库 |
| 2000 万~1 亿 | 分库分表 + 归档 | 报表打主库 |
| > 1 亿且写到顶 | 评估分布式数据库 | 硬扛单库 |
追问:那什么时候才真该上分布式数据库?
当业务数据量和并发已经让分库分表的运维成本(跨分片、分布式事务)变得难以承受,且团队有足够运维能力时,分布式数据库才值得引入。
3 场景二:什么场景用 MySQL 什么场景用 NoSQL
场景描述:业务里有"商品详情""用户画像""会话消息"几类数据,团队争论该全放 MySQL 还是全放 MongoDB。怎么选?
考察点:考察关系型 vs 文档型数据库的适用边界。
解答思路:
按数据特征选:
| 数据特征 | 选 MySQL | 选 NoSQL(MongoDB/Redis) |
|---|---|---|
| 需要事务、强一致 | 订单、账务、库存 | — |
| 结构固定、要 JOIN | 用户、商品 | — |
| 结构灵活、字段多变 | — | 用户画像、内容 |
| 高频读写、要快 | — | 会话、缓存数据 |
-- 本示例基于 MySQL 8.0
-- 订单这种强事务、结构固定的,必须放 MySQL
BEGIN;
UPDATE stock SET num=num-1 WHERE id=1;
INSERT INTO orders(...) VALUES(...);
COMMIT; -- 事务保证扣库存和下单原子性
# 本示例为示意:用户画像这种字段灵活、无事务需求的,用 MongoDB 文档
# db.user_profile.insert_one({
# "uid": 666,
# "tags": ["学生","数码控"], # 字段灵活,随时加
# "ext": {"city":"东营","level":3}
# })
# 不需要预定义表结构,适合 schema 经常变的数据
追问:NoSQL 没有事务,那它能存订单吗?
勉强能,但失去事务保护,并发下容易超卖、不一致。涉及钱和库存的,老老实实用 MySQL。
4 场景三:全文搜索和多维筛选该选什么数据库
场景描述:用户要在商品里做"标题模糊搜索 + 价格区间 + 分类 + 销量排序"的复杂筛选,MySQL 的 LIKE %xx% 越来越慢。怎么办?
考察点:考察为什么要引入 Elasticsearch,而不是死磕 MySQL。
解答思路:
LIKE '%关键词%' 走不了索引,且无法做"分词搜索"("红色连衣裙"要能匹配"连衣裙 红色")。这种全文检索和多维筛选,MySQL 天生不擅长,要引入 Elasticsearch:
-- 本示例基于 MySQL 8.0
-- MySQL 做法(慢且弱):
SELECT * FROM goods WHERE title LIKE '%连衣裙%' AND price BETWEEN 100 AND 500;
-- LIKE 前导 % 全表扫;且不支持分词、相关度排序
-- 正确:商品数据通过 binlog 同步到 ES,搜索查 ES
-- ES 支持:分词全文检索 + 多条件 filter + 相关度排序 + 高亮
分工:MySQL 存商品主数据(增删改走 MySQL),ES 存搜索索引(通过 Canal 订阅 binlog 异步同步),搜索请求打 ES,不打 MySQL。
追问:ES 和 MySQL 数据不一致怎么办?
ES 是派生索引,以 MySQL 为准。通过 binlog 近实时同步(秒级延迟),容忍短暂不一致;搜索不是账务,弱一致可接受。
5 场景四:海量分析报表该用什么数据库
场景描述:运营要做一个"实时大屏",统计全平台每天的 GMV、订单量、转化率,数据量几亿行。MySQL 跑聚合要几十秒。怎么办?
考察点:考察 OLTP 与 OLAP 的分离——分析别打业务库。
解答思路:
OLTP 数据库(MySQL)是为"点查、短事务"优化的,不适合"全表聚合"。海量分析要走列存的 OLAP 数据库(ClickHouse):
-- 本示例基于 MySQL 8.0
-- MySQL 跑这种全量聚合,会扫描大量行,拖垮业务库
SELECT dt, SUM(amount), COUNT(*) FROM orders
GROUP BY dt ORDER BY dt DESC; -- 大表聚合,慢且伤主库
-- 正确:订单 binlog 同步到 ClickHouse,分析查询打 CK
-- CK 列存 + 向量化,几亿行聚合秒出
-- 业务库只负责交易,分析库负责报表,物理隔离
核心原则:交易流量和分析流量必须物理隔离。一个运营报表把主库查挂,是线上最常见的低级事故。
追问:为什么 ClickHouse 聚合比 MySQL 快几个数量级?
CK 是列存:聚合只需要读用到的那几列,且向量化批量处理;MySQL 行存要把整行读出来,IO 放大几十倍。
6 场景五:一个完整业务系统怎么设计整体架构
场景描述:给你一个电商系统(用户、商品、订单、支付、搜索、报表),让你从零设计数据库架构。你会怎么分层?
考察点:考察综合架构设计能力,这是架构师面试的压轴题。
解答思路:
一个完整电商的数据库架构分层(从外到内):
用户请求
→ CDN/网关(限流)
→ 业务服务
├─ Redis(缓存商品、会话、库存)
├─ MySQL 集群(主写 + 从读,分库分表) ← 交易主库
├─ Elasticsearch(商品搜索,binlog 同步)
├─ ClickHouse(报表分析,binlog 同步)
└─ MQ(下单削峰)
-- 本示例基于 MySQL 8.0
-- 交易核心数据(订单、库存、支付)放 MySQL,保证事务
-- 其他数据各司其职:
-- 商品详情、购物车 → Redis 缓存
-- 商品搜索 → ES
-- 经营报表 → CK
-- 会话/验证码 → Redis
关键认知:一个系统绝不是只用一个数据库。是"MySQL 做交易核心 + Redis 缓存 + ES 搜索 + CK 分析"的组合,每个数据库干自己最擅长的事,通过 binlog/MQ 保持数据流转。
面试答架构题时,有个加分技巧:不要只罗列用了哪些组件,要讲清楚数据流向——"MySQL 是真相源,通过 Canal 订阅 binlog,把商品数据同步到 ES 供搜索、把订单数据同步到 CK 供报表,Redis 缓存热点读,MQ 做下单削峰"。能把数据怎么流讲出来,就超过 80% 的候选人了。
追问:这么多数据库,数据怎么保持一致?
以 MySQL 为唯一真相源,其他都是它的派生视图,通过 binlog 近实时同步,容忍秒级延迟。最终一致,不强求实时一致。
7 场景六:分库分表中间件怎么选
场景描述:决定分库分表了,了解到 ShardingSphere、MyCat、ProxySQL 几个中间件,怎么选?
考察点:考察分库分表中间件的选型对比。
解答思路:
| 中间件 | 定位 | 优点 | 缺点 |
|---|---|---|---|
| ShardingSphere | 分库分表+读写分离 | 生态全、社区活跃 | 功能多要学 |
| MyCat | 早期分库分表代理 | 部署简单 | 维护放缓 |
| ProxySQL | 读写分离代理 | 高性能、灵活 | 不管分表 |
-- 本示例基于 MySQL 8.0
-- 中间件的作用:业务写 INSERT INTO orders,中间件根据 user_id 路由到对应分片
-- 业务代码无感知,像操作一张表一样
-- ShardingSphere-JDBC 是以 jar 包形式嵌在应用内,性能好
-- ShardingSphere-Proxy 是独立代理,对业务透明但多一层网络
选型建议:Java 应用优先 ShardingSphere-JDBC(进程内路由,无额外代理层);多语言或想统一治理用 Proxy 模式。
追问:分库分表中间件有什么代价?
跨分片查询、分布式事务、扩容迁移都变复杂;且中间件本身成了新组件要运维。能用单表就别上中间件。
8 场景七:缓存层怎么设计 Redis 该放什么
场景描述:架构里 Redis 该放什么、不该放什么?什么都往里塞会出问题。
考察点:考察缓存设计的边界——缓存不是万能的。
解答思路:
适合放 Redis 的:高频读、可容忍短暂不一致、重建成本高的数据。
# 本示例为示意:Redis 缓存典型场景(无真实 Redis,仅说明)
# 适合缓存:
# 商品详情:高频读、变更少 → 缓存
# 库存计数:高频扣减 → Redis 原子操作
# 用户会话:高频读写 → Redis
# 热点排行榜:zset → Redis
# 不适合缓存:
# 刚写入就要强一致的数据(账务余额)
# 体量巨大、访问稀疏的数据(会撑爆内存)
缓存三大经典问题必须知道:
| 问题 | 含义 | 解法 |
|---|---|---|
| 缓存穿透 | 查不存在的数据,全打 DB | 布隆过滤器 / 空值缓存 |
| 缓存击穿 | 热点 key 过期瞬间打 DB | 互斥锁 / 永不过期 |
| 缓存雪崩 | 大量 key 同时过期 | 过期时间加随机值 |
追问:Redis 和 MySQL 数据不一致,以谁为准?
以 MySQL 为准。Redis 是缓存,挂了或数据错了,从 MySQL 重建即可;永远别让缓存成为唯一数据源。
9 场景八:微服务下数据库该怎么拆
场景描述:系统从单体拆成微服务,订单服务、用户服务、商品服务,数据库怎么拆?还共用一个库吗?
考察点:考察微服务架构下的数据库拆分原则——库随服务走。
解答思路:
微服务的核心原则:每个服务拥有自己的数据库,服务之间不直接访问对方的库。
错误做法(反模式):
订单服务 ──┐
用户服务 ──┼──→ 同一个 MySQL 库(多服务共享)
商品服务 ──┘
正确做法:
订单服务 → 订单库(独享)
用户服务 → 用户库(独享)
商品服务 → 商品库(独享)
服务间通过 API/MQ 交互,不碰对方库
好处:服务独立演进、独立部署、故障隔离。代价:跨服务的数据要通过接口或事件获取,原来的 JOIN 没法直接用了。
-- 本示例基于 MySQL 8.0
-- 以前单体里一条 JOIN 搞定的:
-- SELECT o.*, u.name FROM orders o JOIN users u ON o.uid=u.id WHERE o.id=123
-- 微服务下:订单库查订单 → 调用户服务接口拿用户信息,在应用层组装
追问:微服务下跨服务的事务怎么办?
不能用本地事务跨库。要用分布式事务方案(TCC、本地消息表、最终一致),接受"最终一致"而不是"强一致"。
10 场景九:分布式下数据一致性怎么保证
场景描述:微服务拆了库,订单服务写订单、库存服务扣库存、积分服务加积分,要保证它们一致,怎么办?
考察点:考察分布式事务/最终一致性方案。
解答思路:
跨库没法用本地事务,主流方案是最终一致性:
方案:本地消息表(最常用)
订单服务:本地事务里同时写"订单表"和"消息表"
后台任务:把消息表的记录发 MQ
库存服务:消费 MQ,扣库存,ACK
失败重试,保证最终都成功
# 本示例为 sqlite3 演示:本地消息表保证最终一致
import sqlite3
conn = sqlite3.connect(":memory:")
cur = conn.cursor()
# 本地事务:订单和待发消息在同一个库,要么都成功要么都失败
cur.execute("CREATE TABLE orders(id INT)")
cur.execute("CREATE TABLE msg_table(id INT, status TEXT)")
# 本地事务里:写订单 + 写待发消息,原子提交
cur.execute("INSERT INTO orders VALUES(100)")
cur.execute("INSERT INTO msg_table VALUES(100,'PENDING')")
conn.commit() # 这一步原子,订单和消息一定同时落库
# 后台任务扫 PENDING 消息发 MQ,消费成功后改 status=DONE
for row in cur.execute("SELECT id FROM msg_table WHERE status='PENDING'"):
print("待发消息:", row[0]) # 输出 待发消息: 100
conn.close()
上面脚本实跑会输出待发消息 100,后台任务再把它可靠地发出去。
除了本地消息表,常见的分布式一致性方案还有:
| 方案 | 原理 | 适用 |
|---|---|---|
| 本地消息表 | 本地事务写表+后台发 MQ | 最常用、简单可靠 |
| TCC | Try-Confirm-Cancel 两阶段补偿 | 资金类强一致 |
| Saga | 长事务拆多步+补偿 | 跨多服务长流程 |
| 2PC/XA | 数据库层两阶段提交 | 性能差,少用 |
追问:为什么微服务普遍接受最终一致而不是强一致?
因为跨库强一致(2PC)性能差、可用性低。互联网业务大多数场景接受"短暂不一致",用最终一致换可用性和性能。
11 场景十:从小厂到大厂的架构怎么演进
场景描述:一个产品从日活几千涨到几千万,数据库架构该怎么一步步演进?画一条演进路线。
考察点:考察架构演进的节奏感——什么时候该做什么。
解答思路:
演进是被流量一步步逼出来的,不是一步到位:
| 阶段 | 业务规模 | 数据库架构 |
|---|---|---|
| 初创 | 日活几千 | 单库 MySQL,一门数据库 |
| 成长 | 日活几十万 | 读写分离 + Redis 缓存 |
| 扩张 | 日活几百万 | 分库分表 + ES + CK |
| 大厂 | 日活千万+ | 分布式数据库 + 多活 |
-- 本示例基于 MySQL 8.0
-- 演进原则:每一步都是"现有架构扛不住了"才升级
-- 1. 单库 → 扛不住读了 → 加从库读写分离
-- 2. 读写分离 → 扛不住写了 → 分库分表
-- 3. 分库分表 → 跨分片太痛 → 评估分布式数据库
-- 切忌:日活几千就上分布式,运维成本拖死团队
关键认知:架构演进要克制。过度设计和设计不足都是错的,正确的是"在该升级的时候升级",这个判断力比背架构名词重要得多。
追问:为什么不一开始就用最先进的架构?
因为先进架构=高复杂度=高运维成本。早期团队小、业务变化快,简单架构才能快速迭代;等规模真上来了,再逐步演进。
最后总结一句选型心法,面试收尾时说出来很加分:"先让业务跑起来,再让架构撑起来"。数据库选型的成熟标志,不是你能背出多少种数据库,而是你能克制住引入它们的冲动,在合适的规模用合适的工具解决合适的问题。
12 数据库选型与架构速查表
| 业务需求 | 首选数据库 | 为什么 |
|---|---|---|
| 交易/订单/账务 | MySQL | 强事务、ACID |
| 热点数据/会话/计数 | Redis | 内存级快 |
| 全文搜索/多维筛选 | Elasticsearch | 分词、相关度 |
| 海量报表/聚合 | ClickHouse | 列存、向量化 |
| 结构灵活的业务数据 | MongoDB | 文档模型 |
| 跨服务数据同步 | MQ + 本地消息表 | 最终一致 |
| 缓存问题 | 解法 |
|---|---|
| 穿透(查不存在) | 布隆过滤器 / 缓存空值 |
| 击穿(热点 key 过期) | 互斥锁 / 逻辑不过期 |
| 雪崩(批量过期) | 过期时间加随机抖动 |
| 演进阶段 | 触发信号 | 升级动作 |
|---|---|---|
| 单库 | 读压力大 | 读写分离 |
| 读写分离 | 写压力大 | 分库分表 |
| 分库分表 | 跨分片运维痛 | 评估分布式库 |
13 常见坑与注意事项
1. 早期就上分布式数据库:过度设计,运维成本拖死小团队,单库够用就单库。
2. 用 MySQL 做全文搜索:LIKE %xx% 慢且弱,该上 ES 就上。
3. 报表打业务库:一个聚合查询拖垮交易,分析必须隔离到 CK。
4. 微服务共享一个库:破坏服务边界,等于没拆。
5. 缓存当唯一数据源:Redis 只是派生缓存,MySQL 才是真相源。
6. 跨服务用本地事务:跨库事务不成立,要走最终一致方案。
7. 什么都塞 Redis:大对象、稀疏数据撑爆内存,缓存要精选。
8. 一步到位最先进架构:架构要随规模演进,克制是美德。
14 练习题与自测
练习 1:创业公司日活几千,老板要上分布式数据库,你怎么劝?
参考答案:早期单库 MySQL 足够,分布式数据库运维成本高;等单库扛不住了再按"读写分离→分库分表→分布式"逐步演进。
练习 2:商品标题模糊搜索很慢,为什么不优化 MySQL 而要上 ES?
参考答案:LIKE 前导通配走不了索引,且不支持分词和相关度排序;ES 专为全文检索设计,搜索走 ES、主数据留 MySQL。
练习 3:微服务拆库后,订单服务怎么拿到用户服务的用户名?
参考答案:不再 JOIN,订单库查订单后调用户服务接口获取用户信息,在应用层组装;跨服务数据通过 API 或事件交互。
练习 4:缓存穿透、击穿、雪崩分别是什么?怎么解?
参考答案:穿透是查不存在数据打 DB(布隆过滤器);击穿是热点 key 过期瞬间打 DB(互斥锁);雪崩是大量 key 同时过期(过期加随机)。
练习 5(自测):画出一个电商系统从网关到存储的完整数据库分层,并说出每层用什么数据库、为什么。
自测要点:能否说出 Redis 缓存、MySQL 交易、ES 搜索、CK 分析各司其职,且以 MySQL 为真相源。
下一篇预告:第 27 章 模拟面试:初级MySQL工程师
文章回复
0 条公开回复