mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
4967 字
13 分钟
数据库全景:数据模型、存储模型与数据库分类
2024-05-05

你每天都在和数据库打交道——写 SQL 查订单、用 Redis 缓存热点数据、往 Elasticsearch 灌日志。但你有没有想过:为什么 MySQL 用表存数据,MongoDB 用 JSON,Neo4j 用节点和边?为什么分析型查询在列存数据库上快几个数量级?为什么同一个业务有时需要两三种数据库?

本章是整个系列的”地图”。不会深入任何一种数据库的内部实现(那是后续章节的任务),而是从宏观视角俯瞰数据库的完整分类体系:数据怎么建模、怎么查询、怎么存储、怎么分类、怎么选型。理解了这幅全景图,后续每一章的学习就有了锚点。

数据库的历史可以追溯到 1960 年代。1966 年,IBM 的 IMS(Information Management System)成为第一个商用数据库,采用层次模型存储数据——像一棵树,每个节点只有一个父节点。1970 年,Edgar F. Codd 在 IBM 发表了划时代的论文”A Relational Model of Data for Large Shared Data Banks”,提出了关系模型——数据用表格表示,操作用集合论描述。这篇论文催生了 System R(1974,IBM)和 Ingres(1973,UC Berkeley)两个原型系统,分别演化为今天的 DB2 和 PostgreSQL。1995 年,Michael Widenius 发布了 MySQL,将关系数据库带入了互联网时代。2000 年代,Google 的 Bigtable(2006)和 Amazon 的 Dynamo(2007)论文催生了 NoSQL 运动。2012 年,Google 的 Spanner 论文开启了 NewSQL 时代——分布式数据库可以同时提供 SQL 接口和水平扩展。理解这段演进脉络,你会发现每一种数据库都是对特定时代需求的回应——没有”最好的数据库”,只有”最适合当前需求的数据库”。

前置知识#

  • SQL 基础:SELECT/INSERT/UPDATE/DELETEJOINGROUP BY 等基本语法
  • 基本的数据结构知识:哈希表、B 树、排序
  • 操作系统基础:文件系统、内存管理、进程模型
Note

本章是全系列的起点,不需要前置章节。如果你已有数据库使用经验,可以快速浏览本章,重点关注数据模型分类和存储模型部分。

一、为什么需要数据库#

1.1 从文件系统到数据库#

假设你要做一个用户系统,最朴素的做法是直接写文件:

# 用纯文件存储用户数据
def save_user(user_id, name, email):
with open(f"users/{user_id}.txt", "w") as f:
f.write(f"{name}\n{email}\n")
def read_user(user_id):
with open(f"users/{user_id}.txt", "r") as f:
lines = f.readlines()
return {"name": lines[0].strip(), "email": lines[1].strip()}

这段代码在单线程、小数据量时能跑。但一旦面临真实场景,问题接踵而至:

  • 并发写入:两个进程同时写同一个用户文件,数据互相覆盖
  • 原子性:转账操作需要同时修改两个账户,中间崩溃怎么办?
  • 查询能力:想查”年龄大于 30 且城市为北京的用户”,你得遍历所有文件
  • 数据完整性:邮箱格式不对、年龄为负数,谁来校验?
  • 可靠性:磁盘坏了、进程崩溃,数据能恢复吗?

数据库的出现,正是为了系统性地解决这些问题。

1.2 数据密集型应用的挑战#

现代应用的核心矛盾是:数据量增长的速度远超单机处理能力的增长。Martin Kleppmann 在 Designing Data-Intensive Applications 中指出,数据密集型应用面临三大核心挑战:

挑战含义数据库的应对
可靠性(Reliability)系统在故障面前仍能正确工作WAL、复制、备份恢复
可扩展性(Scalability)系统在负载增长时仍能保持性能分区、分片、读写分离
可维护性(Maintainability)系统在演化过程中仍可理解和修改Schema 演化、数据迁移工具
Note

这三大挑战并非数据库独有,而是所有数据密集型系统的共性。数据库只是将应对这些挑战的机制”内置”了——你不需要自己实现 WAL 或 MVCC,数据库替你做了。但理解这些机制的原理,才能在出问题时知道从哪里排查。

二、数据模型#

数据模型是数据库的”世界观”——它决定了你如何组织、理解和操作数据。不同的数据模型适合不同的问题域,没有万能模型。

2.1 数据模型关系图#

graph TB CORE["数据模型<br/>组织数据的世界观"] CORE --> REL["关系模型<br/>表 + SQL<br/>MySQL / PostgreSQL"] CORE --> DOC["文档模型<br/>JSON / BSON<br/>MongoDB"] CORE --> GRAPH["图模型<br/>节点 + 边<br/>Neo4j"] CORE --> KV["键值模型<br/>Key → Value<br/>Redis / DynamoDB"] CORE --> TS["时序模型<br/>时间戳 + 度量<br/>InfluxDB"] REL -.->|"多对多<br/>JOIN 复杂"| DOC DOC -.->|"关系密集<br/>退化为关系模型"| REL GRAPH -.->|"属性图<br/>可映射为关系表"| REL KV -.->|"值可以是 JSON"| DOC style CORE fill:#e8eaf6,stroke:#283593 style REL fill:#c8e6c9,stroke:#2e7d32 style DOC fill:#fff3e0,stroke:#e65100 style GRAPH fill:#e1bee7,stroke:#6a1b9a style KV fill:#ffe0b2,stroke:#f57c00 style TS fill:#e0f2f1,stroke:#00695c

2.2 关系模型#

关系模型由 E.F. Codd 于 1970 年提出,是数据库领域最成功的数据模型。核心思想:数据组织为关系(relation),即二维表,每行是一个元组(tuple),每列是一个属性(attribute)。

-- 关系模型:用表表示实体和关系
CREATE TABLE users (
id BIGINT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE
);
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
amount DECIMAL(10,2),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 多对多关系通过中间表实现
SELECT u.name, COUNT(o.id) AS order_count
FROM users u
JOIN orders o ON u.id = o.user_id
GROUP BY u.id;

优势:强 Schema 约束保证数据一致性;SQL 声明式查询,优化器自动选择执行路径;ACID 事务支持;成熟的生态和工具链。

劣势:Schema 不灵活,加列需要 ALTER TABLE;多对多关系需要 JOIN,数据量大时性能堪忧;对象-关系阻抗失配(ORM 的根源)。

2.3 文档模型#

文档模型将数据组织为自包含的文档(通常是 JSON 或 BSON),天然支持嵌套结构,减少了 JOIN 的需求。

// 文档模型:一个订单包含所有相关信息
{
"_id": "order_001",
"user": {
"id": "user_001",
"name": "张三",
"email": "zhangsan@example.com"
},
"items": [
{"product": "数据库系统概念", "price": 89.00, "qty": 1},
{"product": "DDIA", "price": 128.00, "qty": 1}
],
"total": 217.00,
"status": "shipped",
"created_at": "2026-04-22T10:00:00Z"
}

优势:Schema 灵活(Schema-on-Read),适合数据结构频繁变化的场景;嵌套文档减少 JOIN;与前端 JSON 天然契合。

劣势:多对多关系仍需引用 + 应用层 JOIN;嵌套过深导致更新困难;缺乏强一致性约束。

Warning

文档模型最常见的陷阱是”把所有东西都塞进一个文档”。当文档大小增长到 MB 级别时,写入放大、查询性能、内存占用都会急剧恶化。文档模型适合”一对多”关系(如订单-商品项),不适合”多对多”关系(如用户-角色-权限)。

2.4 图模型#

图模型用节点(Node)边(Edge) 表示实体和关系,天生适合多对多关系密集的场景。

// Neo4j Cypher:查询张三的朋友的朋友(二度关系)
MATCH (me:User {name: '张三'})-[:KNOWS]->(friend)-[:KNOWS]->(fof)
WHERE NOT (me)-[:KNOWS]->(fof) AND me <> fof
RETURN DISTINCT fof.name AS recommendation, COUNT(friend) AS mutual_count
ORDER BY mutual_count DESC
LIMIT 10

优势:多对多关系查询极快(无需 JOIN,沿边遍历);关系作为一等公民,语义清晰;社交网络、知识图谱、欺诈检测等场景的天然选择。

劣势:无法用标准 SQL 查询(需学习 Cypher/SPARQL);分布式图查询困难;生态不如关系数据库成熟。

2.5 键值模型#

键值模型最简单:一个键对应一个值,值可以是任意二进制数据。

# Redis 键值操作
SET user:001:name "张三"
GET user:001:name
# → "张三"
# 哈希:模拟对象
HSET user:001 name "张三" email "zhangsan@example.com" age 28
HGET user:001 email
# → "zhangsan@example.com"
# 有序集合:排行榜
ZADD leaderboard 9500 "player_A"
ZADD leaderboard 8700 "player_B"
ZREVRANGE leaderboard 0 9 WITHSCORES

优势:极简的数据模型带来极高的性能(单机百万级 QPS);天然支持分布式(一致性哈希);丰富的数据结构(Redis 的 List/Set/Sorted Set 等)。

劣势:无 Schema,数据一致性靠应用保证;不支持复杂查询(无 WHERE/JOIN);数据关系需要应用层维护。

2.6 时序模型#

时序模型针对带时间戳的度量数据优化:每条记录是一个时间点上的观测值。

-- InfluxDB:写入时序数据
INSERT cpu,host=server01,region=us-west value=0.64 1422568543702900257
-- 查询最近 1 小时的平均 CPU 使用率
SELECT mean(value) FROM cpu
WHERE time > now() - 1h AND host = 'server01'
GROUP BY time(5m)

优势:针对时间范围查询和聚合极度优化;自动数据压缩和降采样;海量写入友好(追加为主)。

劣势:只适合时序数据,通用查询能力弱;数据通常只按时间维度组织;更新和删除历史数据的代价高。

2.7 数据模型对比#

维度关系模型文档模型图模型键值模型时序模型
数据组织二维表JSON 文档节点 + 边Key → Value时间戳 + 度量
关系表达外键 + JOIN嵌套 + 引用边(一等公民)应用层维护通常无关系
Schema强约束灵活灵活半约束
查询能力最强(SQL)中等图遍历最弱时间聚合
典型场景通用业务内容管理、配置社交、知识图谱缓存、计数器监控、IoT
代表产品MySQL, PGMongoDBNeo4jRedis, DynamoDBInfluxDB

三、查询语言#

数据模型决定了数据如何组织,查询语言决定了数据如何被操作。查询语言的选择深刻影响着数据库的优化空间和使用体验。

3.1 声明式 vs 命令式#

声明式查询(Declarative)告诉数据库”我要什么”,不指定”怎么做”:

-- 声明式:只描述目标,不描述过程
SELECT name, age FROM users WHERE age > 30 AND city = '北京' ORDER BY age DESC;

命令式查询(Imperative)告诉数据库”一步步怎么做”:

# 命令式:精确描述执行过程
result = []
for user in users:
if user.age > 30 and user.city == '北京':
result.append(user)
result.sort(key=lambda u: u.age, reverse=True)

为什么声明式更优?

维度声明式(SQL)命令式(代码)
优化空间优化器可自由选择执行路径执行路径由代码固定
并行化优化器可自动并行需手动实现并行逻辑
硬件适配新硬件只需优化器适配需修改所有查询代码
可读性意图清晰淹没在实现细节中
Tip

声明式查询的核心优势在于关注点分离:你只关心”要什么”,数据库负责”怎么最快地给你”。这也是为什么 SQL 历经 50 年仍未被取代——它把优化空间留给了数据库,而不是锁死在应用代码里。将在索引原理中看到,优化器如何在声明式查询的基础上选择最优执行路径。

3.2 MapReduce 的局限#

MapReduce 曾被视为 SQL 的替代方案,但它在表达力和性能上都有局限:

# MapReduce:统计各城市用户数
def mapper(user):
yield (user.city, 1)
def reducer(city, counts):
yield (city, sum(counts))
# 等价 SQL(简洁得多)
# SELECT city, COUNT(*) FROM users GROUP BY city;

MapReduce 的问题:

  • 表达力弱:多步 MapReduce 链式调用极其繁琐,SQL 一句 GROUP BY 搞定
  • 每次操作全表扫描:无法利用索引,无法做查询优化
  • 编程模型笨重:写 Mapper/Reducer 比写 SQL 复杂得多
  • 延迟高:面向批处理设计,不适合交互式查询

现代数据系统(如 Spark SQL、Presto)已经回归声明式查询,MapReduce 作为编程模型已基本退出历史舞台。

3.3 图查询语言#

图数据库有专门的查询语言,最流行的是 Cypher(Neo4j)和 SPARQL(RDF 数据库):

// Cypher:模式匹配,直觉表达图遍历
MATCH (u:User {name: '张三'})-[:PURCHASED]->(p:Product)<-[:PURCHASED]-(other:User)
RETURN other.name, collect(p.name) AS shared_products
ORDER BY size(shared_products) DESC

图查询语言的独特之处在于模式匹配:你用类似 ASCII Art 的语法画出”图的形状”,数据库负责在数据中找到匹配的子图。这比用 SQL 的多重 JOIN 表达图遍历要直观得多。

3.4 为什么声明式语言更适合优化#

声明式查询语言为数据库优化器留出了巨大的操作空间:

  1. 逻辑重写WHERE a=1 AND b=2 可以重写为 WHERE b=2 AND a=1,让选择性更高的条件先过滤
  2. 物理优化:同一个 JOIN 可以选择 Nested Loop、Hash Join 或 Merge Join
  3. 索引选择:优化器根据统计信息选择最优索引
  4. 并行执行:将查询拆分为可并行的子任务

这些优化在命令式查询中都需要开发者手动实现——而人的直觉往往不如优化器的代价模型准确。

四、存储模型#

数据模型决定”数据怎么组织”,存储模型决定”数据怎么存在磁盘上”。同一个逻辑表,行存储和列存储的物理布局完全不同,性能差异可达数量级。

4.1 行存储 vs 列存储#

graph LR subgraph 逻辑表["逻辑表 users"] direction TB R1["id=1, name=张三, age=28, city=北京"] R2["id=2, name=李四, age=35, city=上海"] R3["id=3, name=王五, age=42, city=广州"] end subgraph 行存储["行存储(Row-Oriented)"] direction TB RP1["1,张三,28,北京"] RP2["2,李四,35,上海"] RP3["3,王五,42,广州"] end subgraph 列存储["列存储(Column-Oriented)"] direction TB CID["id: 1,2,3"] CNAME["name: 张三,李四,王五"] CAGE["age: 28,35,42"] CCITY["city: 北京,上海,广州"] end 逻辑表 --> 行存储 逻辑表 --> 列存储 style 逻辑表 fill:#e8eaf6,stroke:#283593 style 行存储 fill:#fff3e0,stroke:#e65100 style 列存储 fill:#e0f2f1,stroke:#00695c

行存储将一行的所有列连续存放,列存储将同一列的所有值连续存放。这个看似简单的差异,导致了截然不同的性能特征。

4.2 为什么分析型查询在列存上更快#

假设执行 SELECT AVG(age) FROM users

行存储需要:

  1. 读取每一行的完整数据(包括不需要的 name、city 等列)
  2. 从中提取 age 列
  3. 计算平均值

列存储只需要:

  1. 读取 age 列的数据(其他列完全不碰)
  2. 计算平均值
# 行存储:读取整行,浪费 I/O
# 假设每行 100 字节,age 列占 4 字节
# 100 万行需要读取 100 MB,但只用到 4 MB
# 列存储:只读 age 列
# 100 万行只需读取 4 MB
# I/O 减少约 25 倍!

列存储对分析型查询的优势不止于减少 I/O:

优化手段行存储列存储
只读需要的列不可能,整行读取天然支持
列内数据压缩困难(类型混合)极高压缩比(同类型连续存储)
向量化执行困难天然适配(列数据连续,SIMD 友好)
聚合计算逐行提取列值直接在列上操作
Note

列存储并非万能。对于点查询(SELECT * FROM users WHERE id = 42)和单行写入,行存储远优于列存储——因为行存储一次 I/O 就能拿到完整的一行,而列存储需要从多个列文件中分别读取再重组。这就是为什么 OLTP 用行存、OLAP 用列存。

4.3 OLTP vs OLAP vs HTAP#

graph TB subgraph OLTP["OLTP — 联机事务处理"] OT1["读少写多"] OT2["点查询为主"] OT3["单行操作"] OT4["延迟敏感"] OT5["行存储"] end subgraph OLAP["OLAP — 联机分析处理"] OA1["读多写少"] OA2["范围扫描为主"] OA3["聚合计算"] OA4["吞吐优先"] OA5["列存储"] end subgraph HTAP["HTAP — 混合事务/分析"] OH1["同时支持 OLTP + OLAP"] OH2["行存 + 列存双引擎"] OH3["实时分析"] OH4["避免数据搬运"] OH5["TiDB / OceanBase"] end OLTP -.->|"数据同步"| OLAP OLTP --> HTAP OLAP --> HTAP style OLTP fill:#e3f2fd,stroke:#1565c0 style OLAP fill:#e8f5e9,stroke:#2e7d32 style HTAP fill:#fff3e0,stroke:#e65100
维度OLTPOLAPHTAP
全称联机事务处理联机分析处理混合事务分析处理
典型操作INSERT/UPDATE/DELETEGROUP BY/JOIN/聚合两者兼有
读写模式读少写多,点查询读多写少,范围扫描混合
存储模型行存储列存储行存 + 列存
延迟要求毫秒级秒级到分钟级事务毫秒级,分析秒级
数据量GB ~ TBTB ~ PBTB 级
代表产品MySQL, PostgreSQLClickHouse, DorisTiDB, OceanBase

传统架构中,OLTP 和 OLAP 是分离的:业务库(MySQL)通过 ETL 管道将数据同步到分析库(ClickHouse)。HTAP 的目标是消除这条 ETL 管道,在同一个数据库中同时支持事务和分析。关于存储引擎如何实现行存和列存,将在存储引擎中深入分析。

五、数据库分类体系#

将数据模型、存储模型、查询语言组合起来,就形成了数据库的完整分类体系。

graph TB DB["数据库"] DB --> RDBMS["关系型数据库<br/>SQL / 行存储"] DB --> NOSQL["NoSQL<br/>非关系型"] DB --> SEARCH["搜索引擎<br/>全文检索"] DB --> NEWSQL["NewSQL<br/>分布式关系型"] RDBMS --> MYSQL["MySQL"] RDBMS --> PG["PostgreSQL"] RDBMS --> ORACLE["Oracle"] NOSQL --> DOC["文档型<br/>MongoDB"] NOSQL --> KV["键值型<br/>Redis / DynamoDB"] NOSQL --> GRAPH["图型<br/>Neo4j"] NOSQL --> TS["时序型<br/>InfluxDB"] NOSQL --> WIDE["宽列型<br/>Cassandra / HBase"] SEARCH --> ES["Elasticsearch"] SEARCH --> SOLR["Solr"] NEWSQL --> TIDB["TiDB"] NEWSQL --> COCK["CockroachDB"] NEWSQL --> OB["OceanBase"] style DB fill:#e8eaf6,stroke:#283593 style RDBMS fill:#c8e6c9,stroke:#2e7d32 style NOSQL fill:#fff3e0,stroke:#e65100 style SEARCH fill:#e1bee7,stroke:#6a1b9a style NEWSQL fill:#e0f2f1,stroke:#00695c

5.1 关系型数据库(RDBMS)#

关系型数据库是 50 年来数据管理的主流方案,以 SQL 为查询语言、以表为数据组织形式、以 ACID 事务为一致性保证。

产品特点适用场景
MySQL生态最成熟、InnoDB 事务支持完善Web 应用、电商、内容管理
PostgreSQL功能最丰富、扩展性最强地理空间、复杂查询、JSON 支持
Oracle企业级功能最全、贵金融、电信、大型企业

5.2 NoSQL 数据库#

NoSQL(Not Only SQL)是对关系型数据库的补充,针对特定场景做了极致优化。

类型代表产品核心优化典型场景
文档型MongoDBJSON 嵌套、灵活 Schema内容管理、配置中心
键值型Redis, DynamoDB极低延迟、高吞吐缓存、会话、计数器
图型Neo4j关系遍历优化社交网络、推荐系统
时序型InfluxDB, TimescaleDB时间聚合、自动降采样监控、IoT、金融行情
宽列型Cassandra, HBase高写入吞吐、线性扩展日志存储、消息历史

5.3 搜索引擎#

搜索引擎本质上是倒排索引驱动的文档数据库,专门优化全文检索:

# Elasticsearch:全文检索 + 聚合分析
curl -X POST "localhost:9200/articles/_search" -H 'Content-Type: application/json' -d '
{
"query": {
"multi_match": {
"query": "数据库性能优化",
"fields": ["title^3", "content"]
}
},
"aggs": {
"by_category": {
"terms": { "field": "category.keyword" }
}
}
}'

5.4 NewSQL#

NewSQL 试图兼顾关系型数据库的 ACID 和 NoSQL 的水平扩展能力:

产品架构特点一致性模型
TiDBRaft 共识 + MVCC + 行列双存强一致性(Raft)
CockroachDBRange 分片 + Raft强一致性(Raft)
OceanBasePaxos + 分区 + 多租户强一致性(Paxos)

六、数据库选型初探#

6.1 选型决策树#

flowchart TB START["选择数据库"] --> Q1{"数据是否有<br/>明确 Schema?"} Q1 -->|是| Q2{"主要操作模式?"} Q1 -->|否| Q3{"数据结构类型?"} Q2 -->|事务 + 点查询| Q4{"数据量级?"} Q2 -->|聚合分析| Q5{"实时性要求?"} Q4 -->|单机可承载| MYSQL["MySQL / PostgreSQL"] Q4 -->|需要水平扩展| NEWSQL["TiDB / OceanBase"] Q5 -->|可接受 T+1| OLAP["ClickHouse / Doris"] Q5 -->|需要实时| HTAP["TiDB HTAP"] Q3 -->|JSON 文档| MONGO["MongoDB"] Q3 -->|键值对| Q6{"延迟要求?"} Q3 -->|图关系| NEO["Neo4j"] Q3 -->|时间序列| INFLUX["InfluxDB"] Q6 -->|微秒级| REDIS["Redis"] Q6 -->|毫秒级| DYNAMO["DynamoDB"] style START fill:#e8eaf6,stroke:#283593 style MYSQL fill:#c8e6c9,stroke:#2e7d32 style OLAP fill:#e0f2f1,stroke:#00695c style MONGO fill:#fff3e0,stroke:#e65100 style REDIS fill:#ffe0b2,stroke:#f57c00 style NEO fill:#e1bee7,stroke:#6a1b9a
Warning

选型决策树只是起点,不是终点。真实的选型还需要考虑:团队技术栈、运维能力、社区生态、成本预算、数据迁移代价。一个”技术上最优”但团队无法驾驭的数据库,比一个”够用”但团队熟悉的数据库更危险。更详细的选型框架参见数据库选型与实践

6.2 CAP 定理简介#

CAP 定理指出,分布式数据系统最多只能同时满足以下三者中的两者:

属性含义示例
C(Consistency)所有节点看到相同的数据线性一致性读
A(Availability)每个请求都能得到响应(不保证最新)节点故障时仍可读写
P(Partition tolerance)网络分区时系统仍能运行网络断开时不宕机

在分布式系统中,网络分区(P)是不可避免的现实,因此实际选择是 CP 还是 AP

  • CP 系统(如 ZooKeeper、etcd):分区时牺牲可用性,保证一致性
  • AP 系统(如 Cassandra、DynamoDB):分区时牺牲一致性,保证可用性
Note

CAP 定理常被过度简化。实际上,C 和 A 并非非此即彼的二选一——在正常情况下(无网络分区),系统可以同时提供一致性和可用性;只有在分区发生时才需要做出取舍。现代分布式数据库(如 CockroachDB、TiDB)通过 Raft 共识协议,在大多数场景下同时提供 C 和 A,只在少数节点故障时短暂牺牲 A。

6.3 多语言持久化(Polyglot Persistence)#

现代系统很少只用一种数据库。多语言持久化是指在同一系统中使用多种数据库,每种负责自己最擅长的领域:

# 一个电商系统的多语言持久化架构
class OrderService:
def __init__(self):
self.mysql = MySQLClient() # 核心业务数据(订单、用户、商品)
self.redis = RedisClient() # 缓存、会话、库存预扣
self.es = ElasticsearchClient() # 商品搜索、日志分析
self.mongo = MongoClient() # 商品详情(Schema 灵活)
self.influx = InfluxDBClient() # 业务监控指标
async def create_order(self, order):
# 1. Redis 预扣库存
await self.redis.decr(f"stock:{order.product_id}")
# 2. MySQL 写入订单(事务保证)
await self.mysql.insert("orders", order)
# 3. ES 更新搜索索引
await self.es.index("orders", order)
# 4. InfluxDB 记录指标
await self.influx.write("order_created", tags={"product": order.product_id})

多语言持久化的代价是运维复杂度:每种数据库都需要监控、备份、升级、排障。在数据库选型与实践中,将讨论如何在”够用”和”最优”之间找到平衡。

七、总结#

维度关系型文档型图型键值型时序型搜索引擎NewSQL
数据模型JSON 文档节点+边Key-Value时间+度量倒排索引
查询语言SQLMongoDB QueryCypherRedis CMDInfluxQLDSLSQL
存储模型行存行存/列存行存内存/磁盘列存倒排索引行存+列存
事务支持ACID单文档ACID有限有限ACID
扩展方式垂直为主水平分片垂直水平水平水平分片水平
一致性强一致最终/强一致强一致最终一致最终一致最终一致强一致
代表产品MySQL, PGMongoDBNeo4jRedisInfluxDBESTiDB

本章建立了理解所有数据库的概念框架:

  • 数据模型决定数据如何组织——关系、文档、图、键值、时序各有适用域
  • 查询语言决定数据如何操作——声明式比命令式留给优化器更大空间
  • 存储模型决定数据如何落盘——行存适合 OLTP,列存适合 OLAP
  • 分类体系将数据模型、存储模型、查询语言组合为完整的数据库图谱
  • 选型决策需要平衡技术需求与团队能力,CAP 定理是分布式选型的理论基石

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

数据库全景:数据模型、存储模型与数据库分类
https://blog.souloss.com/posts/database/database-overview/
作者
Souloss
发布于
2024-05-05
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时