什么是 NoSQL?数据模型、与 SQL 的差异及使用时机

NoSQL 数据库是一类使用文档、键值对、宽列和图等模型的数据库,而不是依赖关系表模型。根据数据的形态和应用程序所需的操作选择其中一种;仅有 NoSQL 标签并不保证查询更快或设计更简单。

SQL(结构化查询语言)是一种用于查询和操作数据的语言,而 NoSQL 是一个涵盖多种模型的类别。“Not Only SQL”(不仅仅是 SQL)是解释该名称的一种方式;它并不意味着每个产品都禁止使用 SQL。AWS NoSQL 概述 描述了不同的模型和用途。

四种主要的 NoSQL 数据模型

这些类别描述了数据的表示方式。一个产品可能支持多个模型,同一类别中的产品在查询能力和操作上可能有所不同。

模型 基本表示 需要考虑的工作负载 示例
键值 唯一键及其关联值 通过已知键进行缓存或会话查找 Redis, 亚马逊 DynamoDB
文档 具有字段、数组和嵌套对象的文档 具有不同属性的产品目录,用户档案 MongoDB
宽列 按键和列组织的行或分区 按设备或时间范围查询的事件记录 Apache Cassandra, Bigtable
图 用于实体的节点和用于关系的边 推荐、依赖关系和路径遍历 Neo4j

键值数据库

键值模型使用键(例如 session:abc123)查找值。当访问是可预测的时候,比如通过 ID 检索会话,可以考虑使用它。在值内部搜索任意条件需要检查特定产品的索引和查询功能。

值不必是普通字符串。Redis 支持哈希、列表、集合和有序集合,以及字符串,如其 数据类型参考 中所述。对于缓存,还需要决定权威数据的存放位置,条目的过期时间,以及当缓存条目丢失时会发生什么。

文档数据库

文档将一个项目的字段、数组和嵌套对象组合在一起。JSON(JavaScript 对象表示法)是一种用于表达此类结构的文本格式;MongoDB 使用 BSON(二进制 JSON)存储文档。对于具有不同属性的项目或在同一屏幕上通常一起读取的数据,可以考虑使用文档。

根据访问频率、大小和更新情况,在文档中嵌入相关信息或引用另一个文档的标识符之间进行选择。将一个地址复制到每个订单中可以简化读取,但你必须决定复制的含义以及如何维护它。MongoDB建模指南建议根据访问模式进行设计。

宽列数据库

宽列模型围绕行或分区键和列组织数据。Bigtable 使用按行键排序的映射,并将相关列分组到列族中。它的存储模型文档解释了行、列和列族。

宽列存储不应被视为与将列值聚合在一起的分析列存储相同。Cassandra 需要面向查询的表和分区设计,并且不支持关系型联接。其 数据建模简介 解释了为什么你应首先确定诸如“读取该设备在此期间的记录”之类的问题。检查候选系统是否支持你所需的范围查询和聚合。

图数据库

图将实体(如人、产品或服务器)表示为节点,将它们的连接表示为边。考虑将其用于跟随关系的问题,例如识别受服务器故障影响的服务。

图也可以存储属性。正如其图概念指南中所解释的,Neo4j的属性图模型允许在节点和关系上设置属性。图模型并不会使每个查询都很快;需要定义遍历深度、关系类型和路径条件。

NoSQL与关系型数据库有何不同?

关系型数据库管理系统(RDBMS)使用表、键和约束来组织数据。它为关联记录的连接和应用共享规则提供了有用的基础。NoSQL产品提供适合其模型的操作,因此应比较每个候选产品如何回答您的应用问题,而不是不加修改地复制现有模式。

决策领域 在关系模型中需要检查的内容 在NoSQL模型中需要检查的内容
结构 列、类型、键和约束 文档、键、分区或图关系
查询和关系 连接、聚合和多样化过滤器 嵌入、引用、键查找或图遍历
更改和验证 模式更改及对现有数据的影响 即使在灵活结构下,也需检查必填字段、类型和版本控制
事务与一致性 表之间的隔离与更新 支持的范围、部署要求及读/写设置
扩展与操作 索引、复制、分布与运维复杂性 数据分布键、副本、重新平衡、成本与恢复

存储JSON并不自动需要NoSQL。例如,PostgreSQL提供JSON类型和针对jsonb的索引;详见其JSON类型文档。在使用部分数据的灵活属性时保持关系规则和事务也是一种可行设计。

灵活的模式仍然需要设计

模式定义了结构和规则,例如字段名称、类型和必填值。即使文档可能具有不同的字段,应用程序仍然需要可预测的数据。这个虚构的 JSON 目录展示了两个具有不同属性的产品:

[
  {
    "product_id": "book-101",
    "name": "Database Basics",
    "price_krw": 24000,
    "attributes": {
      "author": "Mina Kim",
      "pages": 320
    }
  },
  {
    "product_id": "headphones-201",
    "name": "Wireless Headphones",
    "price_krw": 79000,
    "attributes": {
      "wireless": true,
      "battery_hours": 30
    }
  }
]

两个产品都共享 product_id、name 和 price_krw,但它们嵌套的 attributes 不同。文档结构自然地表达了这种差异。然而,在同一价格字段中混合数字和字符串可能会使排序和计算复杂化,因此需要定义允许的类型和缺失值的处理方式。

MongoDB 支持类型和数值范围的验证规则;其 模式验证文档 解释了如何在需要的地方应用规则。相比之下,Cassandra 的 CQL(Cassandra 查询语言)在创建表时定义列和类型,如其 表定义参考 所示。因此,“每个 NoSQL 数据库都是无模式的”是不准确的。

JSON 描述了一种结构;它本身并不是非结构化数据。需要单独决定图像和视频是存储在数据库中,还是存储在对象存储中,并在数据库中保存它们的位置和元数据。文件大小、交付要求和管理需求会影响这一选择。

检查每个产品的事务和一致性保证

事务支持和范围

事务将更改视为一个工作单元。ACID 代表原子性、一致性、隔离性和持久性。对于必须一起成功或失败的更改,例如创建订单和减少库存,首先建立所需的保证。

NoSQL 并不意味着没有事务。MongoDB 支持在副本集和分片集群上的多文档事务,其保证取决于部署和读/写设置;请参见其事务文档。DynamoDB 也提供事务 API(应用程序编程接口),但有一些限制,包括支持的目标和区域范围。其事务参考解释了这些边界。

写入之后立即读取可能返回什么?

复制行为也很重要。对于最终一致的读取,最近完成的写入可能尚未在读取结果中出现。根据用户操作定义要求:用户保存订单后,它是否可以暂时不存在?

DynamoDB 表和本地二级索引可以使用强一致性读取,而来自全局二级索引和流的读取是最终一致的。有关按查询目标的差异,请参见其读取一致性文档。在事务内的 ACID 一致性与跨副本的读取一致性是不同的概念。

水平扩展并不保证性能

水平扩展是增加服务器,而不是用更大的机器替换一台服务器。分片将数据分布到各个服务器上;复制则保持副本。仅有多个服务器并不能保证数据平衡或查询加速。MongoDB 分片概览 分别描述了分布式配置。

分布不均的键可能会将请求集中到热点分区上。例如,将每个事件分组到一个固定键下可能不利于负载分布。应结合访问模式和键分布来考虑,如 DynamoDB 分区键指南 中所述。

性能取决于数据大小、索引、查询范围、读/写比例、一致性设置和工作负载。比较慢请求延迟、失败情况和恢复行为,同时参考平均响应时间。在成本评估中包括副本、备份、网络和运维工作;仅 NoSQL 类别并不能证明成本更低。

什么时候应考虑使用 NoSQL?

  • 已知键的缓存或会话访问:在定义过期时间、持久性以及与权威数据的同步时,考虑使用键值存储。
  • 具有不同属性的目录项:在设计共享字段验证和搜索索引时,考虑使用文档存储。
  • 通过已知键和时间段查询事件:在检查分区大小和范围查询时考虑宽列存储。
  • 多步骤关系遍历:在定义连接类型和遍历深度时考虑图。

当各种报表连接多张表或跨记录约束需要集中执行时,关系数据库值得早期比较。不要仅根据行业标签或数据量做出决定:检查每个候选方案中最重要的读取和更新是如何工作的。

您可以为不同的存储分配不同的角色,例如使用关系数据库作为权威来源,而使用键值存储作为缓存。额外的存储还会使数据副本更新、删除传播和恢复更加复杂,因此只有在有明确需求时才添加它们。

一个实际的评估顺序

  1. 写下重要的查询。为任务指定过滤条件和排序,例如按ID查找产品、最近用户订单或设备的每日记录。
  2. 定义正确性要求。记录必须一起成功的更改、重复请求处理、即时读取和可接受的旧值。
  3. 设计实际的候选模型。随着数据增长,检查文档大小、嵌入或引用、索引、分区键和查询路径。
  4. 比较小规模实现。使用相同的数据和操作来重现正常负载、集中负载和故障;记录产品版本和设置。
  5. 规划运维和迁移。检查权限、备份与恢复、监控、迁移、预期成本,以及在必要时返回现有存储的方案。

从查询和正确性要求开始,然后选择满足这些要求的模型和产品。如果你已经使用的数据库满足这些要求,在引入另一种存储之前,先确定添加新存储的理由。