元数据卡
维度 值 难度 (进阶) 前置 理解关系型数据库与 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、没有聚合查询。
# 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)。
// MongoDB 文档示例
{
"_id": "r001"
"name": "龙鳞匕首"
"type": "武器"
"stats": {
"damage": 45
"element": "火"
}
"records": [
{ "keeper": "铁壁" "action": "鉴定" "date": "2024-01-15" }
{ "keeper": "铁壁" "action": "附魔" "date": "2024-01-20" }
]
}对比关系型数据库:这张"宝物-鉴定记录"在 RDBMS 里要拆成 treasures 和 records 两张表,用 JOIN 关联。在 MongoDB 里,一件宝物的所有记录直接嵌在文档里——一次读取,全部拿到。
// 插入
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,但底层完全不一样:
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 查询语言:
// 创建节点和关系
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同样的查询在关系型数据库里:
-- 邻接表: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 / MySQL | ACID 事务、成熟生态 |
| 缓存、探索状态、计数器、排行榜 | 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 精细。
常见陷阱
"NoSQL 就不需要设计模型了" NoSQL 只是没有固定的 SQL Schema,但你的数据模型反而需要更前瞻的设计。MongoDB 的嵌套深度多少合适?Cassandra 的 Primary Key 怎么定?Neo4j 的关系类型怎么设计?这些决定直接影响查询性能。
"Redis 是数据库,数据不丢" Redis 默认是纯内存数据库。虽然 AOF + RDB 持久化可以保证重启恢复,但不能保证不丢数据(异步刷盘)。Redis 的定位是缓存 + 高速数据存储,不是强持久化存储。
"MongoDB 的 JOIN 很方便"
$lookup确实能做 JOIN,但它的性能远不如关系型数据库。它的实现方式是一个文档一个文档地去匹配,没有索引优化和连接算法选择。如果你想做复杂 JOIN,你在错误的地方。"Cassandra 可以随便 CQL 查询" CQL 看起来像 SQL,但查询模式必须匹配 Primary Key 设计。不按主键查的查询——性能灾难,触发全表扫描。Cassandra 的建模哲学是"查询优先":你先确定查询模式,再设计表结构。
"图数据库能替代关系型数据库" 不。图数据库擅长关系遍历,但做不了复杂的事务、多表聚合、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 快、文档灵活、宽列可扩展、图擅长关系——选型的关键是:先理解你的场景,再选择工具。 关系型数据库够用时,不要为了"新"而换。