Web 应用很少是完全独立运行的。用户请求经过 Web 服务器和应用程序后,通常还需要读取或修改数据库中的数据。
因此,理解数据库系统不能只停留在“会写 SQL”,还需要知道 DBMS、数据库、表、连接、事务、索引以及关系型与 NoSQL 数据库之间的关系。
本文从数据库系统的整体架构开始,介绍 DBMS、关系型数据库与 NoSQL、客户端与数据库连接、SQL、事务、ACID、并发控制、索引以及 Web 应用中的数据库访问流程,建立对数据库系统的整体认知。
数据库系统是什么
数据库系统(Database System)可以理解为:
用于组织、存储、管理和访问数据的一整套系统。
一个完整的数据库系统通常不仅包括:
数据还包括:
数据库管理系统客户端 / 应用程序数据库用户可以简单表示为:
用户 │ ▼Web Application │ ▼DBMS │ ▼Database │ ▼数据例如一个 Web 网站可能使用:
浏览器 ↓Nginx ↓Java / Python 应用 ↓MySQL / PostgreSQL数据库位于整个 Web 服务体系的后端。
DBMS
什么是 DBMS
DBMS(Database Management System,数据库管理系统)是:
负责创建、管理、访问和维护数据库的软件系统。
例如:
MySQLPostgreSQLMariaDBOracle DatabaseMicrosoft SQL ServerMongoDBRedis都属于数据库系统中的具体产品,但它们的模型和能力并不完全相同。
可以把 DBMS 理解成:
Application │ │ SQL / API ▼ DBMS │ ├── 连接管理 ├── 查询解析 ├── 查询执行 ├── 事务管理 ├── 并发控制 ├── 权限控制 ├── 缓存 ├── 日志 └── 存储管理 │ ▼ Database因此:
数据库是数据本身的组织与存储,而 DBMS 是管理这些数据的软件。
DBMS 与 Database 的区别
这两个概念非常容易混淆。
可以简单理解成:
Database↓数据以及数据组织结构
DBMS↓管理 Database 的软件例如:
MySQL↓DBMS
my_app↓MySQL 中的一个 Database可以进一步理解成:
MySQL Server│├── Database A│├── Database B└── Database C不同 DBMS 对:
DatabaseSchemaTableNamespace等概念的组织方式可能有所不同,因此不能把一种数据库产品的层级结构机械套用到所有数据库上。
数据库系统的基本架构
一个典型 Web 系统可以表示为:
Client │ ▼ Nginx / Apache │ ▼ Web Application │ Database Connection │ ▼ DBMS │ ▼ Database │ ▼ Data例如:
Browser │ │ HTTP ▼Nginx │ │ Proxy ▼Java Application │ │ JDBC ▼MySQL其中:
HTTP↓Web 层通信
JDBC↓Java 应用访问数据库的接口
MySQL↓数据库管理系统所以数据库并不是直接面对浏览器的。
数据模型
数据库首先需要解决:
数据应该以什么方式组织?
不同数据库采用不同的数据模型。
常见的有:
关系模型键值模型文档模型列族模型图模型在 Web 开发中,最常见的两大类可以先理解成:
关系型数据库 +NoSQL 数据库关系型数据库
什么是关系型数据库
关系型数据库(Relational Database)以:
关系(Relation)
为核心组织数据。
实际使用中通常表现为:
Database │ ├── Table │ ├── Row │ └── Column │ └── Table例如用户表:
| id | name | age |
|---|---|---|
| 1 | Alice | 20 |
| 2 | Bob | 21 |
可以理解成:
Table │ ├── Column │ ├── id │ ├── name │ └── age │ └── Row ├── 1 ├── Alice └── 20Table、Row 与 Column
关系型数据库最基础的三个概念:
TableRowColumn可以简单理解:
Table↓一类数据
Row↓一条记录
Column↓某种属性例如:
users是一张用户表。
其中:
idnameemail是列。
1Alicealice@example.com是一行记录。
Primary Key
关系型数据库中的一张表通常需要一种能够唯一标识记录的字段:
Primary Key(主键)
例如:
CREATE TABLE users ( id BIGINT PRIMARY KEY, name VARCHAR(100));这里:
id↓Primary Key主键的核心作用是:
唯一标识表中的一条记录。
例如:
id = 1只能对应一个用户。
Foreign Key
不同表之间通常存在关系。
例如:
usersorders一个用户可以拥有多个订单。
可以设计成:
users┌────┐│ id │└─┬──┘ │ │ 1 │ │ N ▼orders┌─────────┐│ user_id │└─────────┘这里:
orders.user_id可以作为:
Foreign Key(外键)
引用:
users.id从而表达:
用户 ↓订单之间的关系。
SQL
关系型数据库通常使用:
SQL(Structured Query Language)
进行数据定义、查询和修改。
常见操作:
SELECTINSERTUPDATEDELETECREATEALTERDROPSELECT
查询:
SELECT * FROM users;表示:
查询 users 表中的记录也可以只查询部分列:
SELECT id, nameFROM users;INSERT
插入:
INSERT INTO users (id, name)VALUES (1, 'Alice');UPDATE
修改:
UPDATE usersSET name = 'Bob'WHERE id = 1;DELETE
删除:
DELETE FROM usersWHERE id = 1;需要特别注意:
不带正确条件的
UPDATE/DELETE可能影响大量记录。
例如:
DELETE FROM users;就是删除表中全部记录。
因此生产环境执行数据库修改操作时必须谨慎。
NoSQL
什么是 NoSQL
NoSQL 通常泛指:
不以传统关系模型为核心的数据存储系统。
NoSQL 并不是:
“完全不要 SQL”而更接近:
Not Only SQL现代 NoSQL 数据库包含很多不同的数据模型。
例如:
Key-ValueDocumentColumn FamilyGraphKey-Value
键值数据库:
Key ↓Value例如:
user:1001 ↓Alice典型数据库:
Redis非常适合:
缓存Session计数器分布式锁排行榜等场景。
Document Database
文档数据库使用类似:
{ "id": 1, "name": "Alice", "age": 20}这样的文档组织数据。
典型数据库:
MongoDB它适合某些:
结构灵活文档型数据Schema 变化较快的场景。
Column Family
列族数据库常用于:
大规模分布式数据高吞吐写入典型系统包括:
CassandraHBase其数据模型与传统关系型数据库不同。
Graph Database
图数据库重点描述:
Node+Edge例如:
Alice │ │ 好友 ▼Bob │ │ 好友 ▼Carol典型场景:
社交关系知识图谱推荐关系网络拓扑关系型与 NoSQL 的区别
可以从数据模型角度简单对比:
| 对比 | 关系型数据库 | NoSQL |
|---|---|---|
| 核心模型 | 表 / 关系 | 多种模型 |
| Schema | 通常较明确 | 通常更灵活 |
| 查询方式 | SQL | 各产品 API / 查询语言 |
| 事务 | 通常支持成熟 ACID | 取决于具体产品 |
| 数据关系 | 擅长结构化关系 | 取决于模型 |
| 扩展方式 | 常见纵向扩展,也支持分布式方案 | 很多产品强调水平扩展 |
| 典型产品 | MySQL / PostgreSQL | Redis / MongoDB / Cassandra |
需要注意:
不能把“关系型 = 单机、NoSQL = 分布式”简单画等号。
现代关系型数据库同样可以运行在:
主从架构集群分片云数据库环境中。
而 NoSQL 数据库之间的设计差异也非常大。
因此数据库选型应该根据:
数据模型事务要求查询方式一致性要求性能扩展需求运维能力综合判断。
数据库连接
应用为什么需要连接数据库
Web 应用要访问数据库,首先需要:
建立数据库连接。
例如:
Java Application │ │ JDBC ▼ Database连接通常需要:
HostPortDatabaseUsernamePassword例如:
Host = 127.0.0.1Port = 3306Database = appUser = app_userDatabase Connection
一个数据库连接可以简单理解成:
Application │ │ Connect ▼ DBMS │ ▼ Database连接建立后:
Application │ ├── SQL │ ├── Result │ └── Transaction │ ▼ DBMS为什么需要连接池
如果每个 HTTP 请求都:
创建数据库连接 ↓执行 SQL ↓关闭连接那么大量请求会产生:
连接建立开销认证开销Socket 开销数据库资源消耗例如:
1000 Requests │ ├── 建立连接 ├── 建立连接 ├── 建立连接 └── ...因此 Web 应用通常使用:
Connection Pool(连接池)
Connection Pool
连接池提前创建并维护一些数据库连接:
Connection Pool ┌────┬────┬────┐Application ──►│ C1 │ C2 │ C3 │ ├────┼────┼────┤ │ C4 │ C5 │ C6 │ └────┴────┴────┘ │ ▼ DBMS请求到来:
HTTP Request │ ▼Application │ ▼从连接池获取连接 │ ▼执行 SQL │ ▼归还连接而不是:
每次重新建立连接连接池中的重要问题
连接池并不是越大越好。
例如:
Connection Pool = 1000并不意味着:
性能一定更好因为数据库本身也存在:
CPU内存锁连接数磁盘 I/O等资源限制。
如果连接池过大:
Application │ ├── 100 connections ├── 100 connections └── ... ▼Database │ ▼资源耗尽反而可能导致数据库压力增加。
因此:
连接池大小需要结合应用并发量、SQL 耗时以及数据库能力进行配置。
事务
什么是 Transaction
事务(Transaction)可以理解为:
一组需要作为一个逻辑整体执行的数据库操作。
例如银行转账:
A -100B +100不能只执行一半。
应该:
开始事务 │ ▼A -100 │ ▼B +100 │ ▼COMMIT如果中间失败:
ROLLBACK恢复之前的状态。
BEGIN、COMMIT 与 ROLLBACK
事务可以简单理解为:
BEGIN │ ▼SQL 1 │ ▼SQL 2 │ ▼SQL 3 │ ├── 成功 → COMMIT │ └── 失败 → ROLLBACK例如:
START TRANSACTION;
UPDATE accountsSET balance = balance - 100WHERE id = 1;
UPDATE accountsSET balance = balance + 100WHERE id = 2;
COMMIT;如果中间发现错误,可以:
ROLLBACK;ACID
数据库事务经常使用:
ACID
描述事务的重要特性。
A → AtomicityC → ConsistencyI → IsolationD → DurabilityAtomicity
原子性:
一个事务中的操作要么全部成功,要么整体回滚。
例如:
A -100B +100不能只完成:
A -100而:
B +100失败后仍提交。
Consistency
一致性:
事务执行前后,数据库应该满足定义好的完整性约束和业务规则。
例如:
账户余额主键唯一外键约束等。
需要注意:
ACID 中的 Consistency 不只是“所有查询马上看到一样的数据”,而是事务需要把数据库从一个合法状态带到另一个合法状态。
Isolation
隔离性:
并发执行的事务之间应该按照数据库定义的隔离规则互相影响。
例如:
Transaction A │ ├── 修改数据 │Transaction B │ └── 同时访问数据数据库需要控制:
并发读写带来的问题。
Durability
持久性:
事务提交成功后,其结果应该具有持久保存的保证。
例如:
COMMIT ↓数据库系统发生重启 ↓已提交的数据仍然应该被保留具体保障机制涉及:
WALRedo LogFlushStorage等底层机制。
并发事务
现实中的数据库很少只有一个事务。
例如:
User A │ ▼Transaction A
User B │ ▼Transaction B两个事务可能同时操作相同数据。
因此数据库需要:
Concurrency Control(并发控制)
常见并发问题
Dirty Read
事务 A 修改了数据:
A↓修改事务 B 读取到这个尚未提交的数据。
如果 A 最后:
ROLLBACK那么 B 读到的数据就不存在于最终状态中。
Non-Repeatable Read
事务 A 两次读取同一条记录:
第一次↓100
第二次↓200中间被其他事务修改并提交。
于是同一个事务中:
同一查询前后得到不同结果。
Phantom Read
第一次查询:
SELECT ...得到:
10 行之后另一个事务插入满足条件的新记录。
再次执行:
SELECT ...得到:
11 行新增的记录就像:
“突然出现的幻影”
因此称为:
Phantom Read
事务隔离级别
SQL 标准定义了多个事务隔离级别,常见名称为:
READ UNCOMMITTEDREAD COMMITTEDREPEATABLE READSERIALIZABLE可以粗略理解成:
隔离性增强 ↑ │READ UNCOMMITTED │READ COMMITTED │REPEATABLE READ │SERIALIZABLE一般来说:
隔离程度越高,并发事务之间的可见性限制越严格,但并发性能和锁 / 等待代价可能也越高。
不同数据库的具体实现并不完全一样。
例如:
MySQLPostgreSQLOracleSQL Server对隔离级别的实现细节存在差异,因此实际使用时应该参考具体 DBMS 的文档。
索引
什么是 Index
数据库中的索引可以理解成:
为了加快数据查找而建立的额外数据结构。
如果一张表有:
10000000条记录。
查询:
SELECT *FROM usersWHERE email = 'alice@example.com';如果没有合适索引,数据库可能需要:
逐行检查 ↓Row 1Row 2Row 3...Row 10000000这种方式通常称为:
Full Table Scan(全表扫描)
有索引时
假设:
email有索引。
数据库可以:
查询条件 ↓Index ↓定位记录 ↓读取数据可以理解成:
Table│├── id├── name└── email
Index(email) │ ▼ 目标记录B+Tree 索引
关系型数据库中非常常见的一类索引结构是:
B-Tree / B+Tree 家族
它适合:
等值查询范围查询排序例如:
SELECT *FROM usersWHERE id = 100;或者:
SELECT *FROM usersWHERE age BETWEEN 20 AND 30;B+Tree 可以通过树结构减少需要访问的数据范围。
可以简化理解成:
Root / \ Node Node / \ / \ ... ... ... ... │ ▼ Target实际数据库实现远比这个模型复杂,但理解:
索引↓缩小搜索范围就足够建立第一层认识。
Hash Index
另一类常见思路是:
Hash Index
可以通过:
Hash(Key)快速定位。
例如:
email ↓Hash ↓Bucket它适合某些:
等值查询但不适合传统意义上的:
范围查询因此:
WHERE id = 100和:
WHERE id BETWEEN 100 AND 200对索引结构的需求并不完全一样。
为什么不是索引越多越好
索引能够加快查询,但也有成本。
例如:
INSERTUPDATEDELETE发生时,相关索引也需要维护。
因此:
索引增加↓查询可能更快↓写入成本增加↓占用更多存储所以:
索引设计本质上是在查询性能与写入 / 存储成本之间进行权衡。
组合索引
数据库还经常使用:
Composite Index(联合索引)
例如:
CREATE INDEX idx_userON users (name, age);表示索引包含:
nameage两个字段。
联合索引涉及重要的:
最左前缀原则
例如索引:
(name, age)通常更适合:
WHERE name = 'Alice';以及:
WHERE name = 'Alice' AND age = 20;而仅仅:
WHERE age = 20;通常不能充分利用这个联合索引的前导部分。
具体行为仍取决于 DBMS 的优化器和索引实现。
索引与执行计划
数据库并不会因为:
“存在索引”就一定使用索引。
数据库通常会通过:
Query Optimizer(查询优化器)
分析查询。
例如:
SQL ↓Parser ↓Optimizer ↓Execution Plan ↓Executor可能最终选择:
Index Scan也可能选择:
Seq Scan或者其他执行方式。
因此:
判断索引是否真正有效,需要观察执行计划,而不是只看数据库里有没有建立索引。
例如 PostgreSQL:
EXPLAINSELECT *FROM usersWHERE email = 'alice@example.com';MySQL 则可以:
EXPLAINSELECT *FROM usersWHERE email = 'alice@example.com';数据库查询执行
一条 SQL 并不是直接:
SQL ↓Disk而是经过多个步骤。
可以简化为:
SQL │ ▼Parser │ ▼Query Analysis │ ▼Optimizer │ ▼Execution Plan │ ▼Executor │ ▼Storage Engine / Data │ ▼Result例如:
SELECT *FROM usersWHERE id = 100;数据库可能:
解析 SQL ↓识别 users 表 ↓识别 id 条件 ↓检查可用索引 ↓生成执行计划 ↓读取索引 / 数据 ↓返回结果Buffer Cache
数据库并不会每次都直接访问磁盘。
通常会在内存中维护大量缓存:
Application │ ▼ DBMS │ ▼Buffer / Page Cache │ ├── Hit → 直接使用 │ └── Miss │ ▼ Disk因此数据库性能通常不仅取决于:
CPU还取决于:
MemoryDiskIOPSLatencyCache Hit等因素。
这也是数据库性能分析比单纯查看 SQL 更复杂的原因之一。
数据持久化与日志
数据库需要解决一个非常重要的问题:
系统突然崩溃时,已经提交的数据怎么办?
因此现代数据库通常会使用某种日志机制。
例如 PostgreSQL 中的重要机制:
WAL即:
Write-Ahead Logging
核心思想可以简单理解为:
先记录恢复所需的日志 ↓再认为相关数据修改可以安全持久化系统崩溃后,可以根据日志进行恢复。
因此:
Transaction ↓Log ↓Storage是数据库持久化机制中的重要思想。
数据库备份
需要注意:
事务持久性不等于备份。
即使数据库具有:
ACIDWALCrash Recovery也不能解决:
误删逻辑损坏勒索错误 UPDATE错误 DROP等问题。
因此生产环境仍然需要:
数据库备份+恢复测试完整的数据库运维还需要进一步考虑:
全量备份增量备份日志备份恢复RPORTOWeb 应用访问数据库
将前面的概念串起来,一个典型 Web 请求可能是:
Browser │ │ HTTP ▼Nginx │ ▼Web Application │ │ Connection Pool ▼DBMS │ │ SQL ▼Database例如用户访问:
GET /users/100应用可能执行:
SELECT *FROM usersWHERE id = 100;数据库处理:
SQL ↓Parser ↓Optimizer ↓Index ↓Data ↓Result然后:
Database │ ▼Web Application │ ▼HTTP Response │ ▼Browser数据库连接与事务
实际 Web 应用中,数据库访问通常还会包含:
HTTP Request │ ▼Application │ ▼Get Connection │ ▼Begin Transaction │ ├── SQL 1 ├── SQL 2 └── SQL 3 │ ▼Commit / Rollback │ ▼Return Connection │ ▼HTTP Response因此:
HTTP↓Application↓Connection Pool↓Transaction↓SQL↓Index↓Database实际上是一整条完整链路。
数据库常见性能问题
数据库出现性能问题时,通常不能只看:
CPU还应该考虑:
慢 SQL索引缺失索引失效锁等待事务过长连接池耗尽磁盘 I/O缓存命中率数据量增长例如:
请求变慢 │ ▼Application │ ▼等待数据库 │ ▼慢 SQL │ ▼全表扫描 │ ▼Disk I/O 增加这种情况下:
Web Server本身可能完全正常。
真正的瓶颈在:
Database数据库故障排查思路
可以形成一个基本流程:
Web 请求变慢 │ ▼Application │ ▼数据库连接是否正常? │ ▼连接池是否耗尽? │ ▼SQL 是否执行? │ ▼SQL 是否很慢? │ ▼EXPLAIN / Execution Plan │ ▼是否使用合理索引? │ ▼是否存在锁等待? │ ▼数据库 CPU / Memory / I/O │ ▼进一步定位这也是为什么数据库运维不能只停留在:
“会写 SELECT”而需要同时理解:
连接事务锁索引执行计划资源关系型数据库与 Web 服务
在传统 Web 系统中,一个非常典型的架构是:
Browser │ ▼ Nginx │ ▼ Application │ ┌──────┴──────┐ │ │ ▼ ▼ Connection Pool Cache │ ▼ DBMS │ ┌───────┴───────┐ │ │ ▼ ▼ Table Index │ ▼ Data其中:
Nginx↓处理 HTTP / 代理
Application↓处理业务逻辑
Database↓持久化结构化数据
Cache↓减少部分数据库访问这就是常见的:
Web + Application + Database
基本架构。
NoSQL 在 Web 系统中的位置
NoSQL 数据库也经常出现在 Web 系统中。
例如:
Application │ ├── MySQL │ ↓ │ 业务主数据 │ └── Redis ↓ Cache典型组合:
MySQL / PostgreSQL↓核心业务数据
Redis↓缓存 / Session / Counter
MongoDB↓文档型数据因此:
关系型数据库和 NoSQL 并不是一定互相替代。
实际系统中经常是:
Polyglot Persistence即根据不同数据和访问场景选择不同的数据存储系统。
数据库系统整体模型
到这里,可以把数据库系统完整串起来:
Database System │ ┌──────────────────┼──────────────────┐ │ │ │ ▼ ▼ ▼ DBMS Data Model Client │ │ │ ┌─────┴─────┐ ┌────┼────┐ │ │ │ │ │ │ Relational NoSQL Table Document App │ │ │ │ │ │ ┌───┼───┐ │ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ▼ ▼ SQL KV Doc Graph Row Data Connection │ ▼ Transaction │ ├── Atomicity ├── Consistency ├── Isolation └── Durability │ ▼ Index │ ▼ Query Optimization │ ▼ Data Storage可以进一步浓缩成:
Application ↓Connection ↓DBMS ↓Query ↓Transaction ↓Optimizer ↓Index ↓Storage ↓Data从 Web 运维角度理解数据库
对于 Web 运维来说,真正需要建立的并不是:
“数据库就是一个存数据的地方”而是:
请求 ↓应用 ↓数据库连接 ↓事务 ↓SQL ↓查询优化 ↓索引 ↓磁盘 / 内存任何一环出现问题,都可能最终表现为:
页面变慢API 超时请求失败数据库连接耗尽CPU 飙高I/O 飙高例如:
API 响应变慢 │ ▼应用等待 DB │ ▼数据库查询变慢 │ ▼执行计划异常 │ ▼没有使用合理索引 │ ▼大量 I/O因此数据库系统实际上是 Web 服务架构中非常核心的一层。
理解:
DBMS关系型 / NoSQLConnectionTransactionIndexQuery之后,再学习:
MySQLPostgreSQLRedisMongoDB数据库备份主从复制高可用数据库监控慢查询就会有比较清晰的基础。