跳到正文
MySQL、PostgreSQL、Redis 与 MongoDB:从工作负载到恢复目标的选型指南
MySQL、PostgreSQL、Redis 与 MongoDB:从工作负载到恢复目标的选型指南

MySQL、PostgreSQL、Redis 与 MongoDB:从工作负载到恢复目标的选型指南

不按 QPS 或流行度给数据库排队,而从数据模型、一致性、查询、扩展、RPO/RTO 和团队能力比较 MySQL、PostgreSQL、Redis 与 MongoDB,并分析 GitHub、Discord 的公开实践。

数据库选型不是给产品排总分。脱离数据形状、查询路径、写入冲突、硬件、索引和持久化参数讨论“谁更快”,结论通常没有迁移价值。本文比较的是边界,并给出可以验证的决策过程。

一、先把四种产品放回各自的位置#

产品首要模型适合承担的责任最常见的误用
MySQL(InnoDB)关系模型、SQL通用事务系统、成熟 Web 业务、明确关系与约束因为熟悉就忽略复杂查询、分片和 schema 演进成本
PostgreSQL关系模型、SQL,扩展类型丰富事务系统、复杂查询、JSONB、GIS、全文和扩展能力把所有搜索、分析、队列和向量需求都塞进一个实例
Redis内存数据结构缓存、限流、会话、短队列、计数和协调把“支持持久化”误解为可以直接替代核心账本
MongoDBBSON 文档聚合边界清楚、结构可演进的文档数据把所有关系内嵌,最终得到无界文档和大规模重复数据

最稳妥的默认起点通常不是“每种数据库都上一套”,而是一个主数据库。只有新的访问模式经过测量,确认主库难以经济地满足时,才增加缓存、搜索或文档存储。每增加一种数据库,就增加一套备份、监控、升级、权限、驱动和一致性故障模式。

二、选型前先写一份工作负载护照#

至少回答下面这些问题,再谈产品名称:

数据与约束#

  • 核心实体是什么,实体之间有哪些一对一、一对多和多对多关系?
  • 哪些约束必须由数据库保证,例如余额不能为负、订单号唯一、外键必须存在?
  • 一次原子操作跨多少条记录、多少个集合或多少个分片?
  • 文档字段是真的不稳定,还是团队尚未完成数据建模?

访问模式#

  • 前五条读查询和写命令是什么?过滤、排序和聚合字段有哪些?
  • 读写比是多少,写入是追加、更新热点行,还是批量导入?
  • 延迟目标是 p95 还是平均值?峰值持续多久?
  • 查询是否需要任意 JOIN、地理计算、全文、JSON 内部字段或时间范围扫描?

可靠性#

  • RPO:故障时最多允许丢多少数据?
  • RTO:多长时间内必须恢复服务?
  • 是否允许读到旧数据?客户端重试会不会造成重复扣款或重复创建?
  • 备份恢复到隔离环境需要多久,最近一次演练是什么时候?

运维条件#

  • 团队是否会阅读执行计划、处理锁等待、复制延迟和磁盘增长?
  • 能否承担副本集、分片、Sentinel/Cluster 或连接代理?
  • 云托管服务能否满足地区、合规、版本和成本要求?
  • 三年后迁移数据与业务逻辑的代价是多少?

没有这份护照,“MongoDB 适合海量写”“Redis 十万 QPS”“PostgreSQL 事务最强”都只是无法验收的口号。

三、数据模型决定大部分长期成本#

MySQL 与 PostgreSQL:把约束留在数据旁边#

关系数据库适合需要稳定身份、引用完整性和跨实体事务的数据。例如订单系统可以让数据库直接阻止孤立订单项和重复幂等键:

CREATE TABLE orders (
id BIGINT PRIMARY KEY,
idempotency_key VARCHAR(80) NOT NULL UNIQUE,
customer_id BIGINT NOT NULL,
status VARCHAR(24) NOT NULL,
total_cents BIGINT NOT NULL CHECK (total_cents >= 0)
);
CREATE TABLE order_items (
order_id BIGINT NOT NULL REFERENCES orders(id),
line_no INTEGER NOT NULL,
sku VARCHAR(64) NOT NULL,
quantity INTEGER NOT NULL CHECK (quantity > 0),
PRIMARY KEY (order_id, line_no)
);

应用校验仍然有价值,但数据库约束覆盖所有写入者,包括后台任务、修复脚本和未来服务。

MySQL 8 与 PostgreSQL 都支持事务、窗口函数、CTE、JSON、复制和成熟索引。真正的差异通常出现在细节:

  • MySQL InnoDB 的主键是聚簇索引,二级索引叶子保存主键值;主键宽度会影响所有二级索引。
  • PostgreSQL 以堆表和独立索引为基础,MVCC 产生的旧行版本需要 VACUUM 管理;可见性映射也影响 index-only scan。
  • PostgreSQL 原生提供数组、range、JSONB、GIN、GiST、BRIN 等能力,并通过 PostGIS、pgvector 等扩展拓宽用途。
  • MySQL 拥有广泛的托管、运维和应用生态;Vitess 等项目展示了 MySQL 在大规模分片下的工程路径。

这些差异要映射到具体查询和团队能力,不能压缩成“简单业务选 MySQL,复杂业务选 PostgreSQL”。

MongoDB:围绕一起读取和一起更新的数据建模#

MongoDB 的设计单位是文档。应当内嵌还是引用,取决于数据是否具有共同生命周期、是否经常一起读取,以及数组是否会无界增长。

一篇文章内嵌少量固定元数据通常自然;把所有评论永远塞进文章文档则会导致文档持续膨胀、更新争用和生命周期耦合。更合适的模型可能是:

// articles:文章本体与有界元数据
{
_id: ObjectId("..."),
title: "...",
authorId: ObjectId("..."),
tags: ["database", "architecture"],
stats: { commentCount: 128 }
}
// comments:独立增长、独立分页
{
_id: ObjectId("..."),
articleId: ObjectId("..."),
authorId: ObjectId("..."),
body: "...",
createdAt: ISODate("...")
}

MongoDB 可以使用 schema validation,也支持单文档原子写和多文档事务。它的优势不是“不要 schema”,而是允许围绕聚合边界设计 schema,并按版本演进。跨文档事务越频繁、跨集合关系越复杂,就越应该重新评估关系模型是否更自然。

Redis:先选数据结构,再设计键空间#

Redis 不只是字符串缓存。Hash、Set、Sorted Set、Stream、Bitmap 和 HyperLogLog 对应不同访问模式。但它没有关系数据库那样的任意查询能力,键设计本身就是索引设计。

例如排行榜适合 Sorted Set,因为“按分数排序并取区间”是数据结构原生能力;如果需要按地区、年龄、时间和多种业务字段任意组合查询,继续堆键会把索引一致性转嫁给应用。

Redis 的许多命令在单个 shard 的命令执行路径上串行完成,因此单条命令具有清晰原子边界;这不代表整个 Redis 系统“只有一个线程”,也不代表长命令不会阻塞其他请求。大 Key、全量扫描、耗时 Lua/Functions 和慢网络输出仍然需要治理。

四、事务与一致性:不要只比较是否支持 ACID#

MySQL InnoDB#

InnoDB 支持 ACID、MVCC、行锁和外键,默认隔离级别通常是 REPEATABLE READ。范围更新和唯一性检查可能涉及 next-key lock;“写不同的行一定不互相阻塞”并不成立,索引选择和访问顺序会影响锁范围与死锁。

工程上要明确:

  • 事务内是否包含网络调用或用户等待。
  • 所有并发路径是否按相同顺序锁定记录。
  • 死锁后客户端是否能安全重试。
  • binlog、复制模式和 failover 是否满足一致性目标。

PostgreSQL#

PostgreSQL 默认隔离级别通常是 READ COMMITTED,也提供 repeatable read 与 serializable。Serializable 使用可串行化快照隔离检测危险结构,事务可能因序列化失败而需要重试。更高隔离级别不是一个无需代价的“更强开关”。

PostgreSQL 的 MVCC 允许读写并发,但旧版本清理、长事务、表膨胀和 transaction ID 生命周期成为重要运维主题。VACUUM 不是可选的定期优化,而是系统正常运行的一部分。

MongoDB#

MongoDB 单文档写入原子;多文档事务可覆盖副本集和分片集群,但事务会延长资源占用并增加协调成本。更成熟的用法是先把必须一起更新的数据建模到同一文档,再把多文档事务留给确实跨聚合的业务,而不是用事务修补任意拆分的 schema。

Read concern、write concern 和 read preference 共同决定客户端看到的数据与确认边界。只写一个 majority 标签不足以解释故障语义,还要结合拓扑、超时和重试行为。

Redis#

单条命令通常是原子的;MULTI/EXEC 将命令顺序执行,但不像关系数据库那样在运行时错误后自动回滚。乐观并发可以使用 WATCH,多步原子逻辑可使用 Lua 或 Redis Functions,但跨 Cluster slot 操作受分片边界限制。

如果业务要求“余额扣减、账本落库、审计记录”共同满足持久事务,Redis 不应因为命令快就成为唯一事实来源。它更适合保存可重建派生状态,或在明确持久化与故障模型后承担有限范围的数据责任。

五、索引与查询:写出访问路径再建索引#

不要为所有字段建索引。每个索引都增加写放大、缓存占用、备份体积和维护成本。

系统需要重点理解的索引行为
MySQL InnoDB联合索引左前缀、覆盖索引、二级索引回表、主键宽度
PostgreSQLB-tree、GIN/GiST/BRIN、partial/expression index、统计信息与 bitmap scan
MongoDBcompound/multikey/partial/TTL index、字段顺序、排序覆盖与 shard key
Redis键与数据结构就是主要访问路径;二级查询通常要自行维护或使用相应模块

PostgreSQL JSONB 与 MongoDB 文档不是同一选择题#

JSONB 适合关系实体中一部分稀疏或可扩展属性,同时保留 SQL、JOIN 和约束;MongoDB 适合整个聚合以文档为主要读写单位。两者都能索引 JSON 内部字段,但更新语义、存储布局、事务边界和扩展方式不同。

同样,不要因为 PostgreSQL 支持全文、向量和地理扩展,就默认一个实例同时承担 OLTP、搜索、RAG 和分析。扩展减少系统种类的收益,必须与资源隔离、备份时间和故障半径一起计算。

用执行计划代替猜测#

关系数据库使用 EXPLAIN / EXPLAIN ANALYZE,MongoDB 使用 explain(),Redis 使用 SLOWLOG、latency diagnostics 和命令统计。验证时关注:

  • 估算行数与实际行数是否偏离。
  • 扫描了多少数据才返回多少结果。
  • 排序和聚合是否落盘。
  • 命中索引后是否仍发生大量随机回表。
  • p95/p99 是否受锁、GC、磁盘或网络影响。

六、性能没有脱离场景的 QPS 排名#

“Redis 十万 QPS、关系库一万 QPS”缺少硬件、数据集、并发、请求大小、持久化、复制和延迟分位数,不能用于容量设计。同一数据库执行主键点查、跨表聚合和同步刷盘事务,结果可以相差多个数量级。

一个可复现的基准至少固定:

  • 数据量与热数据比例。
  • 索引、字段长度和返回结果大小。
  • 并发连接数与连接池策略。
  • 读写比例、事务长度和热点分布。
  • 持久化、复制和确认参数。
  • CPU、内存、磁盘、网络与版本。
  • p50、p95、p99、错误率和资源饱和度。

压测还应包含故障:副本落后、节点重启、磁盘延迟升高、主节点切换和缓存冷启动。只测稳定状态下的峰值吞吐,会遗漏真正决定生产容量的恢复阶段。

七、复制、高可用与备份是三个不同问题#

  • 复制提高冗余与读取能力,也会复制误删除和逻辑错误。
  • 自动 failover 缩短部分故障的 RTO,但可能带来旧主重新加入、客户端重连和数据确认边界问题。
  • 备份用于从误操作、损坏和灾难中恢复,必须与在线集群隔离。
产品常见高可用路径备份与恢复重点
MySQLInnoDB Cluster、Orchestrator、云托管、多副本物理/逻辑备份、binlog、PITR、一致性校验
PostgreSQL流复制、Patroni/repmgr、云托管base backup、WAL archive、PITR、恢复时间
RedisSentinel 或 Cluster、云托管RDB/AOF 策略、复制积压、可接受丢失窗口、重建来源
MongoDBReplica Set、sharded cluster、Atlas一致快照、oplog/PITR、分片协调恢复

Redis 的 RDB 与 AOF 能提供不同持久性选择,并非“Redis 一定会丢数据”;但当配置为每秒 fsync、异步复制或发生复杂 failover 时,应用必须接受相应窗口。MongoDB、MySQL 和 PostgreSQL 也不能仅凭“写成功”三个字推导零数据丢失,必须说明确认级别和故障点。

先写 RPO/RTO,再选拓扑#

例如:

订单主库:RPO ≤ 30 秒,RTO ≤ 30 分钟
缓存:允许全部丢失,15 分钟内从主库预热
分析副本:允许落后 10 分钟,4 小时内可重建

这三类数据即使来自同一业务,也不应该使用同一套恢复策略。

八、缓存是数据一致性协议,不是一个旁路加速器#

最常见的 Cache-Aside 流程是:

读:查缓存 → miss → 查主库 → 写缓存
写:写主库 → 删除缓存

这个流程仍需处理并发覆盖、删除失败、热点 Key、空值穿透、TTL 同时到期和主库故障。成熟实现通常包含:

  • TTL 加随机抖动,避免同批 Key 同时失效。
  • 热点 Key 使用请求合并或互斥重建。
  • 对不存在的数据做短 TTL 负缓存,并在写入后主动失效。
  • 更新路径具备幂等和重试;必要时使用 outbox/CDC 异步失效。
  • 缓存不可用时有限流和降级,不能无限把流量回灌主库。
  • 命中率与延迟按业务维度观察,而不是只看全局平均值。

如果无法回答“缓存与主库不一致时以谁为准”,就还没有定义这套架构的数据责任。

九、水平扩展:分片键是长期 API#

MySQL 与 PostgreSQL#

关系库通常先通过索引、查询优化、垂直扩容、只读副本、归档和分区延长单主架构寿命。真正分片后,跨分片事务、唯一约束、JOIN、分页和再平衡都会变复杂。

成熟开源路径包括:

  • MySQL:Vitess、ProxySQL、Orchestrator。
  • PostgreSQL:Citus、Patroni、PgBouncer。

它们解决的层次不同,不能把连接池、故障编排和分布式查询视为同一种“中间件”。

MongoDB#

MongoDB 原生提供 sharded cluster,但“内置”不等于“自动选对”。Shard key 决定写入是否形成热点、查询是否广播、数据是否容易均衡以及未来是否能有效再分片。选择前应使用真实查询分析目标键的基数、频率和单调性。

Redis Cluster#

Redis Cluster 将键分布到固定 hash slot。多键操作通常要求键位于同一 slot,可通过 hash tag 有意识地共置相关键,但过度共置又会制造热点。应用必须理解重定向、故障转移和客户端拓扑刷新。

十、海外成熟案例:提取约束,不照抄答案#

GitHub:MySQL 高可用与关系库分区#

GitHub 的公开工程文章描述了 MySQL 高可用自动化,以及为了规模将关系数据按业务边界 partition 的过程。值得注意的不是“GitHub 用 MySQL,所以 MySQL 适合所有大型网站”,而是这些工程条件:

  • 明确主库身份与故障转移流程。
  • 客户端和路由层配合拓扑变化。
  • 通过自动化降低人工故障处理的不确定性。
  • 分片前识别数据边界,处理跨分片访问和迁移。
  • 大版本升级需要复制验证、流量迁移和回退路径。

GitHub 也公开记录了升级 GitHub.com 到 MySQL 8.0 的多年工作。这恰好说明“生态成熟”不等于升级没有成本。

Discord:数据库会随工作负载和运维现实改变#

Discord 的公开文章记录了消息存储从早期方案演进到 Cassandra,再迁移到 ScyllaDB 的过程,并讨论分区热点、垃圾回收、读延迟和数据迁移。这个案例没有证明某一个数据库永久获胜;它证明规模、访问模式和团队运维能力变化后,旧选择可能不再成立。

对小系统最有价值的迁移原则是:先定义目标模型,双写或批量搬迁必须可校验,切流要可暂停,旧系统要保留回退窗口。没有校验和回退的迁移,只是在生产上一次性重写数据。

为什么不使用“某大厂使用某库”的名单#

一家公司通常同时运行多种数据库,同一产品也会在不同年代迁移。供应商案例页只能证明某场景曾使用某产品,不能证明它是公司的“主数据库”,更不能替代工作负载分析。案例的可迁移单位是约束、指标、故障和取舍,而不是 Logo。

十一、四类典型决策#

通用事务 Web 应用#

从团队更熟悉的 MySQL 或 PostgreSQL 开始。把唯一性、外键、幂等和关键状态转换交给关系数据库;在执行计划和慢查询证明需要后再加 Redis。

JSON 属性很多的业务系统#

如果核心实体关系稳定,仅部分属性稀疏,优先评估 PostgreSQL JSONB 或 MySQL JSON,并保留关系约束。如果整个聚合天然按文档读写、字段版本频繁演进且跨文档事务少,再评估 MongoDB。

排行榜、限流和短期会话#

Redis 的数据结构与 TTL 很合适,但要定义重建来源、内存上限、淘汰策略和故障降级。Session 是否允许丢失,决定是否需要持久化与副本,而不是由“它只是缓存”决定。

分析、全文与向量#

先区分在线事务与分析检索。小规模时 PostgreSQL 扩展可以降低系统复杂度;当查询资源影响主交易、索引构建过重或数据生命周期不同,再拆到 ClickHouse、OpenSearch 或专用向量系统。MongoDB aggregation 也不是数据仓库的无条件替代。

十二、选型验证:两周 PoC 应该交付什么#

不要做只展示 CRUD 的样例。PoC 应使用脱敏后的真实数据分布和前五条查询,至少交付:

  1. schema、索引和关键查询的执行计划。
  2. 稳态与峰值的 p50/p95/p99、错误率和资源曲线。
  3. 热点写、缓存冷启动或大文档情况下的表现。
  4. 一次节点重启或主从切换,以及客户端恢复时间。
  5. 一次备份恢复,记录真实 RPO/RTO。
  6. schema 变更或数据迁移的向前兼容方案。
  7. 三年成本估算,包括副本、备份、流量和人力。

最终决策记录应写清:为什么选择、拒绝了什么、哪些假设尚未验证、达到什么指标后重新评估。这样未来迁移时,团队能判断是业务变了,还是当年的假设本来就不成立。

十三、生产验收清单#

  • 关键约束由数据库或可靠的单一写入边界强制执行。
  • 前五条查询有代表性数据和执行计划。
  • 延迟以分位数衡量,压测固定了持久化与复制参数。
  • 客户端超时、重试和幂等策略与数据库故障语义一致。
  • 备份与在线副本隔离,并做过完整恢复。
  • RPO/RTO 有实测,不以“开启了复制”代替。
  • 缓存失效、击穿和整体不可用都有降级路径。
  • 分片键通过真实访问分布验证,而不是只看基数。
  • 数据迁移可校验、可暂停,并保留回退窗口。
  • 团队能维护所选拓扑,而不只会调用驱动 API。

参考资料#

版权许可

CC BY-NC-SA 4.0 本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

相关文章

s1oopX

登录 s1oopX