Skip to content

元数据卡

维度
难度(进阶)
前置理解关系型数据库与 SQL(第1章)
关键词NoSQL、KV 存储、文档数据库、宽列存储、图数据库、CAP 定理
代码语言伪代码 / 配置 / CLI

你的进度

你在数据堡垒里待了十四天,学会了关系型数据库的一切。但有一天你走出堡垒主楼,发现周围散落着各种小作坊——有的只存钥匙和值(KV作坊),有的把所有东西写成一大本灵活的笔记(文档作坊),有的按列排货(宽列作坊),有的专注于记录物品之间的关系(图作坊)。它们不做 JOIN,不强制 Schema,但有些事做得比主仓库好。这就是 NoSQL 四大家族。

你的任务

这一章要回答:当关系型数据库不够用的时候,你能用什么替代方案?

你会认识 NoSQL 四大家族——KV 存储、文档数据库、宽列存储、图数据库——每个都在某些方面比关系型数据库做得更好。你还会接触 CAP 定理,理解为什么没有一种数据库能在一致性、可用性和分区容错上都做到完美。

目标是:读完这章,你能对着一个业务场景说出"这个适合用 Redis,那个适合用 MongoDB,关系型的够用就别折腾"。


破局 · 溯源

1. 关系型数据库做不到什么

关系型数据库(RDBMS)在过去四十年里统治了数据存储界。但它有几个天生的短板:

扩展性瓶颈。 关系型数据库是为单机设计的。你可以做读写分离、分库分表,但跨节点 JOIN 的代价很高,分布式事务的复杂度让人头皮发麻。当数据量从 GB 走向 TB 甚至 PB,单机垂直扩容(买更贵的机器)有物理天花板。

固定的 Schema。 你要先定义表结构,再往里填数据。加一列要 ALTER TABLE,可能锁表,迁移成本高。而现实中的数据是半结构化的——今天多一个字段,明天少一个。日志文件、JSON payload、传感器数据——它们没有一个固定的二维表结构。

对象-关系阻抗不匹配。 你在代码里用对象、Map、list。到了数据库你要拆成多张表、写 JOIN、再组装回来。ORMs 是胶带,但胶带粘不住所有裂缝。

某些查询场合太慢。 组队关系图中的"队友的队友"、路径搜索、匹配算法——用 SQL 的 JOIN 和递归 CTE 能写,但查询优化器不一定能高效处理。图数据库在这些场景上快了几个数量级。

实时性的瓶颈。 排行榜、计数器、在线状态——这些需要微秒级读写的场景,走关系型数据库的 SQL → 解析 → 权限检查 → 查询优化 → 执行 → 返回,每一行都太重了。

这些短板孵化了一个新的数据库运动——NoSQL(Not Only SQL)。

2. NoSQL 四大分类

NoSQL 不是一个单一的数据库,而是一个家族。四个成员做的事完全不同。

2.1 KV 存储(Key-Value Store)

代表:Redis、DynamoDB、Riak、Etcd

KV 存储是 NoSQL 里最简单的模型——就是一个巨大的哈希表。


 KV Store 
 
 key_A → value_A (JSON) 
 key_B → value_B (二进制) 
 key_C → value_C (计数)

你只通过 key 读写 value。没有 WHERE 条件、没有 JOIN、没有聚合查询。

bash
# Redis 示例
> SET adventurer:a001:name "艾琳"
OK
> SET adventurer:a001:level 42
OK
> GET adventurer:a001:name
"艾琳"
> INCR adventurer:a001:level
(integer) 43

强项:极高性能(O(1) 读写)、模型极其简单、适合缓存/会话/计数。

弱项:只有 key 查询,不能按 value 属性筛选。"查所有等级大于 10 的冒险者"——做不到。

怎么做到的:数据全在内存(Redis),或者用 SSD 但 key 总能在哈希表 O(1) 定位(DynamoDB)。没有查询优化器、没有锁管理、没有 MVCC——存和取就是全部。

实际案例:

  • Redis:内存数据库,每秒几十万操作,支持 List/Set/Sorted Set 等数据结构。面试题"Redis 为什么快"的答案就两个关键词:内存 + 单线程事件循环(避免锁竞争)。
  • DynamoDB:AWS 托管 KV/文档融合。自动扩展,一致性哈希分片,多活架构。面向大流量场景。不需要运维,只付钱。

2.2 文档数据库(Document Store)

代表:MongoDB、Couchbase、Firestore

文档数据库存的不是 JSON 字符串,而是有结构的文档——BSON(二进制 JSON)。

json
// MongoDB 文档示例
{
 "_id": "r001"
 "name": "龙鳞匕首"
 "type": "武器"
 "stats": {
 "damage": 45
 "element": "火"
 }
 "records": [
 { "keeper": "铁壁" "action": "鉴定" "date": "2024-01-15" }
 { "keeper": "铁壁" "action": "附魔" "date": "2024-01-20" }
 ]
}

对比关系型数据库:这张"宝物-鉴定记录"在 RDBMS 里要拆成 treasuresrecords 两张表,用 JOIN 关联。在 MongoDB 里,一件宝物的所有记录直接嵌在文档里——一次读取,全部拿到

javascript
// 插入
db.treasures.insertOne({
 name: "龙鳞匕首"
 type: "武器"
 stats: { damage: 45 }
})

// 查询——类 SQL 语法
db.treasures.find({ "stats.element": "火" })
db.treasures.find({ "stats.damage": { $gt: 30 } })

强项:Schema-free(每个文档可以有不同的字段结构)、嵌套文档消除 JOIN、水平扩展能力强(自动分片)。

弱项:不支持 JOIN(有 $lookup 但性能远不如 RDBMS 的 JOIN)、多文档事务支持弱(早期完全没有,后来加了但性能有折损)、嵌套太深时数据冗余和一致性维护成本高。

Schema-free 的双刃剑:开发阶段迭代快——加字段不用跑 migration。但生产环境没有 schema 约束,脏数据防不胜防。"这个字段以前是字符串,现在是对象了?谁改的?"——总有人会在维护时对着屏幕问这个问题。

最佳实践:在应用层做 schema 验证。MongoDB 本身也有 schema validation(MongoDB 3.6+),用 JSON Schema 约束。

2.3 宽列存储(Wide-Column Store / Column Family Store)

代表:Google Bigtable、Apache HBase、Cassandra、ScyllaDB

宽列存储是最容易让人误解的 NoSQL 分类。它名字里带"列",但和列存(Parquet/ORC)是两个完全不同的概念。

宽列存储的核心是:每一行可以有任意多列,不同行的列集合可以完全不同。


 Row Key CF:info 
 
 name email phone

 adv_1 "艾琳" ailin@fx — 

 adv_2 "铁壁" — 龙鳞… 

 adv_3 "霜语" shuang@fx —

Cassandra 用 CQL(Cassandra Query Language)查询,看起来像 SQL,但底层完全不一样:

cql
CREATE TABLE adventurer_by_id (
 adv_id UUID PRIMARY KEY
 name text
 faction text
 level int
);

INSERT INTO adventurer_by_id (adv_id name faction)
VALUES (uuid() '艾琳' '龙鳞守卫');

SELECT * FROM adventurer_by_id WHERE adv_id = ?;

关键限制:WHERE 子句必须包含主键。没有范围查询(除非按主键设计)、没有 JOIN、没有聚合。

强项极致水平扩展——Cassandra 可以扩展到上千节点,不加任何复杂的分片策略。数据按一致性哈希分布到所有节点,自动复制,无单点故障。

弱项:查询模式受限——你必须先知道你的查询模式再建模,不能像关系型数据库那样"先建模后随便查"。冷读性能差(LSM-Tree 读路径放大)。

适用场景:时间序列数据(法阵监测写入)、消息/日志存储、推荐引擎/冒险者档案(每行列数不同)。

Google Bigtable 的起源:2006 年 OSDI 论文。Google 内部用来存储网页索引、Google Earth、Gmail 等。每日 PB 级写入。Bigtable 用 GFS(Google File System)做底层存储,Chubby 做分布式锁。它的设计启发了 HBase 和 Cassandra。

2.4 图数据库(Graph Database)

代表:Neo4j、Amazon Neptune、ArangoDB、TigerGraph

图数据库存储的是节点(实体)和(关系),每个节点和边都可以带属性。

 
 艾琳 ← 冒险者节点
 
 
 组队组队
 
 
 铁壁 ← 推荐队友 
 推荐 
 
 探索探索
 
 
 "龙鳞洞穴下层发现宝箱" ← 探索记录节点

Neo4j 用 Cypher 查询语言:

cypher
// 创建节点和关系
CREATE (ai:Adventurer {name: '艾琳' level: 28})
CREATE (tie:Adventurer {name: '铁壁' level: 32})
CREATE (ai)-[:TEAM_WITH]->(tie)

// 查"艾琳组队的人组队的人"——六度关系
MATCH (ai:Adventurer {name: '艾琳'})-[:TEAM_WITH*2]->(fof)
RETURN DISTINCT fof.name

同样的查询在关系型数据库里:

sql
-- 邻接表:adventurers(id name) / teams(leader_id member_id)
-- "艾琳组队的人组队的人"
SELECT DISTINCT a3.name
FROM teams t1
JOIN teams t2 ON t1.member_id = t2.leader_id
JOIN adventurers a3 ON t2.member_id = a3.id
WHERE t1.leader_id = (SELECT id FROM adventurers WHERE name = '艾琳');

当深度 > 2 时事情开始失控。到深度 6("队友的队友的队友的队友的队友的队友"),SQL 要写 6 次 JOIN,表的大小按指数膨胀。图数据库通过遍历——-[:TEAM_WITH*6]->——不需要 JOIN,直接沿着边走过去,O(度数^深度) 的复杂度但不会产生笛卡尔积的中间结果。

图模型的根本洞察:关系(边)在图中是第一等公民,不是通过外键和 JOIN 推导出来的附加产物。

强项:关系遍历效率远超关系型数据库、直观建模(组队/向导/知识图谱/路径搜索)。

弱项:跨节点聚合/扫描慢、不适合 OLAP 场景、生态不够成熟(工具、运维经验)。

典型场景

  • 组队推荐("铁壁的队友的队友"——冒险者组队匹配)
  • 知识图谱(宝物传说溯源)
  • 异常检测(环检测:A→B→C→A 的宝物流转异常)
  • 权限管理(RBAC 里"冒险者→职阶→权限"的继承关系)

3. CAP 定理

CAP 定理能帮你理解 NoSQL 系统行为——它告诉你在分布式环境下你不可能拥有 C、A、P 三者

三个字母:

  • C(Consistency,一致性):所有节点在同一时刻看到的数据是相同的。写入后,所有读请求都返回最新数据。
  • A(Availability,可用性):每个请求都能收到一个(非错误的)响应——即使某些节点挂了。
  • P(Partition Tolerance,分区容错性):系统中任意节点间的网络通信断开(网络分区),系统仍然能继续工作。
 Consistency
 
 
 CA CP
 (传统 RDBMS) (HBase MongoDB)
 
 Partition Tolerance
 
 AP 
 (Cassandra 
 DynamoDB) 
 
 Availability

关键理解:网络分区(P)是分布式系统中的一个必然事件——网络一定会断。所以你在设计分布式数据库时,P 是必选项。你只能在 C 和 A 之间二选一。

这就是所谓的 CP vs AP

  • CP 系统(HBase、MongoDB ← 默认配置下):当网络分区发生时,系统选择停止服务某些节点以保持一致性。你读到的一定是最新数据,但可能读不了。
  • AP 系统(Cassandra、DynamoDB):当网络分区发生时,系统选择继续服务,但读到的数据可能是过时的(最终一致性)。你总是能读到数据,但不一定是最新的。

一个实际的例子

数据堡垒的宝库系统架设在七座城之间。一个名叫铁壁的守卫在龙鳞城的分仓取走了最后一件"星辉护符"——物卡上写着还剩 1 件。但宝库节点分布在龙鳞城和星辉城。

不巧的是,龙鳞城和星辉城之间的传讯法阵正好断了。

  • CP 系统:龙鳞城的写入不会同步到星辉城。星辉城守卫查询——系统发现龙鳞城那边的数据同步不上来,直接甩了个错误。"法阵中断,请稍后再查。"守卫跺脚骂了一声。一致性保证了,但守卫等不了。
  • AP 系统:星辉城守卫打开系统一看——"星辉护符:库存 1 件。"他正要取走。但实际上铁壁已经在龙鳞城取走了它。一件护符,两双主人。可用性保证了,但数据不一致。

现实中的 CAP

CAP 定理不是"三选二",而是"P 是强制选择的,C 和 A 之间二选一"。

而且 CAP 的描述是二值式的(要么一致要么不一致),但现实系统提供的是渐变的选择——最终一致性、可调一致性(Cassandra 的 QUORUM 级别)、可串行化、快照隔离。这些超出了 CAP 的简单二分法。PACELC 定理(在正常情况还要考虑延迟 vs 一致性的权衡)是更完整的模型,但 CAP 是最好的起点。

4. 选型逻辑:什么场景用什么存储

你的数据场景 
 
 要不要事务和JOIN? 要不要事务和JOIN?
 
 要→ RDBMS 足够 不要
 
 
 
 缓存/计数 文档/日志 关联分析
 
 KV 文档DB 图DB
 (Redis) (MongoDB) (Neo4j)

更具体的决策树:

你的场景推荐系统为什么
登记台常备系统(宝物登记、冒险者档案、主仓库)PostgreSQL / MySQLACID 事务、成熟生态
缓存、探索状态、计数器、排行榜Redis内存级速度,数据结构丰富
高写入吞吐、海量探索日志Cassandra / ScyllaDB宽列模型,写入从不打架
灵活 Schema、快速迭代宝物属性MongoDB文档模型,Schema-free
组队关系、向导匹配、知识图谱Neo4j图遍历 vs 多层 JOIN
自动扩展的 KV 库房DynamoDB托管、按量付费、一致性哈希自动分片
法阵指标、探索日志搜索Elasticsearch全文搜索 + 聚合能力

核心铁律:关系型数据库够用就别换 NoSQL。NoSQL 每解决一个问题,顺手带来一群新问题——缺事务、删数据难、运维复杂。


深入冒险:多模数据库

现实中的趋势是多模数据库(Multi-Model Database)——一个数据库引擎支持多种数据模型:

  • ArangoDB:文档 + 图 + KV
  • Microsoft Cosmos DB:文档 + 宽列 + 图 + KV
  • Redis Stack:KV + 文档(JSON)+ 图(RedisGraph)+ 搜索
  • PostgreSQL 加扩展也可以当文档数据库用(JSONB)

多模数据库的根本思路:用一种查询接口访问多种数据模型,避免为不同模型维护多个数据库。

但挑战也很明显:一个引擎在多个模型上做到最优很难。Redis Graph 的性能不如 Neo4j,Cosmos DB 的文档功能不如 MongoDB 精细。


常见陷阱

  1. "NoSQL 就不需要设计模型了" NoSQL 只是没有固定的 SQL Schema,但你的数据模型反而需要更前瞻的设计。MongoDB 的嵌套深度多少合适?Cassandra 的 Primary Key 怎么定?Neo4j 的关系类型怎么设计?这些决定直接影响查询性能。

  2. "Redis 是数据库,数据不丢" Redis 默认是纯内存数据库。虽然 AOF + RDB 持久化可以保证重启恢复,但不能保证不丢数据(异步刷盘)。Redis 的定位是缓存 + 高速数据存储,不是强持久化存储。

  3. "MongoDB 的 JOIN 很方便"$lookup 确实能做 JOIN,但它的性能远不如关系型数据库。它的实现方式是一个文档一个文档地去匹配,没有索引优化和连接算法选择。如果你想做复杂 JOIN,你在错误的地方。

  4. "Cassandra 可以随便 CQL 查询" CQL 看起来像 SQL,但查询模式必须匹配 Primary Key 设计。不按主键查的查询——性能灾难,触发全表扫描。Cassandra 的建模哲学是"查询优先":你先确定查询模式,再设计表结构。

  5. "图数据库能替代关系型数据库" 不。图数据库擅长关系遍历,但做不了复杂的事务、多表聚合、OLAP 分析。它是补充,不是替代。


通关挑战

  • ** 热身(5 分钟)**:说出四大 NoSQL 分类的名称和代表系统。
  • ** 挑战(30 分钟)**:给你一个堡垒组队探索场景——冒险者档案(稳定结构)、宝物信息(多变的属性)、组队关系(谁和谁组队)、实时在线探险人数(秒级更新)、推荐队友(基于组队关系)。为每个子场景选择最合适的数据库,并说明理由。
  • ** 观察(15 分钟)**:启动 Redis 容器 docker run -d --name myredis -p 6379:6379 redis:7,用 redis-cli 执行 SET/GET/INCR,感受 O(1) 操作的响应时间。再用 redis-benchmark -q -n 100000 测试吞吐。

验收标准

读完后你能:

  • 解释为什么关系型数据库在某些场景下不够用
  • 说出四大 NoSQL 分类的核心理念、代表系统及其优劣
  • 用 CAP 定理解释一个分布式数据库在分区时的行为
  • 根据业务场景做出合理的选型判断
  • 知道 NoSQL 不是"更好"而是"不同"

常见卡点

  • CAP 定理反直觉:很多人会问"为什么不能三全其美"——因为网络分区是必然事件,你选择分区容忍后,C 和 A 是一个 0-100% 的连续值,不是二值开关。Cassandra 的 QUORUM 写 + QUORUM 读能提供强一致性,代价是延迟更高。
  • 宽列和列存混淆:宽列(Wide-Column)是 NoSQL 的一种数据模型,允许不同行有不同的列集合。列存(Columnar Storage)是存储格式(Parquet/ORC),是数据分析场景的优化方向——两者完全不同。
  • "MongoDB 支持 JOIN 了,那是关系型数据库":MongoDB 的 $lookup 支持有限 JOIN 操作,但仍然不是关系型数据库。关系型数据库的核心是关系模型 + ACID 事务 + SQL 优化器。

现在不需要理解

  • 一致性哈希的算法细节(下一章会讲)
  • LSM-Tree 在 Cassandra 和 HBase 中的内部行为
  • 图数据库的遍历算法和查询优化
  • PACELC 定理对 CAP 的扩展

旅人笔记

NoSQL 不是关系型数据库的替代品,而是补充。KV 快、文档灵活、宽列可扩展、图擅长关系——选型的关键是:先理解你的场景,再选择工具。 关系型数据库够用时,不要为了"新"而换。

下一站预告

Built with VitePress | Software Systems Atlas