一本实用指南:从读/写路径、延迟、一致性与增长需求出发选择数据库,避免让潮流制造可以避免的技术债务。

因为某个数据库“流行”就去选它,像是因为大家都在谈论一款车就去买——却不去考虑你到底需要踏板车、皮卡还是大巴。潮流反映的是别的产品、团队规模、预算和风险承受力下的成功。你的数据库必须契合你的工作负载:你的应用每天实际上在做什么。
工作负载是系统在生产中的真实行为:
这些行为就是你的访问模式——应用触及数据的可重复方式。如果你能清晰描述访问模式,数据库选择就不会那么神秘了。
没有一种方案能通吃。很多成功系统采用混合策略:一个数据库负责事务,另一个负责分析,有时还会单独使用搜索引擎或缓存。这并不是“为了复杂而复杂”——而是承认不同的访问模式受益于不同的存储与查询引擎。
在比较“SQL vs NoSQL”或追逐时髦之前,把你排名前 5–10 的读写写下来。从那里开始,其他都是细节。
访问模式是对应用每天如何触及数据的实用描述:它读什么、写什么、多频繁、速度如何以及以何种形态查询。它更关心你用数据做什么(“每分钟按 ID 查 10,000 次订单”或“扫描上个月所有订单来生成报告”),而不是数据本身是什么(“订单”或“用户”)。
大多数读取流量落在几类可识别的桶里:
社交 feed 是混合读取形态的好例子:可能对资料做点查,对“最新帖子”做范围读,对计数做聚合。
写入模式同样重要:
日志常常是“写多、几乎不改”的(大量插入、很少更新)。订单通常是“先写入再更新”(创建后状态变更)。
许多产品想把所有需求都放在一个系统里:为应用提供快速点查、为客服提供复杂查询、为分析提供大规模扫描。一个数据库可以处理某些混合场景,但某些组合会相互冲突——例如,重度分析扫描会拖慢驱动结账或 feed 的低延迟小查询。
当你能清楚命名访问模式时,就能根据真实行为而不是流行度评估数据库。
在对比数据库品牌前,先明确你实际服务的工作负载。大多数产品并非“单一工作负载”——而是几个共存(有时互相竞争)的不同工作负载。早期把分类做对,可以避免把数据库强行放到它不擅长的岗位上。
OLTP 是大多数应用的日常节奏:许多小型读写、并发用户众多、请求需要快速完成。
想象场景:“更新购物车”、“创建订单”、“更改地址”、“检查库存”。这些操作短小、有针对性,并且对正确性敏感。如果一次支付被记录却又消失,或两个用户拿到同一座位,后果严重。
OLTP 通常倾向于能很好处理高并发并提供事务与数据完整性保证的系统。
分析改变了工作的形态:查询更少,但每次触及的数据更多。
想象场景:“上季度各地区营收”、“渠道转化”、“每类热销产品”、“日活趋势”。这些查询常常扫描大量行、group、聚合并排序。延迟期望可以更宽松(几秒可能没问题),但重度扫描的成本会显著——尤其当仪表盘全天运行时。
如果尝试在驱动结账的同一系统上运行 OLAP 风格扫描,通常会导致其中一个受影响。
时序与日志通常是追加为主:新事件不断到达,查询通常按时间范围进行。
想象场景:指标、点击流、设备遥测、审计日志。常见需求包括保留策略(删除/过期旧数据)、汇总(保留原始事件 7 天、聚合 12 个月)以及在峰值时的快速写入。
这个工作负载更关注高效摄取大量带时间戳的记录,并随时间使存储可预测,而不是复杂联表。
搜索不仅仅是“找行”。它是文本匹配、相关度排序、部分匹配与用户友好过滤。
想象场景:按关键词搜索商品、按短语查找工单、按分面(品牌、价格范围、颜色)过滤并按“最佳匹配”排序。这些通常需要专门的索引与查询能力,通用数据库可以近似但很少卓越。
如果搜索是核心产品功能,就应从一开始把它视为独立的工作负载,而不是“以后再加”的细节。
性能不是一个数字。两种都被称为“快”的数据库,用户与运维体验可能截然不同。要做出正确选择,请把人感知的东西(延迟)和系统必须持续承受的压力(吞吐量)区分开,然后用突发流量来检验假设。
延迟 是单次请求的耗时——“点了按钮,得到结果”。用户直接感知延迟。
吞吐量 是每秒能处理多少请求——系统能承受的流量总量。
有的数据库通过批处理高效完成工作以获得高吞吐,但单次请求可能有明显延迟;另一种优化点读很快,但当大量写入同时到达时会吃力。
平均延迟会掩盖痛点。如果 99 次请求在 50 ms 完成,而 1 次花 2 秒,平均 看起来不错——但那 1% 会成为“这个应用很慢”的时刻。
这就是 P99 延迟 的意义:最慢 1% 请求所需时间。对面向用户的关键功能(结账、登录、搜索结果),P99 往往决定数据库设计是否让人感到可靠。
大多数系统不是在平均流量下失败,而是在峰值时失败:营销邮件、突发新闻、发薪日、月末报表等。
突发流量会改变数据库的讨论:
缓存可以让读多的工作负载看起来更小——直到发生未命中或缓存清空。
如果大多数读取命中缓存,你的数据库可能主要负责写入与偶发昂贵读取。这会倾向于不同的选择,而不是所有读取都落库的场景。要为“冷缓存”事件和未命中尾延迟做计划,而不是只看理想路径。
选择数据库不仅关乎速度,还关乎哪些可以出错、能容忍多少停机,以及用户分布在何处。
先明确哪些数据必须每次都正确。支付、账户余额、库存计数是典型例子。如果顾客被重复扣款或出现超卖,代价不仅是性能问题——还会带来退款、支持工单和信任损失。
对于这些关键数据,通常希望强一致性:写入被确认后才视为完成,读者不应看到半成品更新。代价是更少的灵活性:某些扩展策略更难,跨区写入可能更慢。
接着,决定如果数据库不可用 5 分钟会发生什么。
如果停机会导致“订单停止、收入中断”,你需要更高的可用性:自动故障转移、良好备份和无需下线的维护计划。如果停机会只影响“内部仪表盘延迟”,你可以接受更简单的方案。
更高的可用性通常提高成本与运维复杂度(更多副本、更密集的监控、更小心的升级)。关键是把投入与业务影响匹配起来。
如果用户主要集中在一个区域,把数据放在一个地方通常更便宜且更快。如果用户分布在全球或有数据驻留合规要求,你可能需要多区域复制。
多区域设计提升用户体验与弹性,但也会带来艰难选择:允许略微陈旧的读取,还是接受更慢的写入以保持同步?正确答案取决于你的工作负载能容忍多少陈旧性。
大多数“数据库争论”其实是围绕查询形态。如果你知道应用必须提出哪些问题——联表、聚合、过滤、时间窗——通常能很快缩小数据库选项。
当你需要跨多个实体灵活过滤与联表(客户 → 订单 → 商品),且需求会演进时,关系模型很有优势。若产品需要临时报表(“显示同时买过 X 且退过 Y 的客户”),SQL 与联表在长期会更简单。
如果你的查询可预测且主要按主键读取(“按 user_id 获取资料”),文档或键值模型通常表现良好——通过把常读数据聚在一起。但代价是可能需要复制数据以避免联表,这会把复杂性转移到写入和更新上。
索引就是你告诉数据库“这些是我的访问模式”的方式。一个在 mockup 看起来不错的查询,如果在非索引字段上过滤或排序,就会变慢。
一个实用规则:每个频繁的过滤、排序或联表键都应该有索引计划。但索引不是免费的:它们占用存储并增加写入负担。
“快速写入”的宣称常常忽略写放大——由二级索引、压缩、复制或去重数据更新引起的额外工作。为了优化读取而增加索引或复制文档的设计可能悄无声息地把高写入工作负载变成瓶颈。
无模式并不等于无结构。灵活模式加速早期迭代,但如果没有约定,会产生字段不一致、难以调试的查询和昂贵的迁移。当你预计会有多个团队、众多功能或长期保留时,更严格的模式与清晰约束往往能降低总体成本——即便一开始看起来较慢。
因为某个数据库流行而选择它,常常会在运营和账单的灰色地带付出代价:维持运行、保证安全、每月付款。两种数据库可能满足相同功能,但在运维工作量和总成本上差别巨大。
早点问清楚谁会在凌晨 2 点操作这个系统。备份、时间点恢复、升级、打补丁、演练故障恢复与监控不是“以后再说”的任务——它们决定了风险与人员配置。
托管服务可以减少重复劳动,但不会完全消除。一些系统需要定期压缩、精细调优或深厚专业知识才能避免性能下降。另一些让模式变更困难,或需要专门的迁移流程。如果团队小,一个易于运维的数据库可能比纸面上“完美”的选择更好。
数据库成本通常来自:
对写密集且带二级索引的访问模式,即使数据集小也可能放大 I/O 与存储成本。
专有查询语言、独特一致性特性或无服务器“魔法”可以加快交付,但也可能限制未来迁移。考虑是否能导出数据、在本地做测试或在不改写应用的前提下更换提供商。
至少确认传输/静态加密、密钥管理、审计、访问控制与保留策略。合规需求常常决定某方案是“可行”还是“不可接受”,无论它有多么时髦。
一旦描述清楚访问模式(你读什么、写什么、多频繁、在何种突发下),“正确”的数据库家族通常会变得清晰。目标不是挑最流行的工具——而是选择在你的工作负载下保持正确性的最简单系统。
当你需要强一致性、明确关系与可靠事务时(订单、支付、库存、权限、调度),选择关系型数据库。如果你经常需要跨实体查询(“在 30 天内有未结发票的客户”)或强制约束(唯一邮箱、外键),SQL 往往能减少应用端复杂度。
一个常用启发式:如果团队准备在代码里重写联表、约束与事务,通常应选择关系型数据库。
当你大多读写整个可变结构的对象(用户资料、内容页面、带可选字段的产品目录或设置)时,文档数据库最合适。如果典型查询是“按 user_id 获取资料”并局部更新,文档能把常用数据放在一起。
当查询变得高度关系化(跨文档多实体查询)或需要多实体事务保证时要小心。
键值系统适合缓存、会话、速率限制、特性开关与短期状态,访问模式是“按键 get/set”且延迟关键。它们通常作为辅助组件,而非主记录存储。
如果用于持久化业务数据,要考虑驱逐、重启或复制延迟时会发生什么。
对于分析——仪表盘、留存、营收汇总、需要对大量历史做 group-by 的查询——列式/仓库系统胜出,因为它们针对扫描与聚合做了优化。
实用拆分:把 OLTP 写入保留在主库,定期把数据送入仓库做报表。这样可以避免 BI 工作负载拖慢面向客户的查询。
许多成功产品并不是“选一个数据库”。它们把每个主要访问模式映射到最简单的存储上,即便这意味着并行使用两到三个数据库。
在线商店常见三类截然不同的工作负载:
产品感觉是统一的,但存储按访问模式专业化。
B2B SaaS 工具可能把核心实体(项目、发票、工单)放在事务数据库,但仍需处理:
IoT 平台摄取突发遥测,然后按时间窗口在仪表盘中读取。
常见拆分:近期数据用快速摄取存储,长期用更便宜的长期存储保留,聚合用分析引擎。
要点:当访问模式分歧时,不同组件可以(也应当)使用不同数据库。
数据库不匹配通常会表现为一堆看似“小”的补救措施。如果团队更多时间在和数据库“搏斗”而不是构建产品功能,要注意——这通常是访问模式的问题,而不是调优问题。
常见警告信号包括:
如果数据库需要英雄式的努力来支撑日常业务,说明工作负载与数据库家族可能不匹配。
因为流行而选库,代价常常在后期到来:
账单会在规模上升或需求变化时到来,而唯一现实的修复往往是痛苦的重构。
你不需要完美的可观测性,但需要几个信号:
写下顶级访问模式(读/写、关键查询、峰值速率)、数据量假设与“不可谈判项”(一致性、可用性、地域约束)。附上仪表盘链接和最差查询示例。这个简短记录让未来决策更快,也能清楚指出何时数据库已不再匹配现实。
把选数据库当成需求收集而非流行竞赛,用这个清单把“需要可扩展”模糊表述转成具体对比输入。
先用白话回答,再尽量填入数字:
做一页纸的表格,左侧列出标准,顶部列出候选数据库。把每个标准标为 必须 或 可选,然后给每个数据库打分(例如 0–2)。
至少要包含:查询匹配度、扩展方式、一致性需求、运维工作量、生态/工具链与成本可预测性。
用代表性数据和真实查询来测试,而不是玩具示例。重现“前 5 个查询”和现实的写入模式(包括峰值)。
如果你需要快速验证产品想法,像 Koder.ai 这样的 vibe-coding 环境能帮你快速搭出工作应用并尽早验证访问模式:生成 React 前端和 Go + PostgreSQL 后端,建模几个真实端点,并在承诺长期架构前测量“前 5 个查询”的表现。能导出源码并控制模式与迁移也能帮助避免把自己套死。
事先写明“通过”意味着什么:延迟目标、可接受的错误率、所需运维步骤(备份、模式变更)以及预估在预期使用下的月成本。如果候选项在 PoC 中达不到必须项,就尽早淘汰并继续下一候选。
面向未来不是在第一天就选最“可扩展”的数据库,而是做出让你在访问模式变化时仍保持灵活的明智选择。
如果工作负载主要是事务性读写且查询直白,关系型数据库通常是最快上手并可靠的路径。目标是有信心发布:可预测性能、清晰的正确性保证和团队已熟悉的工具链。
这里的“面向未来”是避免早期做出不可逆的承诺——比如在未证明需要前就采用专门化存储并承担其权衡。
构建明确的数据访问层(或服务边界),使应用其余部分不依赖数据库特有的怪癖。把查询逻辑集中、定义契约(输入/输出),并把模式变更视作常规开发的一部分。
一些实用习惯帮助未来迁移:
许多产品最终需要两条路径:面向日常事务的 OLTP 与面向报表/实验的分析平台。当分析查询开始影响生产延迟,或需要不同的保留/分区策略时就应拆分。
为保持一致,标准化事件/数据定义、自动化管道,并在系统间对账(日汇总等)以防“真相”碎片化。
如果你想要具体的下一步,搭建一个轻量的迁移计划模板供团队复用:/blog/database-migration-checklist.
访问模式是你的应用在生产中触及数据的可重复方式:它读/写什么,频率如何,响应速度如何,以及以何种查询形态访问(点查、范围扫描、联表、聚合、时间窗口等)。比起“我们有用户和订单”这样的描述,访问模式更能直接映射到索引、模式设计和数据库匹配。
因为“流行”反映的是别的团队的约束和背景,而不是你的。相同的数据库对某些工作负载(例如 OLTP)可能很合适,但对另一些(如大量分析扫描)则很痛苦。先列出你最重要的5–10 个读写操作,再根据这些行为评估数据库,而不是根据品牌热度。
先写下:
这会成为比较选项时的需求文档。
OLTP 是许多小而并发的、对正确性敏感的操作(结账、库存更新、账户变更),事务和约束很重要。
OLAP/分析 是较少但触及大量数据的查询(扫描、group-by、仪表盘),秒级延迟通常可接受,但大量读取代价高。
将两者混在同一系统上运行,分析扫描常常会影响用户交互的延迟。
关注 p95/p99 延迟,不要只看平均值。如果最慢 1% 的请求需要数秒,用户依然会觉得应用不可靠,即便平均看起来不错。
实用建议:为关键端点(登录、结账、搜索)单独跟踪 p95/p99,并将峰值与数据库指标(锁、复制延迟、I/O 饱和)关联起来。
当需求互相冲突时经常适用:
使用专门的存储引擎往往比把一个数据库逼成万金油要简单得多。
缓存可以使读取负载看起来变小——直到发生缓存未命中或缓存清空。
缓存能暂时掩盖问题,但也可能导致在未命中潮水般涌来时使数据库崩溃。
强一致性意味着你需要关于事务与更新可见性的保证(不要出现“半写入”状态)。对支付、余额、库存、预订等数据尤其重要。
代价可能包括:
定义哪些数据是“绝不可错”的,哪些可以容忍一定程度的陈旧性。
索引是工作负载与数据库之间的性能契约。为常见的:
做索引规划。但索引会占用存储并使写入变重(写放大)。目标是为实际频繁使用的操作建索引,而不是一切都索引。
把 PoC 当成一次小型的生产演练:
如果候选项在 PoC 中不能满足必须项,就及早淘汰它。