第 10 章 · 数据库对比与应用

课程最后一章回答一个被问烂但永远真问题:「项目该用什么数据库?」。我的答案先行:默认 MySQL 或 PostgreSQL + Redis 缓存,有确凿的反对理由才换。这个答案背后是九年互联网项目的集体试错,而要讲清楚它,得把前面九章的零件全部调出来对账:事务(第 7 章)、日志(第 8 章)、索引(第 9 章)、范式与反范式(第 5 章)。选型从来不是「哪个数据库好」,是「你的业务愿意放弃前面哪一章的哪个承诺」。

10.1 一张全景图:四类存储各自放弃了什么

类型代表数据模型强项放弃了什么
关系型MySQL、PG表 + 外键ACID、连接、复杂查询水平扩展难(单机写)
KV 内存型Redis键 → 值微秒级延迟大数据量、强持久
文档型MongoDBJSON 文档模式灵活、天然分片连接、跨文档事务(弱)
列式/OLAPClickHouse、HBase列族聚合分析吞吐单行更新、事务

看「放弃了什么」这一列,比看「强项」更有信息量。关系模型是「完全体」:约束、连接、事务、日志、优化器,一应俱全,代价是单机写扩展难(分库分表是杀敌一千自损八百的手艺);每类 NoSQL 都是拿着关系模型的某一块去换专项性能。所以选型的第一步永远是回答:我的核心数据,哪些承诺丢不起? 账务的 ACID 丢不起,购物车的毫秒延迟丢不起,浏览计数连准确都无所谓。

10.2 MySQL 和 PostgreSQL:二选一的实务

两家都是第 7、8 章原理的完全体实现,差异在细节和气质:

  • MySQL(InnoDB):默认 REPEATABLE READ,MVCC + undo 版本链;生态巨大,运维资料、主从复制、分库分表中间件一应俱全;互联网行业的母语。
  • PostgreSQL:默认 READ COMMITTED,更贴 SQL 标准;功能上常年领先(窗口函数、CTE、JSONB、数组类型、全文检索、更强的扩展体系);这几年在新兴团队里声势压过 MySQL。

选择建议朴素到土:团队熟谁、招人容易、周边工具链齐,就用谁。两家在正确性上都是值得托付的,差异远小于「用没用对索引、有没有管好长事务」这类第 9 章问题带来的差距。真要一条硬标准:重度复杂分析查询、需要 JSON 字段做半结构化存储的,PG 顺手;超大并发简单读写、生态内分库分表方案成熟的,MySQL 顺手。

10.3 Redis:内存里的瑞士军刀

Redis 把数据放内存,单个操作微秒级,还附赠一堆精心设计的数据结构,每个都对应一类真实业务:ZSet(跳表实现)做排行榜,INCR 做计数器和限流,Hash 做会话,List 做简单队列,EXPIRE 做验证码过期。它是「加在主库前面的缓存层」的标准答案,经典姿势是 Cache-Aside:

读:先查 Redis,命中直接回;未命中查 MySQL,回写 Redis(带过期时间)
写:先写 MySQL,再删 Redis(下次读时自动回填)

两个必须知道的坑。缓存与库的一致性:写库后删缓存存在时间窗,极端并发下旧值可能回填,资金类数据别依赖缓存,Redis 只放「读得起一次回源」的数据。持久化的诚实边界:RDB 快照 + AOF 追加日志(第 8 章 WAL 的思想)让 Redis 能在重启后恢复大部分数据,但默认配置下「最近一秒」可能丢;缓存丢一秒无所谓,当主存储用就要 appendfsync always(性能大跌),多数人走到这里会重新考虑该不该把 Redis 当主库。

10.4 MongoDB:文档的自由与代价

MongoDB 存 JSON 文档,同一集合的文档结构可以各不相同,嵌套对象直接存进去。这对「结构多变、以聚合读为主」的业务(内容管理、埋点事件、商品详情的动态属性)是真正的生产力:免建表、免迁移、一次读出整个文档,不用四表连接拼装。代价回到第 5 章的话题:没有连接,跨文档的关联查询要自己在应用层拼,或者在文档里冗余(反规范化成为默认姿态);跨文档事务有,但那是近年补的课,代价高、生态习惯不围绕它转。经验法则:你的数据天然是「一份独立的文档」(一个商品、一条日志、一封简历),MongoDB 顺手;你的数据天然是「一张关系网」(订单、库存、用户互相牵连),老老实实关系库。

10.5 决策树:从需求到选型

把判断流程写成树,每条分支挂上前文的章节依据:

核心数据有强一致 + 复杂连接需求?
  ├─ 是 → 关系库(MySQL/PG)          [第 2、7、8 章]
  │       ├─ 读压力大? → 前面加 Redis 缓存        [Cache-Aside]
  │       └─ 单机写到顶? → 先读写分离,再谈分库分表
  └─ 否 → 继续问
        ├─ 追求微秒延迟、结构简单 → Redis(或当缓存)   [第 9 章哈希]
        ├─ 结构多变、免连接的文档 → MongoDB            [第 5 章反规范化]
        └─ 海量分析聚合、写多读少 → OLAP/列式

最后一条比任何选型表都重要的经验:别让选型焦虑代替索引优化。我见过太多次争论「MySQL 还是 MongoDB」的会议,最后发现瓶颈是那条全表扫描的 SQL(第 9 章)和没人管的长事务(第 7 章)。数据库的坑九成在用法,一成在选型,而选型的九成,「关系库 + Redis」都能罩住。

常见错误

  • 拿 Redis 当主存储。内存容量、持久化窗口、单点风险三重隐患,除非数据小且可再生(缓存、计数),否则当加速层。
  • 缓存不过期、不设上限。Redis 是内存数据库,没有过期的缓存就是慢性 OOM;宁可短 TTL + 回源,别赌「这数据不会变」。
  • 以为换数据库能救性能。慢查询的根因(无索引、N+1 查询、长事务)换哪个库都还在;先用第 9 章的方法把现库调好,再谈架构。

练习

  1. 设计:给「短链接服务」选型(读 QPS 极高、写入中等、每条数据极小、允许极小概率丢失)。列出你的组件清单和每条数据的存放位置,说明理由。
  2. 手算:缓存命中率 95%,Redis 命中 1ms、回源 MySQL 50ms,平均读延迟多少?命中率掉到 80% 呢?(算完你就懂为什么缓存策略值得抠。)
  3. 动手:本机装 Redis,用 INCR 实现一个接口限流器(键 rate:{user}:{minute},EXPIRE 60 秒),用 curl 压一下,想想它是怎么做到天然原子的。
  4. 思考:第 7 章的 MVCC、第 8 章的 WAL、本章 Redis 的 AOF,三套机制共同的思想是什么?用一句话总结,再想一个这个思想不适用的反例。