设想你是一位叫张老板的创业者,正准备开一家大型综合性商场。动工之前,你不会先去选「哪种货架品牌最潮」,而是先想清楚四件事:所有分店的价签能否统一(数据一致性)、顾客是逛得多还是买得多(读写模式)、生意爆满后能不能快速加楼层(扩展能力)、请多少店员交多少水电费(运维成本)。这四件事,就是数据库选型的「经营定位」。 当你在系统设计面试中遇到「请设计一个电商系统」「如何存储海量订单」这类问题时,面试官真正想看的,不是你背下了多少数据库名字,而是你是否有一套像「开商场」一样的选型方法论:先定场景,再定技术;先算容量,再谈架构。本文会用这家「数据商场」的完整规划过程,带你走一遍数据库选型的全流程——从收银台的账本(关系型数据库),到灵活的储物柜(NoSQL),再到门口的展示柜(缓存)、导购台(搜索引擎)、财务室(数据仓库),最后画出整座商场的布局图(混合架构)。

岗位关联

对 AI 从业者而言,数据库选型是系统设计面试的高频考点,也是从「会写 CRUD」走向「能设计系统」的必修课。掌握这套方法论,能让你在架构评审、技术方案设计和技术晋升答辩中建立体系化优势。

目录
  1. 可视化演示
  2. 选型原则:先定商场定位
  3. 关系数据库:收银台的账本
  4. NoSQL 分类:灵活储物区
  5. 缓存设计:门口展示柜
  6. 搜索与数仓:导购与财务
  7. 容量规划:预估客流
  8. 混合架构:商场布局图
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

动态演示数据库选型系统设计的核心机制,通过架构/数据/状态/性能四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

数据库选型系统设计:关系型数 关系型数据库适用场景与限制 NoSQL数据库分类与选型 核心机制可视化 数据库选型系统设计整体架构与关键组件

选型原则:先定商场定位

张老板筹建商场时,最焦虑的不是选哪个货架品牌,而是四件「定位级」的事:所有门店的价签能不能永远一致,顾客来商场是闲逛还是直奔柜台,生意火爆后能不能半年内加盖一层楼,以及每年的人员和水电开销是否扛得住。这四件事恰好对应数据库选型的第一性原理——一致性、读写模式、扩展能力和运维成本。系统设计面试中,几乎所有选型题都从这里出发。

在技术世界里,一致性描述多个数据副本在同一时刻是否保持相同;读写模式决定数据是「读多写少」还是「写多读少」;扩展能力关注能否通过增加机器来分摊压力;成本则包含硬件、人力和故障损失。以电商系统为例,商品详情页是典型的读多写少场景,订单支付则要求强一致性。把这些维度量化,对每个候选方案逐项打分,就能得到一个可比较的综合分。

这个打分过程可以抽象为一个加权公式——每个维度的权重由业务决定,得分乘以权重后累加,总分高者胜出:

张老板拿着四家货架供应商的评分表,圈出了最符合商场定位的一家。先想清楚「商场是什么定位」,再谈「货架买哪家」——选型原则是整座数据商场的奠基石,后面的每一个架构决策,都要回到这份评分表上校准方向。

S=w1C+w2R+w3E+w4M\displaystyle S = w_1 C + w_2 R + w_3 E + w_4 M
通俗解释:这就像张老板给四个候选货架方案打「综合分」。金融系统把「一致性」权重拉到 0.5,内容社区则把「读写性能」和「扩展能力」权重抬高。没有绝对优劣,只有是否匹配定位。
  1. 权重 w代表对每个维度的重视程度,所有权重加起来等于 1,权重越大说明该项越不能妥协。
    w1+w2+w3+w4=1w_1 + w_2 + w_3 + w_4 = 1
  2. 一致性 C多份数据是否总能保持相同,得分越高代表越接近「强一致」,通常对应事务能力强的系统。
    C∈{0,1,2,…,10}C \in \{0, 1, 2, \dots, 10\}
  3. 读写性能 R系统每秒能处理的读写请求量,读多写少和写多读少的打分口径不同。
    R=αR读+(1−α)R写R = \alpha R_{\text{读}} + (1 - \alpha) R_{\text{写}}
  4. 扩展能力 E扩容后吞吐量与扩容前吞吐量的比值,体现水平扩展的收益。
    E=QPS扩容后QPS扩容前E = \frac{QPS_{\text{扩容后}}}{QPS_{\text{扩容前}}}
  5. 运维成本 M单位业务量消耗的运维投入,成本越高得分越低。
    M=C运维C总预算M = \frac{C_{\text{运维}}}{C_{\text{总预算}}}
  6. 加权求和把四个维度的得分分别乘权重后相加,得到方案的最终综合分。
    S=w1C+w2R+w3E+w4MS = w_1 C + w_2 R + w_3 E + w_4 M

关系数据库:收银台的账本

商场最核心的区域永远是收银台。每一笔交易都要开票、盖章、入账,笔笔清楚;如果顾客转账到一半发现余额不足,整笔交易立刻作废,像撕掉一张写错的发票;收银员的账本按「一单一单」的顺序记录,谁买了什么、付了多少钱,随时可以翻查。这台收银台,就是关系型数据库(MySQL、PostgreSQL)——它是交易系统的定海神针。

关系型数据库的核心是 ACID:原子性保证一笔交易要么全部完成要么全部回滚,一致性保证数据始终满足业务规则,隔离性让并发交易互不干扰,持久性保证数据断电不丢。它把数据按行存放,天然适合「订单、用户、账户」这类结构化强事务场景。然而单台收银台总有瓶颈,于是店长想到一个办法:把账本复印一份交给副店长,顾客查询时找副店长,只有记账时才找店长——这就是主从复制。

主从复制是读写分离的基础,但它不是瞬间完成的。店长写完账本后,复印件(binlog)需要经过传输、重放才能在副店长手里生效,这个时间差就产生了复制延迟:

收银台的账本可靠、严谨,但还不够:当订单量突破千万级,一台账本承载不下时,老板会把账本拆成多本,按用户 ID 取模分配——这就是分库分表。它解决了单机瓶颈,却带来了跨库查询与分布式事务的新麻烦。做系统设计时,先考虑缓存与读写分离,分库分表应作为最后一道防线。

t可见=t写主+t传输+t重放\displaystyle t_{\text{可见}} = t_{\text{写主}} + t_{\text{传输}} + t_{\text{重放}}
通俗解释:顾客在「副店长」那儿看到的账目,总是比店长手里的最新账本慢一拍。对账类敏感操作必须走主库,而商品列表这类允许秒级延迟的查询则可以放心走从库。
  1. 写主库耗时店长在正式账本上落笔的时间,通常极小但不可忽略。
    t写主=t磁盘写入+t事务提交t_{\text{写主}} = t_{\text{磁盘写入}} + t_{\text{事务提交}}
  2. 传输耗时账本复印件从主店送往分店的在途时间,受网络带宽和距离影响。
    t传输=Dbinlog 大小带宽t_{\text{传输}} = \frac{D_{\text{binlog 大小}}}{\text{带宽}}
  3. 重放耗时分店把复印件誊写到自己的账本上,受从库磁盘性能制约。
    t重放=Dbinlog 大小从库写入速度t_{\text{重放}} = \frac{D_{\text{binlog 大小}}}{\text{从库写入速度}}
  4. 总延迟从主库写入到从库可读之间的完整时间窗口。
    t可见=t写主+t传输+t重放t_{\text{可见}} = t_{\text{写主}} + t_{\text{传输}} + t_{\text{重放}}

NoSQL 分类:灵活储物区

商场不能只有收银台,还需要各种灵活的储物空间:进门处的寄存柜随取随走,货架上的商品说明书一物一档,仓储区把同款商品整列存放,会员系统则记录着顾客之间的社交关系。这些设计在数据库世界里有一个共同的名字——NoSQL,它们专门解决关系型数据库「不擅长」的问题。

为什么 NoSQL 敢放宽账本级的强约束?因为分布式系统的网络不可能永远畅通。堵车、断网就像商场之间的联络通道偶尔瘫痪,这时店与店之间无法实时对账。这就是 CAP 定理描述的困境:在网络分区发生时,一致性(C)和可用性(A)只能保一个,分区容忍性(P)却是每个分布式系统躲不开的默认项。

NoSQL 按数据模型分为四大类:键值存储(Redis)像一本大字典,适合缓存与会话;文档存储(MongoDB)存 JSON 文档,schema 自由,适合内容管理;列式存储(HBase、Cassandra)按列族组织数据,适合海量日志写入;图数据库(Neo4j)记录节点与关系,适合社交网络与风控反欺诈。张老板把这些储物区安排在商场各处,让每类货物都有最合适的安放位置——代价是,当顾客想跨区域对货时,可能要等上一阵。

C+A+P≤2\displaystyle C + A + P \le 2
通俗解释:商场越大,分店之间「对不上账」的概率越高。NoSQL 放弃强一致,换来的是「分店各自正常营业」的可用性和「随时加开分店」的弹性。
  1. 一致性 C所有节点在同一时刻看到完全相同的数据,对账最严谨但通信代价最高。
    C  ⟺  ∀ 节点 x,y:数据(x)=数据(y)C \iff \forall \text{ 节点 } x, y: \text{数据}(x) = \text{数据}(y)
  2. 可用性 A每个请求都能在合理时间内获得非错误响应,哪怕数据可能不是最新。
    A  ⟺  P(请求在限定时间内有响应)≈1A \iff P(\text{请求在限定时间内有响应}) \approx 1
  3. 分区容忍性 P网络故障时系统仍能继续运行,是分布式系统的必选项。
    P  ⟺  消息丢失时系统不崩溃P \iff \text{消息丢失时系统不崩溃}
  4. 网络分区条件只有发生分区时,C 与 A 才必须二选一;平时三者可以同时满足。
    C+A+P=2(仅当分区发生时)C + A + P = 2 \quad (\text{仅当分区发生时})

缓存设计:门口展示柜

商场门口的展示柜是整个商场最亮眼的位置——把最热销的商品放进去,顾客不用穿越整个商场就能直接拿走。这个展示柜就是缓存,在系统里通常由 Redis 承担。它的读取速度比数据库快几个数量级,但空间有限,只能放「热数据」。

展示柜的成败取决于命中率,也就是顾客有多少比例能直接从柜台上拿走商品:

展示柜也有三个经典隐患:一是商品明明下架了柜里还摆着——缓存与数据库不一致,业界用「写后失效」(先更新数据库再删缓存,或先删缓存再写库)来缓解;二是顾客反复询问一个根本不存在的商品,每次都要白白跑去仓库确认——这叫缓存穿透,用「空值缓存 + 布隆过滤器」拦截;三是所有展示柜在同一秒集体撤换,顾客瞬间全部涌进仓库——这叫缓存雪崩,解决办法是给过期时间加上随机抖动,或引入多级缓存错峰。

展示柜用得好,商场客流翻倍、仓库压力大减;用不好,顾客拿着过期货架上的价格去收银台结账,矛盾反而更多。缓存设计的本质是对「速度」与「一致性」的权衡——它永远不是终点,而是整座商场动线上的第一道加速器。

rhit=N缓存命中N总请求×100%\displaystyle r_{\text{hit}} = \frac{N_{\text{缓存命中}}}{N_{\text{总请求}}} \times 100\%
通俗解释:命中率是缓存的「健康指标」。100 个顾客来问保温杯,90 个直接从展示柜拿走了,命中率就是 90%,后面的仓库和收银台自然轻松。
  1. 命中数请求的数据确实存在于缓存中的次数。
    N缓存命中={请求 r∣r 的数据在缓存中} 的数量N_{\text{缓存命中}} = \{ \text{请求 } r \mid r \text{ 的数据在缓存中} \} \text{ 的数量}
  2. 总请求数一段时间内到达缓存层的全部请求数量。
    N总请求=N缓存命中+N缓存未命中N_{\text{总请求}} = N_{\text{缓存命中}} + N_{\text{缓存未命中}}
  3. 比值命中数占总请求数的比例,衡量缓存的有效性。
    N缓存命中N总请求\frac{N_{\text{缓存命中}}}{N_{\text{总请求}}}
  4. 百分化把比值转为百分比,便于设定监控阈值。
    rhit=N缓存命中N总请求×100%r_{\text{hit}} = \frac{N_{\text{缓存命中}}}{N_{\text{总请求}}} \times 100\%

搜索与数仓:导购与财务

商场规模扩大后,张老板增设了两个幕后角色:一个是导购台,顾客说「我要保温杯」,导购员能瞬间告诉他 3 号货架和 12 号货架都有——这是搜索引擎;另一个是财务室,每天闭店后统一盘点全天营收、分析哪个品类卖得最好——这是数据仓库。

如果没有导购台,顾客想找保温杯,只能让员工到仓库里逐箱翻找,数据库里的 LIKE '%保温杯%' 就是这种低效的全文扫描。倒排索引则完全反过来:它像一本「词 → 货架清单」的目录,提前把「保温杯」这一词条对应到所有包含它的商品文档上。查询时直接翻目录,而不是扫仓库。

当商品文档数量极大时,相关性排序需要量化——词频越高、词越稀有,文档匹配度越高,这就是 TF-IDF 的核心思想:

导购台负责「找得快」,财务室负责「算得清」。数据仓库采用列式存储——同一列的数据紧挨着放,做聚合统计时只需读取相关列,比行式存储快一个量级。业务库的数据通过 ETL(抽取、转换、加载)每天导入数仓,支撑大促报表、用户画像等离线分析。在面试中,回答「如何设计搜索」时先讲倒排索引,回答「如何做报表」时先提数据仓库——这对组合是系统设计中「在线」与「离线」的经典分工。

Score(q,d)=∑t∈qtf(t,d)×log⁡Ndf(t)\displaystyle \text{Score}(q, d) = \sum_{t \in q} \text{tf}(t, d) \times \log \frac{N}{\text{df}(t)}
通俗解释:顾客搜「保温杯」,「保温杯」在某个商品标题里出现 3 次,这个商品的匹配度会显著高于只在详情页末尾出现 1 次的商品;而「杯子」这种常见词会被拉低权重,避免搜索结果全是满大街的通货。
  1. 求和符号把查询中每个词对文档的相关性贡献累加起来,得出最终匹配分。
    ∑t∈q\sum_{t \in q}
  2. 词频 tf词 t 在文档 d 中出现的次数,出现越多代表文档越贴合查询。
    tf(t,d)=词 t 在文档 d 中出现的次数文档 d 的总词数\text{tf}(t, d) = \frac{\text{词 } t \text{ 在文档 } d \text{ 中出现的次数}}{\text{文档 } d \text{ 的总词数}}
  3. 对数项文档总数 N 除以包含词 t 的文档数,再取对数,得到「稀有度权重」。
    log⁡Ndf(t)\log \frac{N}{\text{df}(t)}
  4. 文档总数 N索引库中全部文档的数量。
    N=∣文档集合∣N = |\text{文档集合}|
  5. 文档频率 df包含词 t 的文档数量,越大说明词越常见、区分度越低。
    df(t)=∣{d∣t∈d}∣\text{df}(t) = |\{ d \mid t \in d \}|

容量规划:预估客流

张老板开业前要做一道算术题:商场每天会来多少顾客?平均每人买几件东西?最忙的那个小时会有多少人排队?只有算清楚这些,才能决定备多少台收银机、租多大的仓库。这道题在系统设计面试中有一个标准名字:容量规划。

容量规划最常用的起步公式是 QPS(每秒查询数)估算。假设你有 100 万注册用户,平均每人每天发起 10 次业务请求,按一天 86400 秒平均分布,每秒约 115 次。但真实流量有高峰低谷,大促时可能是平时的 5 倍;为了留出故障转移空间,还要再乘上 20% 的冗余系数:

算出峰值 QPS 后,存储和实例数也随之确定:存储容量 ≈ 用户数 × 每人每日新增数据量 × 数据保留天数;实例数 ≈ 峰值 QPS ÷ 单机支撑 QPS,再乘以冗余系数。面试官问「需要几台机器」时,真正的加分项是展示这条完整的推导链——先算 QPS,再算存储,最后落到机器数。算不清客流的商场,要么少备收银机导致排长队,要么空着仓库白交房租。

QPS峰值=U×R86400×F×(1+S)\displaystyle QPS_{\text{峰值}} = \frac{U \times R}{86400} \times F \times (1 + S)
通俗解释:这是一个「从日活推峰值」的经验公式。先算出平均每秒请求量,再用峰值系数放大到真实尖峰,最后加上冗余缓冲,得到需要支撑的目标值。
  1. 用户数 U系统的注册用户数或日活用户数,通常取 DAU(日活)更贴近真实流量。
    U=DAUU = \text{DAU}
  2. 人均请求数 R每个用户每天发起的平均请求次数,来自业务埋点或历史日志。
    R=日总请求数日活跃用户数R = \frac{\text{日总请求数}}{\text{日活跃用户数}}
  3. 时间换算 86400把「每天」换算成「每秒」,公式成立的前提是请求量均匀分布。
    T=24×60×60=86400 秒T = 24 \times 60 \times 60 = 86400 \ \text{秒}
  4. 峰值系数 F真实业务中最高峰流量与平均值的比值,大促场景通常取 5~10。
    F=QPS峰值日最高日均 QPSF = \frac{QPS_{\text{峰值日最高}}}{\text{日均 QPS}}
  5. 冗余系数 S为故障转移和突发流量预留的余量,一般取 0.2~0.5。
    1+S=1.21 + S = 1.2

混合架构:商场布局图

张老板最终在规划图上画出了整座商场的动线:顾客进门先看展示柜(缓存),找不到东西去导购台(搜索引擎),下单去收银台(关系型数据库),闭店后财务室统一盘账(数据仓库),仓库管理员定时把热销品补到展示柜(异步任务)。没有一层存储是万能的,关键是让每层各司其职。

混合架构就是把多种存储按流量特征组合起来:读多写少的数据用主从复制做读写分离;热数据留在 Redis、冷数据归档到数仓做冷热分离;订单写入先发给消息队列,由消费者异步落库,实现异步解耦;交易走 MySQL、搜索走 Elasticsearch、报表走 ClickHouse,各取所长形成多模组合。

在数据量超过单库承载时,分库分表是混合架构中的关键路由规则。分片的核心是一个「散列 + 取模」运算,决定一条记录该进哪个库:

商场规划的终点不是「选一个最好的货架」,而是「让每个货架都做自己最擅长的事」。混合架构要求工程师具备全局视野:先做容量规划摸清流量,再按访问特征分层,最后用异步与路由把各层串联成一张网。当你在面试中画出这张布局图,并讲清楚每一层的取舍理由时,你已经具备了架构师的核心素养。

库号=hash(主键) mod N库\displaystyle \text{库号} = \text{hash}(\text{主键}) \bmod N_{\text{库}}
通俗解释:这相当于给每位顾客分配一个「常去的分店编号」。用同一个主键做 hash 取模,同一个用户的所有订单永远落在同一家分店,查询时直接去那家分店,不用满城跑。
  1. 哈希映射把主键散列成一个分布均匀的整数。
    h=hash(user_id)h = \text{hash}(\text{user\_id})
  2. 取模运算用哈希值除以库数量取余数,余数就是目标库的编号。
    库号=h mod N库\text{库号} = h \bmod N_{\text{库}}
  3. 库数量总分库数通常取 2 的幂,便于后续扩容时只迁移一半数据。
    N库=2kN_{\text{库}} = 2^k
  4. 均匀分布好的哈希函数让每条记录等概率落入任一库,避免「热库」。
    P(库号=i)=1N库P(\text{库号} = i) = \frac{1}{N_{\text{库}}}

AI 应用场景

电商大促:容量规划 + 缓存 + 混合架构

阶段:系统设计与容量预估

解决的问题:大促流量是平时的 10 倍以上,传统做法是盲目加机器,成本高且容易在峰值瞬间被打垮。AI 时代的系统设计利用历史流量数据预测峰值 QPS,再结合缓存、读写分离和异步解耦,把有限成本用在刀刃上。

具体案例:某电商平台双十一前,用过去 30 天的流量日志训练峰值预测模型,得出大促当天峰值 QPS 约 8 万。据此规划 Redis 集群承载 60% 热点读请求,MySQL 主从分离承接交易写放量,订单异步写入消息队列削峰填谷,最终用 3 台缓存机 + 6 台数据库机扛住了原本需要 20 台的流量。

金融账务:一致性优先的选型决策

阶段:技术选型与风险评估

解决的问题:转账、支付场景对数据一致性要求极高,任何一笔账都不能错。传统系统一刀切使用关系型数据库,但高并发下账务流水查询压力巨大。AI 系统设计通过账务流水的冷热分离,把近 3 个月的热数据留在 MySQL,历史流水归档到列式数仓,既保证强一致又控制成本。

具体案例:某支付公司在账户流水查询场景中,日常读写比高达 40:1。选型时给「一致性」权重 0.5、「读写性能」0.25、「扩展能力」0.15、「成本」0.1,加权后 MySQL + 冷热分离方案胜出。上线后单机 QPS 提升 5 倍,历史流水查询响应时间从分钟级降到秒级。

内容社区:搜索 + 数仓 + 个性化推荐

阶段:数据管道与在线检索

解决的问题:内容社区的搜索和推荐需要同时处理「在线快速检索」和「离线海量分析」。传统 LIKE 查询无法支撑毫秒级搜索,跑一次全量用户行为统计会拖垮生产库。AI 系统用 Elasticsearch 的倒排索引承接关键词搜索,用 ClickHouse 列式数仓做行为聚合,双轨并行互不干扰。

具体案例:某知识社区日活 500 万,用户搜索「AI 入门」时,ES 通过倒排索引在 50ms 内召回 2 万篇相关文章;每晚定时任务把用户点击日志 ETL 进 ClickHouse,次日清晨产出「热门内容榜」推送运营后台。系统稳定运行一年,搜索 P99 延迟保持在 120ms 以内。

Python 演示

python69 行
# -*- coding: utf-8 -*-"""数据库选型评估器:给存储方案打分,并估算峰值 QPS。""" class StorageOption:    def __init__(self, name, scores_map):        self.name = name        self.scores_map = scores_map     def total_score(self, weights_map):        total = 0.0        for dim, weight in weights_map.items():            total += self.scores_map.get(dim, 0) * weight        return total  CANDIDATES = [    StorageOption("MySQL + Redis", {        "consistency": 9, "read_perf": 9, "write_perf": 6,        "scalability": 5, "cost": 7,    }),    StorageOption("MongoDB", {        "consistency": 6, "read_perf": 7, "write_perf": 8,        "scalability": 8, "cost": 6,    }),    StorageOption("HBase + ES", {        "consistency": 7, "read_perf": 5, "write_perf": 9,        "scalability": 9, "cost": 4,    }),]  def recommend(system_name, weights_map, min_score=7.0):    """按权重计算总分,推荐最合适的存储方案。"""    if abs(sum(weights_map.values()) - 1.0) > 1e-9:        raise ValueError("权重之和必须等于 1")    results = []    for opt in CANDIDATES:        score = opt.total_score(weights_map)        results.append((score, opt.name))    results.sort(reverse=True)    for score, name in results:        print(f"{system_name} | {name}: {score:.2f} 分")    best_score, best_name = results[0]    if best_score < min_score:        print("结论: 没有单一方案达标,建议采用混合架构")    else:        print(f"结论: 推荐 {best_name}")    return best_name  def estimate_qps(users, daily_requests, peak_factor, redundancy):    """估算峰值 QPS: 日均请求量换算到每秒,再乘峰值系数与冗余。"""    avg_qps = users * daily_requests / 86400    peak_qps = avg_qps * peak_factor * (1 + redundancy)    print(f"日均 QPS 约 {avg_qps:.0f}, 峰值 QPS 约 {peak_qps:.0f}")    return peak_qps  if __name__ == "__main__":    weights = {        "consistency": 0.2,        "read_perf": 0.35,        "write_perf": 0.15,        "scalability": 0.2,        "cost": 0.1,    }    recommend("某电商商城", weights)    estimate_qps(users=1_000_000, daily_requests=10,                 peak_factor=5, redundancy=0.2)
目的:演示数据库选型的完整决策链——先用加权评分法为候选方案打分,再用容量规划公式估算峰值 QPS,两者结合输出推荐结论。
  1. class StorageOption:定义一个候选方案的「数据模型」,记录方案名和五个维度的得分。
  2. def total_score(self, weights_map):实现加权评分公式:每个维度得分乘以对应权重后累加。
  3. CANDIDATES = [...]内置三个常见方案:MySQL+Redis、MongoDB、HBase+ES,模拟真实选型池。
  4. if abs(sum(weights_map.values()) - 1.0) > 1e-9:校验权重之和必须为 1,防止公式失效。
  5. results.sort(reverse=True)把得分从高到低排序,取第一名作为推荐结果。
  6. avg_qps = users * daily_requests / 86400容量规划第一步:日均请求量除以一天的秒数,得到平均 QPS。
  7. peak_qps = avg_qps * peak_factor * (1 + redundancy)第二步:平均 QPS 乘以峰值系数和冗余系数,得到目标峰值。
  8. print(f"结论: 推荐 {best_name}")输出最终决策,供架构评审使用。

常见误区

误区系统访问变慢就给最热的数据加 Redis 缓存,反正缓存越多越快,加缓存总不会出错。甚至把整个数据库都塞进缓存,觉得这样一劳永逸。

正解缓存像商场门口的展示柜,只适合放「被反复询问的热门款」。把冷数据也放进展示柜,既挤占有限空间,又因数据更新不一致让顾客看到过期价格。缓存的核心指标是命中率——只有当请求热度集中时,缓存才产生正收益;写多读少、强一致敏感的场景,缓存引入的一致性问题反而会让系统更复杂。

为何常见:大家最先感知到「查询慢」,而缓存是效果最直观的加速手段,容易让人忽略它在数据一致性上付出的隐性成本。

误区关系型数据库是上个时代的东西,NoSQL 在性能上全面碾压 SQL。所以选型时直接上 MongoDB 或 HBase,觉得用了 NoSQL 就万事大吉。

正解数据库没有绝对的快慢,只有「场景匹配」。SQL 强在事务与关联查询,NoSQL 强在灵活模型与水平扩展。拿一笔跨账户转账来说,NoSQL 的弱事务可能or导致账不平;但拿海量日志写入来说,SQL 的行存储与强约束反而是累赘。性能对比必须放在具体的读写模式和一致性要求下才有意义。

为何常见:网上流传的 benchmark 大多只测单点读写,而真实业务包含事务、join、一致性约束,实测表现与直觉完全不同。

误区现代分布式系统技术成熟,网络分区时我既想要数据一致,又想要服务可用,CAP 三个属性应该能全部满足。

正解CAP 是分布式系统不可调和的基本矛盾。网络分区不是极端异常,而是每天都会发生的「堵车」。一旦分区发生,你只能在一致性和可用性之间二选一;两者同时满足本质上意味着「系统没有发生分区」或「系统是单机」。成熟系统普遍选择「最终一致」,在 C 与 A 之间找动态平衡。

为何常见:系统在正常运行(无分区)时确实同时满足 C 和 A,人们容易把「平时」当成「定理」,忽略了分区发生的那一瞬间。

误区单库撑不住了就做分库分表,拆完性能翻好几倍,以后再也不用担心容量问题,一劳永逸。

正解分库分表就像把一个商场拆成多个分店,顾客要查跨店订单时必须逐个分店询问,分布式查询、跨库事务、分页排序全部变复杂;而且取模路由一旦上线,未来扩容要迁移大量数据,rehash 过程极其痛苦。业内共识的顺序是:先加缓存,再做读写分离,最后才考虑分库分表。

为何常见:分库分表的效果立竿见影,而拆分后的查询复杂度和运维成本是「延迟显现」的,导致很多团队拆完才发现骑虎难下。

误区数据量大了,直接给生产数据库扩容成数据仓库就行,反正都是存数据的软件,大就是强。

正解数据仓库(OLAP)和业务数据库(OLTP)是两种「物种」。业务库面向每笔交易,行式存储、强事务、低延迟;数仓面向海量历史数据的聚合分析,列式存储、批量导入、高吞吐。在数仓上跑订单扣款会因事务支持不足而出错,在业务库上跑全量行为报表则会把交易系统拖垮。两者是互补关系,不是替代关系。

为何常见:从使用者的视角看,两者都是「存数据的软件」,存储引擎、查询模式等本质差异对外行并不直观。

面试题

Q1. 数据库选型时最优先考虑的因素是什么?

单选题 · easy

  • A.分清交易与分析场景← 正确答案
  • B.使用数据库最新版本
  • C.优先选择开源的产品
  • D.数据库的品牌知名度

答案:A。选型的第一步永远是从业务场景出发。交易系统(OLTP)和分析系统(OLAP)对事务、存储格式、查询模式的要求完全不同。至于版本新旧、是否开源、品牌大小,都属于次要因素。就像开商场要先想清楚定位,而不是先选货架品牌。

Q2. ACID 中的「D」(Durability)代表什么含义?

单选题 · easy

  • A.数据写入持久保存← 正确答案
  • B.数据查询速度极快
  • C.数据库免费开放使用
  • D.数据永久保存不删

答案:A。Durability(持久性)指事务一旦提交,即使系统断电或崩溃,数据也不会丢失——好比商场收银台的账本已经盖章入账,即便当晚停电,第二天账目依然在。查询速度和免费与否都与 ACID 无关,数据永不删除也不是事务层面的承诺。

Q3. Redis 缓存「先更新数据库再删除缓存」策略的主要风险是什么?

单选题 · mid

  • A.高并发窗口读到旧数据← 正确答案
  • B.缓存占用过多磁盘空间
  • C.总请求数变多变慢
  • D.数据库压力反而变小

答案:A。先改库、后删缓存,在删除完成前的极短窗口内,并发读请求可能拿到旧缓存值返回旧数据。这就像商品已经在仓库改价了,但门口展示柜的价签还没来得及换,顾客看到旧价格。解决办法是延迟双删或引入版本号。其他选项并非该策略特有风险。

Q4. 主从复制架构中从库读到旧数据的核心原因是什么?

单选题 · hard

  • A.binlog 重放存在延迟← 正确答案
  • B.从库磁盘空间不足
  • C.主库 CPU 使用率过高
  • D.网络带宽被完全占用

答案:A。主库写入后,binlog(账本复印件)需要经过网络传输到从库,再从从库的 relay log 重放才能生效。这个「传输 + 重放」的时间差就是复制延迟,也是从库读到旧数据的根源。磁盘不足和带宽占满只是加重延迟的因素,不是核心原因。

Q5. 缓存穿透指的是什么现象?

单选题 · easy

  • A.大量查询不存在的数据← 正确答案
  • B.缓存服务器整体宕机
  • C.热点数据集中过期
  • D.数据库连接池被占满

答案:A。缓存穿透指查询的 key 在缓存和数据库中都不存在,导致每次请求都绕过缓存直接打到数据库。好比顾客反复询问一个根本不存在的商品,导购每次都要去仓库翻一遍。解决方案是缓存空值或使用布隆过滤器。缓存宕机属于雪崩,热点集中过期也属于雪崩范畴。

Q6. 相比关系型数据库,NoSQL 数据库的典型特点有哪些?

多选题 · mid

  • A.数据模式结构更灵活← 正确答案
  • B.水平扩展能力更强← 正确答案
  • C.跨行事务支持完善
  • D.强制固定表结构约束

答案:AB。NoSQL 的设计目标就是「灵活 + 弹性」——文档存储允许字段随业务自由变化,键值、列式存储天然支持多节点水平扩展。而完善的跨行事务是关系型数据库 ACID 的强项,固定表结构也正是关系库的特点,这两项不是 NoSQL 的标签。

Q7. 分布式系统发生网络分区时,AP 系统倾向于如何取舍?

单选题 · mid

  • A.保可用性舍弃一致性← 正确答案
  • B.保一致性舍弃可用性
  • C.只保留分区容忍性
  • D.三者同时完全满足

答案:A。CAP 定理指出分区(P)是必选项,A 和 C 只能二选一。AP 系统选择保障可用性——每个分店继续营业,哪怕账目暂时对不上,之后通过异步同步达成最终一致。选项 B 是 CP 系统的取舍,C 不成立,D 正是 CAP 定理否定的命题。

Q8. 分库分表后通常会引入哪些新问题?

多选题 · hard

  • A.跨库事务难以保证← 正确答案
  • B.跨库查询复杂度上升← 正确答案
  • C.磁盘空间占用大减
  • D.数据库并发能力无限

答案:AB。分库分表把数据拆到多个物理库,原本单库内的事务跨越了多个库,分布式事务难以保证;跨库 join、全局排序、分页也复杂度飙升。磁盘占用不会因拆分而减少,数据库并发能力也不会无限提升——分片只是把压力分散,并没有消灭物理瓶颈。

Q9. 下列哪个数据库最适合 OLAP 场景的海量聚合分析?

单选题 · mid

  • A.ClickHouse 列式存储← 正确答案
  • B.Redis 键值数据库
  • C.Neo4j 图数据库
  • D.Oracle 关系数据库

答案:A。OLAP 场景的核心是海量数据上的聚合统计,列式存储按列簇组织数据,只需读取参与计算的列,I/O 开销远小于行式存储。ClickHouse 就是典型的列式分析数据库。Redis 面向缓存,Neo4j 面向关系图谱,Oracle 定位 OLTP 交易场景。

Q10. 一个高并发电商平台中,下列哪些混合架构组合是合理的?

多选题 · extreme

  • A.MySQL 负责交易事务← 正确答案
  • B.Redis 承载热点读流量← 正确答案
  • C.单库扛下全部读写请求
  • D.ES 支撑商品全文检索← 正确答案

答案:ABD。成熟的电商架构必然是多模组合:MySQL 保障订单交易的事务性,Redis 挡住热商品的读流量,Elasticsearch 提供全文检索能力。三者通过异步消息和路由规则串联成整体。而「单库扛下全部请求」恰恰是要避免的架构陷阱,也是混合架构要解决的问题。