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

第 26 章 数据库选型与架构设计

内容摘要

面试到了中高级,面试官不再问"索引怎么建",而是问"这个业务你怎么选型、怎么设计整体架构"。数据库选型没有银弹——OLTP 用 MySQL、缓存用 Redis、搜索用 ES、分析用 ClickHouse,各管一摊。本篇用 10 个场景,带你建立一套"按业务特征选型"的方法论:从早期别过度设计、MySQL vs NoSQL、搜索与分析选型,到完整架构、分库分表中间件、缓存设计、微服务拆库、分布式一致性,再到架构演进路径。示例基于 MySQL 8.0,学完你能系统回答架构设计题。

📖 本篇简介:面试到了中高级,面试官不再问"索引怎么建",而是问"这个业务你怎么选型、怎么设计整体架构"。数据库选型没有银弹——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工程师

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

文章推广海报

《第 26 章 数据库选型与架构设计》完整推广海报
DISCUSSION

文章回复

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