关系型数据库与NoSQL数据库:如何为您的系统选择合适的基础架构
The choice between a relational and a NoSQL database is one of the most consequential architectural decisions you'll make. Get it right and your system scales gracefully. Get it wrong and you'll be fighting your database for years. This is what you actually need to know.
每种数据库都用于存储数据。但相似之处仅此而已。关系型数据库和 NoSQL 数据库在数据应如何结构化、存储和查询方面代表着根本不同的理念——而这些差异会层层渗透到基于它们构建的系统的各个层面。
这并非新旧之争,亦非简单与复杂之分。这两大类别中都包含经过实战检验、已在超大规模环境中运行的成熟系统。关键在于适用性:哪种模型更契合您的数据形态、应用程序的访问模式,以及用户所需的一致性保障。
关系型数据库的本质
关系型数据库将数据组织成表——即行与列,且它们之间具有明确定义的关系。名称中的“关系型”并非指表之间相互关联(尽管确实如此),而是指数学中的“关系”概念:一组具有相同属性的元组。 这一由埃德加·科德(Edgar Codd)于 1970 年在 IBM 提出的理论基础,至今仍展现出非凡的生命力。
关系型数据库的核心特征包括:
**固定模式。**在插入数据之前,需先定义结构:存在哪些表、每张表包含哪些列、每列接受何种数据类型,以及适用哪些约束(如NOT NULL、UNIQUE、CHECK等)。模式是应用程序与数据库之间的契约。
规范化。 数据经过组织以消除冗余。客户姓名仅在“客户”表中出现一次。每笔订单都通过ID引用该客户。如果姓名发生变更,只需在单一位置进行修改。这不仅是整洁有序——更是对一致性的保障。
**ACID事务。**原子性、一致性、隔离性和持久性。事务要么完全完成,要么完全不执行。若将资金从一个账户转入另一个账户,借方和贷方操作要么同时发生,要么都不发生。数据库在引擎层级强制执行这一原则。
**SQL。**结构化查询语言(SQL)是关系型数据库的通用接口。它具有声明性——你只需描述需求,而非实现方式——且表达能力极其强大。 连接、聚合、窗口函数、子查询、CTE:SQL 的全面功能为您提供了工具,让您无需编写应用程序代码即可对数据提出复杂的问题。
主流关系型数据库——PostgreSQL、MySQL、SQL Server、Oracle——虽在具体功能集、性能特征和许可模式上各不相同,但都具备这些特性。尤其是 PostgreSQL,它既严格遵循标准,又拥有卓越的功能集,已成为新项目的默认选择。
NoSQL 数据库的真实含义
“NoSQL”是一个统称,涵盖了几个不同的数据库类别,它们的主要共同点在于“非关系型”以及不以 SQL 作为主要接口(尽管有些支持类似 SQL 的查询语言)。 将该术语理解为“不仅限于 SQL”更为贴切——这表明关系模型并非唯一有效的解决方案。
NoSQL 的主要类别包括:
文档数据库将数据存储为文档(通常是 JSON 或 BSON),每个文档的结构可以各不相同。MongoDB 是其中应用最广泛的,其他还包括 CouchDB 和 Firestore。这类数据库没有表,也没有固定的模式;一个文档“集合”中可能包含字段完全不同的记录。
键值存储是最简单的模型:每条记录都是一个可通过唯一键定位的值。Redis是其代表性产品,广泛用于缓存、会话存储和实时排行榜。DynamoDB主要也作为键值存储运行,并在其上叠加了文档处理能力。
列族存储按列而非按行组织数据,这使得在数百万行数据中读取特定列时效率极高。Apache Cassandra 和 HBase 是主要代表。该模型专为写入密集型、高吞吐量的工作负载设计,且查询模式需预先确定。
图数据库将数据建模为节点和边——即实体及其之间的关系。Neo4j 是该领域的代表性产品。当实体间的关系是查询的核心主题时(如社交网络、推荐引擎、欺诈检测),图数据库能够以自然的方式表达这些查询,而传统 SQL 则需要使用深度递归的语句才能实现。
时间序列数据库专为按时间索引的数据进行优化:InfluxDB、TimescaleDB、Prometheus。传感器读数、金融行情、应用程序指标——这类数据中时间戳始终是主键,且跨时间范围的查询是主要的访问模式。
驱动一切的结构性差异
关系型数据库与 NoSQL 数据库之间最根本的差异不在于语法或规模——而在于数据结构与查询灵活性之间的关系。
关系型数据库以前期严格的模式规范为代价,换取了最大的查询灵活性。由于数据经过规范化和类型化处理,您可以在查询时对其提出几乎任何问题。 你可以以设计模式时从未预料到的方式进行表连接。你可以对初始设计数月后才添加的维度进行聚合。模式约束了数据,但查询语言赋予了你自由。
NoSQL 数据库则做出了相反的权衡。它们在数据结构和写入方式上赋予你灵活性,但代价是查询模式受到更多限制。文档数据库允许你存储任意形式的数据,且无需迁移。但要高效地跨文档进行查询,则需要预先了解访问模式,并围绕这些模式设计数据结构。
这就是核心的矛盾:**模式灵活性与查询灵活性。**二者可以兼得,但仅限于一定程度。您选择的数据库模型反映了哪种权衡对您的用例更可接受。
CAP 定理及其重要性
分布式系统无法同时保证一致性、可用性和分区容忍性——最多只能实现其中两项。这就是 CAP 定理,它为理解关系型数据库与 NoSQL 数据库在故障条件下的不同行为提供了理论基础。
关系型数据库传统上优先考虑一致性和分区容错性。每次读取都反映了最新的写入。如果发生网络分区且数据库无法保证一致性,它将拒绝提供过时的数据——此时系统会变得不可用,而非返回错误数据。
许多 NoSQL 数据库——特别是像 Cassandra 和 DynamoDB 这样专为水平分布设计的——则优先考虑可用性和分区容错性。即使在网络分区期间,它们仍会提供数据,并接受部分读取可能返回过期值。 这就是“最终一致性”:所有节点最终会收敛到同一值,但在某个时间窗口内,它们可能无法达成一致。
实际意义在于:如果您的应用程序无法容忍读取过时数据——例如银行余额、库存数量、医疗记录——您就需要强一致性。 如果您的应用程序可以容忍短暂的不一致,以换取始终可用——例如社交媒体动态、产品目录、用户资料——那么最终一致性是可以接受的,并且能为您提供架构自由。
现实世界中的案例:正确的选择如何改变局面
GitHub:PostgreSQL 作为全球平台的核心
GitHub 基于 PostgreSQL 运行着全球最大的软件协作平台之一——数百万个仓库、数十亿条事件,以及横跨用户、组织、仓库、拉取请求、问题和评论的复杂关系查询。 关系型模型与该领域高度契合:这些实体之间的关系即为产品本身。一个拉取请求属于某个仓库,由用户创建,经其他用户审查,关闭问题,并触发持续集成管道。
多年来,GitHub 一直运行着一个主 PostgreSQL 实例,并辅以读取副本。随着规模扩大,他们引入了 Vitess(用于部分工作负载的 MySQL 分片层)和 ProxySQL 进行连接管理——但关系型核心始终未变。 这并非意味着 PostgreSQL 无需投入即可无限扩展。关键在于,对于由关系定义的领域,关系型模型是最佳选择;而投入在扩展上的努力之所以值得,正是因为迁移到其他数据模型的代价会更高。
Twitter:从 MySQL 向混合架构的痛苦迁移
Twitter 最初基于 MySQL 构建,随着用户增长速度超过单个关系型数据库的承载能力,它面临了一场广为人知的扩展危机。 核心问题在于社交图谱——数亿用户之间的关注/被关注关系——以及时间线扇出问题:当一位拥有 1000 万粉丝的用户发布推文时,该推文需要近乎即时地出现在 1000 万条时间线上。
Twitter 当时采用的关系型模型无法以所需的速度处理这种写入扇出需求。Twitter 将社交图迁移到了自定义的内存存储中,最终采用 Manhattan(其内部分布式键值存储)来处理时间线,并采用 Cassandra 来处理其他高写入工作负载。
这里得到的启示颇具深意。Twitter 的核心数据——推文、用户、关系——本质上是关系型的。问题不在于数据模型,而在于写入量和广播范围已达到需要分布式处理的规模。这次迁移成本高昂、技术复杂,且需要多年的工程投入。 在 Twitter 的规模下,这是正确的决策。但在几乎任何其他应用程序的规模下,这都为时过早。
MongoDB:模式灵活性的警示案例
2010年代初,随着 MongoDB 迅速走红,许多团队主要因其模式灵活性(即无需预先定义模式即可开始存储数据)而采用它。对于原型设计和早期开发阶段,这确实加速了迭代进程。
但随着系统日趋成熟,问题逐渐显现。缺乏模式强制性导致数据不一致性逐渐累积。本应为必填字段的字段有时缺失,本应为整数的值有时却变成了字符串。那些假设数据结构一致的查询会返回意料之外的结果。开发团队发现自己不得不编写大量应用层验证代码,而这些本应由数据库自动强制执行。
这并非 MongoDB 作为数据库的失败——对于许多用例而言,它仍是合适的工具。问题在于“模式灵活性无条件有益”这一假设的谬误。灵活性只是推迟了思考数据结构的成本,并未消除这一成本。 那些使用 MongoDB 却缺乏纪律性的团队,最终在应用程序代码中实现了关系型模式的弱化版本,却无法获得关系型数据库本应提供的保障。
Amazon DynamoDB:适用于正确用例的成功案例
亚马逊的购物车是 NoSQL 正确应用的经典范例之一。 需求非常明确:购物车必须始终可用(即使在区域性故障期间,客户也应始终能够添加商品),它需要处理海量的写入量(数亿用户添加和移除商品),且访问模式简单且可预知(始终通过客户 ID 访问)。
亚马逊工程团队在2007年那篇启发了DynamoDB的论文中,描述了他们为购物车刻意选择“最终一致性”的决策:若在网络分区期间有两台设备同时添加商品,当分区恢复时,这两次添加操作都会被保留,即使这意味着系统会短暂显示出略微不一致的状态。 一个始终可写入的购物车——即使需要付出短暂不一致的代价——也比一个时常不可用的购物车更有价值。
DynamoDB 围绕这一用例的设计——简单的键值访问、已知的查询模式、可用性优先于一致性——正是其卓越之处。试图将 DynamoDB 作为通用关系型数据库使用、在多个实体上运行复杂查询的应用程序,往往会遇到困难。
Airbnb:Elasticsearch 负责搜索,PostgreSQL 确保数据准确性
Airbnb 的搜索功能——根据位置、日期、价格、设施和房源状态进行筛选——是一种查询,没有任何关系型数据库能在大规模场景下高效处理。 在数百万条房源中结合地理搜索、全文匹配和复杂的多属性筛选,正是 Elasticsearch 专为之设计的用例。
Airbnb 将 Elasticsearch 用作搜索层,同时保留 PostgreSQL 作为数据源系统。 当房源信息更新时,变更会先写入 PostgreSQL(作为数据源,具备完整的 ACID 保证),随后异步同步至 Elasticsearch(作为搜索索引,针对搜索体验的特定查询模式进行了优化)。
这是一种成熟的架构模式:针对不同需求选用合适的数据库,保持明确的“真相来源”,并接受维护多个数据存储同步所带来的运维复杂性。之所以行之有效,是因为职责边界清晰,且各方对权衡取舍了然于心。

何时选择关系型数据库
对于大多数应用程序而言,关系型数据库是最佳的默认选择。以下情况显然应选择关系型数据库:
**您的数据存在有意义的关联关系。**如果系统中的实体之间存在实质性的关联——例如用户与其帖子、订单及其明细、项目及其任务——关系模型能直接体现这些关系,并让您高效地跨关系进行查询。
您需要强一致性。 金融交易、库存管理、预订系统、医疗记录——任何领域中,错误读取都会产生实际后果,都需要 ACID 保证。关系型数据库在引擎层面上提供了这一点。
**您的查询模式尚未完全确定。**如果您的应用程序需要回答关于数据的问题,而这些问题在设计阶段无法完全预见,那么 SQL 的灵活性就显得尤为宝贵。能够针对一个规范良好的模式编写即席查询,是一项重要的运营资产。
您的团队精通 SQL。 这听起来微不足道,实则不然。SQL 是软件工程领域最广泛掌握的技术技能之一。您聘用的每位开发人员都或多或少掌握它。相关的运维知识——如何添加索引、如何解读查询计划、如何管理备份——随处可得。工具生态系统也已相当成熟。
**您不确定具体需求。**如有疑虑,建议从 PostgreSQL 开始。等您了解实际的访问模式后,再引入专用存储方案会比发现数据比预期更具结构化特征时,从文档存储迁移回关系模型要容易得多。
何时选择 NoSQL
当关系型模型无法在不付出不成比例的努力的情况下满足特定需求时,NoSQL 便是正确的选择:
写入量超出单节点处理能力。 若您的应用需要在地理分布式系统中维持每秒数十万次写入,则必须通过多节点进行水平扩展。Cassandra 等列族存储正是为此而设计;关系型数据库虽可分布式部署,但这并非其天然模式。
您的数据真正属于无模式或高度可变。 例如:不同产品类型属性完全不同的产品目录、内容类型快速演变的 CMS、事件模式由外部系统定义的日志系统——这些场景都适合采用文档存储,因为数据库不会强制要求数据结构。
您的主要访问模式是超大规模的键值查找。 会话存储、缓存、实时排行榜、功能开关——在这些工作负载中,您始终通过单一键访问数据,且无需进行连接或聚合操作,因此能充分利用键值存储的简单性和性能优势。
您正在构建图模型。 若主要查询涉及关系遍历——查找所有朋友的朋友、确定两个节点间的最短路径、检测循环依赖——图数据库能比递归 SQL 更自然地表达这些查询,并更高效地执行它们。
您需要时间序列优化。 每秒数百万条传感器读数、金融行情或应用指标,且查询主要基于时间范围,这种场景更适合使用专用的时间序列数据库,而非仅在关系型数据库中添加时间戳列。
多数据库的现实
关于关系型数据库与 NoSQL 的争论,最关键的一点在于:在大多数成熟的系统中,这并非非此即彼的选择。大规模系统通常会使用多种数据库,每种数据库都针对其所服务的特定工作负载而选择。
PostgreSQL 作为主数据存储。Redis 用于会话缓存和速率限制。Elasticsearch 用于全文搜索。InfluxDB 用于应用程序指标。该架构中的每个数据库都在发挥其最大优势,而应用层负责管理它们之间的一致性。
这种复杂性带来的运维成本是切实存在的。每增加一个数据库,就意味着需要监控、备份、扩展和理解的系统又多了一个。要保持多个存储的一致性——例如处理“写入 PostgreSQL 成功但后续更新 Elasticsearch 失败”这类故障场景——所需的工程管理能力绝非易事。
几乎所有应用程序的起点都应是一个单一的关系型数据库。只有当存在特定且明确的需求无法通过单一数据库满足时,才应添加专用存储。过早的数据库多样化是一种“过早优化”:在尚未明确复杂性是否必要之前,便增加了不必要的复杂性。

做出决策
选择数据库时真正需要考虑的问题:
您对数据一致性有何要求? 如果对“用户读取过期数据会怎样”的回答是“这会造成严重问题”,那么您需要ACID特性。如果回答是“这虽然有点烦人但尚可接受”,那么您还有其他选择。
您的查询模式是怎样的? 如果您确切知道数据将如何被访问,且访问模式简单且高频,您可以针对此进行优化。如果您的访问模式复杂或尚未完全明确,SQL 的灵活性将非常宝贵。
**您的写入量是多少?**对于绝大多数应用程序而言,在配置合理的硬件上,经过良好调优的 PostgreSQL 实例每秒可轻松处理数千次写入。真正无法满足需求的场景,其实比 2010 年代 NoSQL 热潮所暗示的要少得多。
**您的数据结构如何?**高度均匀且关联紧密的数据非常适合关系模型。高度多变且关联松散的数据可能更适合文档模型。
您的团队掌握哪些技术? 操作熟练度至关重要。即使某款 NoSQL 数据库在理论上更合适,但一个深谙 PostgreSQL 的团队,在 PostgreSQL 上构建的系统仍会比在正在学习的 NoSQL 数据库上更可靠。
关系型数据库之所以能在五十年的技术变革中屹立不倒,并非因为它总是最佳工具,而是因为它是一个极其优秀的默认选择——灵活、可靠、被广泛理解,并且拥有数十年的运维经验支撑。若要刻意偏离它,必须清楚地了解你将获得什么,又将放弃什么。