Appearance
第 6 讲|数据库的出现
数据量少的时候,JSON、CSV、纯文本文件就能解决问题——简单、直接、不需要任何额外组件。但场景一旦升级到"多人同时读写、按条件查询、按时序追加",文件方案会迅速暴露一系列根本性缺陷:并发写入互相覆盖、按条件查找要扫整个文件、误删后恢复困难、字段格式难以统一校验。数据库就是为解决这一系列问题而设计的系统。
本讲从文件方案的局限出发,讲清楚数据库的核心概念:表、SQL、事务、索引、缓存、复制与备份。这些是后续所有业务架构(单机、集群、微服务、分布式)的共同基础。
一、文件方案的局限
把文件方案的问题逐一列出,数据库的价值就一目了然:

| 问题 | 具体表现 |
|---|---|
| 查询慢 | 按条件查找需要从头扫整个文件,百万行级数据基本不可用 |
| 并发难 | 两个进程同时写文件,后写的覆盖先写的,没有并发控制机制 |
| 格式脆弱 | JSON 少一个逗号、CSV 多一个换行,整份文件无法解析 |
| 约束少 | 字段类型、必填、唯一性、外键关联,全靠业务代码自己校验 |
| 恢复麻烦 | 误删一行后只能从备份恢复,且备份可能也已损坏 |
| 事务缺失 | "扣库存 + 写订单" 这种必须整体成功或失败的操作无法保证原子性 |
数据库的"表"看起来与结构化文件类似——都是行和列——但多出了字段类型、约束、索引、事务、查询语言这些能力。这些机制是文件方案天然不具备的。
二、表与 SQL
表的结构
数据库中最基本的概念是表(table)。表由若干列(column)和若干行(row)组成:
| id | name | status | created_at | |
|---|---|---|---|---|
| 1 | alice | alice@example.com | active | 2026-06-10 09:00 |
| 2 | bob | bob@example.com | inactive | 2026-06-10 10:30 |
每一行代表一条记录,每一列代表一个字段。字段在建表时声明类型:id 是整数、name 是字符串、created_at 是时间戳——类型在写入时被强制校验,不符合类型的数据会被拒绝。
SQL:操作数据库的标准语言
SQL(Structured Query Language)是关系型数据库的标准操作语言,核心动词:
| SQL 关键字 | 操作 | 示例 |
|---|---|---|
SELECT | 查询 | SELECT name FROM users WHERE id = 1; |
INSERT | 插入 | INSERT INTO users (name, email) VALUES (...); |
UPDATE | 修改 | UPDATE users SET status = 'inactive' WHERE id = 1; |
DELETE | 删除 | DELETE FROM users WHERE id = 1; |
CREATE TABLE | 建表 | CREATE TABLE users (id INT PRIMARY KEY, ...); |
JOIN | 多表关联 | SELECT u.name, t.title FROM users u JOIN tasks t ON t.user_id = u.id; |
GROUP BY | 聚合 | SELECT status, COUNT(*) FROM users GROUP BY status; |
SQL 语法学起来不难,但写好 SQL 是另一回事——索引设计、查询优化、连接池管理、慢查询定位是数据库相关工作的核心技能,需要长期积累。
数据库的几大类别
根据数据结构和场景,数据库分为几大类:
| 类别 | 代表 | 数据结构 | 典型用途 |
|---|---|---|---|
| 关系型 | MySQL、PostgreSQL、Oracle、SQL Server | 行列表,固定 schema | 用户、订单、权限、审计等核心业务数据 |
| 文档型 | MongoDB、CouchDB | JSON 文档 | 内容管理、嵌套结构、schema 易变化 |
| 键值型 | Redis、Memcached | key-value | 缓存、会话、计数器、消息队列 |
| 列式 | ClickHouse、Cassandra | 按列存储 | OLAP 分析、大数据聚合 |
| 检索型 | Elasticsearch、OpenSearch | 倒排索引 | 全文检索、日志检索 |
| 时序型 | InfluxDB、TimescaleDB、Prometheus | 时间戳 + 标签 + 数值 | 监控指标、IoT |
| 图数据库 | Neo4j、JanusGraph | 节点 + 边 | 社交关系、推荐、风控 |
业务系统通常同时使用多种数据库:MySQL 存核心业务、Redis 做缓存、Elasticsearch 做搜索、ClickHouse 做分析,各自负责擅长的场景。
三、事务:必须整体成功或失败
事务(Transaction)是数据库最核心的能力之一。它解决的问题是:一组操作必须整体成功或整体失败,不能停在中间状态。

转账场景的经典例子
最经典的例子是转账:A 账户扣 100,B 账户加 100。如果扣完 A 的钱程序崩溃,B 那边没加上,钱凭空消失了 100——这种状态在金融系统里是绝对不能出现的。
事务把"扣 A + 加 B"包在一起,要么两步都成功提交,要么任何一步失败就整体回滚:
sql
BEGIN; -- 开始事务
UPDATE accounts SET balance = balance - 100 WHERE id = 'A';
UPDATE accounts SET balance = balance + 100 WHERE id = 'B';
COMMIT; -- 全部成功,提交
-- 或
ROLLBACK; -- 任一步失败,回滚ACID 四特性
事务保证的四个特性合称 ACID:
| 特性 | 含义 |
|---|---|
| Atomicity 原子性 | 一组操作整体成功或整体失败,不会停在中间状态 |
| Consistency 一致性 | 操作前后数据必须满足所有约束(外键、唯一、检查约束) |
| Isolation 隔离性 | 并发事务之间的影响受控制,看起来像串行执行 |
| Durability 持久性 | 提交后的数据不因进程崩溃或断电丢失 |
隔离级别:并发事务的可见性控制
并发执行的事务之间,如何看到对方的修改,由隔离级别控制。SQL 标准定义了四种:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 性能 |
|---|---|---|---|---|
| Read Uncommitted | 可能 | 可能 | 可能 | 最高 |
| Read Committed | 不会 | 可能 | 可能 | 高 |
| Repeatable Read | 不会 | 不会 | 可能 | 中 |
| Serializable | 不会 | 不会 | 不会 | 最低 |
MySQL InnoDB 默认 Repeatable Read,PostgreSQL 默认 Read Committed。隔离级别越高,并发性能越差——通常不动默认值,有特殊需求时显式调整。
业务场景中需要事务的常见动作
- 金融:转账、扣款、结算
- 电商:扣库存 + 生成订单 + 写入支付流水
- 任务系统:创建任务 + 写审计日志 + 发送通知
- 配置变更:更新配置 + 记录版本号
- 审批流:状态变更 + 写入审批历史
凡是"一组关联操作不能停在中间状态"的场景,都需要事务包裹。
四、索引

索引是数据库的查找目录
索引(Index)是数据库为某些字段建立的额外查找结构,作用类似于书的目录——没有索引,找一个用户要翻整本书(全表扫描);有索引,直接定位到第几页(索引查找)。
最常见的索引数据结构是 B+ 树(MySQL、PostgreSQL 默认),还有哈希索引、全文索引、空间索引等。索引的本质是用额外的存储和写入开销,换取查询速度。
索引带来的性能差异
一个具体例子:任务表 100 万行,接口要查询"状态为 failed 且最近 1 天创建"的任务。
sql
SELECT * FROM tasks WHERE status = 'failed' AND created_at >= '2026-06-22';无索引时:数据库扫描全表 100 万行,逐条判断,通常需要数秒。
加索引后:
sql
CREATE INDEX idx_status_created ON tasks (status, created_at);数据库通过索引直接定位到满足条件的行,通常在几十毫秒内返回。
这种"加索引后接口从 5 秒变成 50 毫秒"的提升在慢 SQL 优化中非常常见——绝大多数性能问题的根因都是缺少合适的索引。
索引不是越多越好
索引带来代价:
| 代价 | 影响 |
|---|---|
| 占用磁盘空间 | 每个索引都是一份额外的数据 |
| 拖慢写入 | INSERT/UPDATE/DELETE 时必须同步维护所有相关索引 |
| 占用内存 | 查询时索引需要加载到内存才高效 |
一般原则:
- 经常出现在 WHERE、JOIN、ORDER BY 中的字段适合建索引
- 极少查询、频繁更新的字段不适合建索引
- 重复值很多的字段(如性别)单独建索引意义不大
- 联合索引比多个单字段索引更高效——但联合索引有"最左前缀"原则(详见 MySQL 文档)
慢查询排查的标准流程
接口慢且怀疑数据库时,标准排查路径:
sql
-- 1. 看执行计划,确认是否走索引
EXPLAIN SELECT * FROM tasks WHERE status = 'failed' AND created_at >= '2026-06-22';
-- 看 type 列:ALL 全表扫描、index 索引扫描、range 范围扫描、ref 索引等值
-- 2. 看慢查询日志(MySQL)
SHOW VARIABLES LIKE 'slow_query_log%';
-- 3. 看当前正在执行的慢查询
SHOW FULL PROCESSLIST;绝大多数慢查询的根因是:应该走索引但走了全表扫描——执行计划的 type = ALL 是最直接的信号。
五、缓存
数据库再快也有上限。有些数据被频繁读取但很少变化,每次都查数据库会造成不必要的负担。缓存就是把热数据放在更快的存储(通常是 Redis)中,应用先查缓存,缓存未命中再查数据库并回填缓存。
适合缓存的数据特征
- 读多写少 — 读写比例 10:1 以上
- 变化不频繁 — 几分钟到几小时不变
- 可容忍轻微延迟 — 缓存与数据库可能短暂不一致
典型场景:用户权限、配置项、字典表、热门商品、排行榜、统计接口的聚合结果。
不适合缓存的数据
- 强一致要求的数据(如账户余额)
- 写多读少的数据
- 数据量极大且访问随机(命中率低)
缓存引入的四个经典问题
| 问题 | 含义 | 处理 |
|---|---|---|
| 缓存穿透 | 大量查询根本不存在的数据,缓存里没有,全部打到数据库 | 缓存空值(短 TTL)、布隆过滤器 |
| 缓存击穿 | 某个热点 key 突然过期,瞬间大量请求同时查数据库 | 互斥锁、热点 key 永不过期 |
| 缓存雪崩 | 大量 key 同时过期(或缓存整体挂),数据库压力暴涨 | 过期时间加随机抖动、多级缓存、降级 |
| 数据不一致 | 数据库更新了但缓存还是旧值 | 主动失效、延迟双删、订阅 binlog 同步 |
缓存的核心原则:缓存只是优化手段
缓存绝不能作为唯一数据来源。缓存里丢了数据,系统必须能从数据库恢复出来。把数据只放缓存(不持久化到数据库),Redis 重启或数据被淘汰后业务就瘫了——这不是合理的架构,而是把缓存当数据库用的滥用。
业务架构的不变量:数据库是事实来源(source of truth),缓存只是临时副本。
六、复制与备份
数据库的高可用和容灾,主要依赖以下三种机制:

| 机制 | 解决什么问题 |
|---|---|
| 主从复制 | 主库故障时从库可接管;读请求可分摊到从库 |
| 备份 | 误删、表损坏、机房整体故障后能恢复数据 |
| binlog / WAL | 记录所有变更过程,支持基于时间点恢复(PITR)与复制 |
主从复制:实时同步
主从复制的工作原理:主库执行的每一次数据变更,通过 binlog(MySQL)或 WAL(PostgreSQL)同步到从库,从库重放这些变更以保持数据一致。
主从复制的两个核心用途:
- 故障切换:主库挂了,从库提升为主继续提供服务,业务停机时间可控
- 读写分离:写请求只给主库,读请求分摊到多个从库,扩展读容量
备份:防御数据损坏
备份是把数据库的某一时刻状态完整保存到独立存储(对象存储、磁带、远程机房)。备份的核心价值是应对"数据损坏"和"误操作"——这些场景下复制完全无效。
常见备份策略:
| 策略 | 频率 | 保留 |
|---|---|---|
| 全量备份 | 每日 / 每周 | 数天至数月 |
| 增量备份 | 每小时 / 每天 | 配合全量恢复 |
| binlog 持续归档 | 实时 | 至少最近一周 |
复制不能替代备份
这是必须记住的核心认知:主从复制是异步的(或半同步)——主库上误删一张表 DROP TABLE users,这个 DROP 操作会被同步到所有从库,所有副本里这张表都会被删除。
这种场景下只有从备份恢复才是出路。复制无法防御:
- 误操作:
DROP DATABASE、DELETE未带 WHERE、UPDATE影响范围错 - 应用 bug 写脏数据
- 安全事件:被入侵后批量删除/篡改数据
- 表结构损坏
备份不能替代高可用
反过来,备份恢复需要时间——几十 GB 的数据库恢复可能需要数十分钟到几小时,期间业务完全不可用。
如果业务对可用性有要求(如 99.95%),光靠"挂了从备份恢复"远远不够——必须有主从切换、集群方案,保证故障时分钟级甚至秒级切换。
生产环境的标准配置
生产数据库的合理配置组合:
- 主从复制 — 保证主库故障时分钟级切换
- 定期全量备份 + 增量 + binlog 归档 — 保证误操作时可恢复到任意时间点
- 备份异地存储 — 防御机房级故障
- 定期演练恢复 — 确认备份是真的能用(很多团队备份从没成功恢复过)
备份未演练 = 没有备份——只有真正恢复过的备份才是可靠的备份。这是数据库容灾里最容易被忽略但最重要的原则。
三种机制的覆盖关系
| 故障类型 | 主从复制 | 备份 | binlog |
|---|---|---|---|
| 主库进程崩溃 | ✓ 切换从库 | ✗ 太慢 | ✗ 单独无用 |
| 主库硬件故障 | ✓ 切换从库 | △ 可恢复但慢 | ✗ |
| 误删数据 | ✗ 复制把删除同步走了 | ✓ 从备份恢复 | ✓ PITR 恢复到删除前 |
| 表结构损坏 | ✗ 同上 | ✓ | ✗ |
| 机房整体故障 | △ 需要异地从库 | ✓ 异地备份 | ✓ |
| 入侵后数据篡改 | ✗ | ✓(但需保留时间够长) | ✓ |
三种机制必须同时存在,任一缺失都会让某类故障无法处理。