# 28 · 数据库与存储选型
> 一句话点题:**数据库不是「MySQL 还是 PostgreSQL」这么小的问题,而是「这份数据的读写模式、一致性要求、查询形态、增长速度和失败代价是什么」。选存储,先画数据生命周期;工具名排在后面。**
---
> **🧰 技术栈选型篇第 2 章 · 本章只练一件事**
>
> [05 章](05-数据与状态.md) 说过:系统真正难的不是逻辑,是数据。语言可以换,服务可以拆,但数据一旦放错地方,迁移成本会非常高。本章把数据库、缓存、搜索、对象存储、向量库放到同一张选型地图里看。
---
## 开场:别用一个数据库扛所有问题
很多系统一开始长这样:
```
App ──▶ 一个关系型数据库
├─ 交易数据
├─ 报表查询
├─ 搜索筛选
├─ 文件附件
└─ AI 检索向量
```
MVP 这样做没错。问题是,随着业务长大,这些数据的访问方式完全不同:
- 订单要强一致,不能丢。
- 报表要扫大量历史数据,不能拖垮主库。
- 搜索要相关性排序,不是简单 `LIKE`。
- 图片、视频、附件要便宜存、能走 CDN(内容分发网络)。
- RAG(检索增强生成)要向量召回和权限过滤。
> **架构判断:**不是「哪个数据库最好」,而是「哪类数据,该用哪种存储模型承载」。一个系统常常需要多种存储,但每增加一种,都会增加一致性、同步、运维和排障成本。
---
## 一、先画数据生命周期
选数据库之前,先把一类数据从出生到归档画出来:
```
写入 ──▶ 校验 ──▶ 事务提交 ──▶ 查询/检索 ──▶ 分析/报表 ──▶ 归档/删除
│ │ │ │ │ │
谁写? 怎么验? 要多一致? 怎么查? 多久查一次? 保留多久?
```
然后回答五个问题:
| 问题 | 为什么重要 |
|---|---|
| **写多还是读多?** | 决定是否需要读写分离、缓存、索引模型 |
| **事务边界在哪里?** | 决定能不能用关系型数据库的事务兜住 |
| **查询形态是什么?** | 主键查、范围查、全文搜索、向量相似度,完全不同 |
| **数据增长速度多快?** | 决定分区、冷热分层、归档策略 |
| **错了会怎样?** | 决定一致性、备份、审计、恢复等级 |
这一步和 [07 章](07-从0到1设计一个系统.md) 的信封背面估算是一回事:先算数据量、读写比、保留周期,再谈工具。
---
## 二、主存储:关系型数据库仍然是默认起点
如果你不知道该选什么,大多数业务系统默认从关系型数据库开始:
| 类型 | 适合 | 不适合 |
|---|---|---|
| **关系型数据库**(如 PostgreSQL、MySQL) | 交易、订单、权限、租户、账务、需要事务的数据 | 极大规模分析、全文搜索、海量非结构化文件 |
| **文档数据库**(Document DB,如 MongoDB) | 结构经常变化、文档整体读写、弱关系数据 | 强事务、多表复杂关联、严格报表 |
| **键值存储**(Key-Value,如 DynamoDB、Redis 持久化形态) | 按 key 高速读写、结构简单、超大规模 | 临时复杂查询、灵活关联 |
| **列式/分析数据库**(OLAP,在线分析处理) | 报表、聚合、日志分析、行为分析 | 高频小事务写入 |
> **默认建议:**先用关系型数据库把核心交易数据放稳。等报表、搜索、日志、向量检索真的变成独立压力,再把对应读模型拆出去。不要一上来为了「现代」把每类数据都塞进不同数据库。
---
## 三、读模型:搜索、分析、向量不是主库的附属品
当查询形态开始偏离主库擅长的方向,就要考虑读模型:
| 需求 | 常见存储/引擎 | 关键取舍 |
|---|---|---|
| **全文搜索** | Elasticsearch、OpenSearch、Meilisearch | 相关性强,但索引同步和最终一致要处理 |
| **报表分析** | ClickHouse、BigQuery、Snowflake | 扫描聚合快,但不是交易主库 |
| **对象存储** | S3、OSS、GCS | 存文件便宜可靠,但不能当数据库做复杂查询 |
| **向量数据库**(Vector DB) | Milvus、Qdrant、pgvector | 相似度检索强,但权限过滤、召回质量和成本要评估 |
| **时序数据库**(Time Series DB) | Prometheus、InfluxDB | 指标时间线查询强,但不适合普通业务对象 |
这里最容易犯的错是把读模型当成事实源:
```
正确:
主库 = 事实源(Source of Truth)
搜索/分析/向量库 = 从主库或对象存储同步出来的读模型
错误:
用户改了资料 → 只改搜索索引 → 主库不知道
```
读模型可以落后,但要说清楚**能落后多久**、**怎么补偿**、**怎么重建**。这就是 [11 章](11-数据一致性工程.md) 的一致性工程。
---
## 四、RAG 场景:向量库不是唯一答案
RAG 企业知识库([案例 03 DocuMind](../cases/documind-rag/README.md))很容易被简化成:
```
文档切块 ──▶ 向量库 ──▶ topK ──▶ LLM 回答
```
真实系统更像:
```
原文对象存储 ──▶ 解析/切块 ──▶ 元数据主库
│ │
├────────▶ 关键词索引 ◀────┤
├────────▶ 向量索引 ◀────┤
└────────▶ 权限/租户过滤 ◀──┘
```
选型时要问:
- 权限过滤是在检索前做,还是检索后过滤?后过滤可能召回不够。
- 只做向量召回,还是混合检索(Hybrid Search,关键词 + 向量)?
- 原文和引用存在哪里?向量库不应该是唯一事实源。
- 索引坏了能不能从原文和元数据重建?
- 成本能不能随文档量和查询量线性增长?
> 向量库解决的是「相似度召回」,不是权限、事实源、引用、评测的全部问题。不要把一个 RAG 系统压扁成一个库。
---
## 五、什么时候该拆存储
拆存储的触发信号要具体:
| 信号 | 说明 | 可能动作 |
|---|---|---|
| 报表查询拖慢交易库 | OLTP(在线事务处理)和 OLAP(在线分析处理)互相干扰 | 同步到分析库 |
| 搜索相关性差、LIKE 扫描慢 | 查询形态变成全文搜索 | 建搜索索引 |
| 附件/图片撑爆数据库 | 二进制文件不该塞主库 | 迁到对象存储 + CDN |
| 单表增长导致索引和备份变慢 | 数据生命周期没有分层 | 分区、归档、冷热分离 |
| RAG 召回质量不稳 | 单一向量召回不够 | 混合检索 + 重排 + eval |
也要记住反面:
> 如果当前数据量小、团队少、故障代价低,一套关系型数据库 + 合理索引 + 定期备份,往往比过早引入五种存储更健康。
---
## 六、写一条存储选型 ADR
```md
### ADR-028:报表查询从交易库拆到 ClickHouse
- 背景:工单列表和状态流转依赖主库,但月度报表开始扫描 2 亿行历史事件,导致交易库 P99 从 120ms 升到 900ms。
- 选择:主库继续作为事实源;通过 Outbox 事件同步到 ClickHouse,报表只查分析库。
- 放弃:放弃报表的强实时性,允许 1 分钟以内延迟。
- 换来:交易链路与报表链路隔离,报表聚合性能提升,主库负载稳定。
- 复审条件:如果报表必须秒级实时,或同步延迟超过业务可接受范围,重新评估流式同步和预聚合。
```
这条 ADR 的关键是:它没有说「ClickHouse 很快」,而是说清楚了**哪个读模型拖垮了哪个事实源**。
---
## 🎯 随堂检验
---
## 本章小结
- **选存储先画数据生命周期**:写入、校验、事务、查询、分析、归档,每一步都有约束。
- **关系型数据库仍是多数业务系统的默认事实源**:先稳住核心交易数据,再拆读模型。
- **搜索、分析、向量库是不同查询形态的读模型**:它们强在特定查询,弱在事务和事实源。
- **RAG 不是一个向量库**:还包括原文、元数据、权限、混合检索、重排、引用和 eval。
- **拆存储要靠触发信号**:报表拖垮主库、搜索变慢、附件撑爆库、召回质量不稳,这些才是升级理由。
> **承上启下**:主存储解决「事实放在哪里」。但系统一旦有热点、洪峰、异步协作,就会遇到缓存、消息队列和事件系统。下一章 [29 · 缓存、消息队列与事件系统选型](29-缓存消息队列与事件系统选型.md),我们专门讲这些「很有用,也很容易被误用」的中间层。
---
## 相关链接
- 方法论本体:[05 · 数据与状态](05-数据与状态.md) · [06 · 质量属性与取舍](06-质量属性与取舍.md) · [11 · 数据一致性工程](11-数据一致性工程.md)
- 演进配套:[13 · 规模化的力学](13-规模化的力学.md) · [14 · 演进与拆分大型系统](14-演进与拆分大型系统.md)
- 案例对照:[DocuMind:企业 RAG 知识库](../cases/documind-rag/README.md) · [StarArena:演唱会抢票系统](../cases/stararena-ticketing/README.md)