# 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)