数据库从零到精通验收路线
这一页用来回答一个很现实的问题:只看完这些文档,算不算真的会数据库?
答案不能靠“我看过 MySQL、Oracle、PostgreSQL、SQL Server 的介绍”来判断。真正学会数据库,要能把业务建模、SQL、索引、执行计划、事务、日志、恢复、复制、高可用、线上排查和面试表达串成闭环。
学数据库不是背名词,而是建立一套判断能力:面对一个业务需求,你能设计表;面对一条慢 SQL,你能解释慢在哪里;面对并发扣库存,你能保证不会错;面对宕机、误删、主从延迟、锁等待,你能知道数据库为什么会这样,以及该怎么恢复。
学完后必须达到什么水平
| 能力 | 合格标准 | 不合格表现 |
|---|---|---|
| SQL 基础 | 能写增删改查、Join、聚合、分页、事务 SQL | 只会单表 select * |
| 表设计 | 能根据订单、支付、库存、采集任务设计字段、约束、索引 | 字段全用字符串,没有唯一约束 |
| 索引 | 能解释 B+Tree/B-tree 家族、联合索引、覆盖索引、回表、索引失效 | 认为“加索引一定快” |
| 执行计划 | 能看懂访问方式、扫描行数、排序、临时表、回表、Join 顺序 | 慢 SQL 只会猜 |
| 事务 | 能解释 ACID、隔离级别、锁、MVCC、死锁、丢失更新 | 只背四个英文单词 |
| 日志恢复 | 能讲清 redo、undo、binlog、WAL、Redo、事务日志分别解决什么 | 以为日志只是记录操作历史 |
| 高可用 | 能理解主从复制、流复制、Data Guard、Always On、备份恢复边界 | 以为主从就是复制一份数据 |
| 线上排查 | 能按慢 SQL、锁等待、连接池、磁盘 IO、复制延迟拆问题 | 只会重启数据库 |
| 数据库选型 | 能说明 MySQL、PostgreSQL、Oracle、SQL Server 的差异和商业边界 | 只说“哪个性能更好” |
| 面试表达 | 标准回答简洁,追问时能跳到原理和案例 | 只背八股,没有解释链路 |
总学习流程
零基础到能上项目,推荐按下面这条线走。不要一上来就背 MVCC,也不要只看面试题。
flowchart TD
A["第一阶段<br/>会写 SQL"] --> B["第二阶段<br/>会设计表"]
B --> C["第三阶段<br/>理解索引"]
C --> D["第四阶段<br/>看懂执行计划"]
D --> E["第五阶段<br/>理解事务并发"]
E --> F["第六阶段<br/>理解日志恢复"]
F --> G["第七阶段<br/>理解复制高可用"]
G --> H["第八阶段<br/>能做线上排查"]
H --> I["第九阶段<br/>能做技术选型"]
I --> J["第十阶段<br/>面试能讲清原理"]这条线背后的原因是:
- 先学 SQL,是因为数据库最终接收到的是 SQL 或协议请求。
- 再学表设计,是因为错误模型会让后续所有查询和事务都变难。
- 再学索引,是因为索引是数据库性能的第一道门。
- 再学执行计划,是因为索引有没有用、慢在哪里都要靠计划验证。
- 再学事务,是因为商业系统最怕数据错。
- 再学日志,是因为提交、回滚、复制、恢复都离不开日志。
- 最后学排查和选型,是因为生产问题通常是多个机制叠加。
第一阶段:SQL 必须学到什么程度
SQL 不是只会查一张表。商业系统常见需求包括:条件筛选、排序、分页、分组统计、Join、去重、存在性判断、批量更新、事务提交。
必会内容
| 知识点 | 要学到什么程度 | 商业场景 |
|---|---|---|
select | 能选择字段、过滤、排序、分页 | 用户列表、资产列表 |
insert | 能单条和批量插入 | 采集结果入库 |
update | 能带状态条件更新 | 支付成功更新订单 |
delete | 知道硬删和软删区别 | 删除草稿、逻辑删除 |
join | 知道 inner/left join 结果差异 | 订单关联用户、资产关联科室 |
group by | 能做聚合统计 | 每日订单量、科室设备数 |
| 子查询 | 知道何时可读、何时可能慢 | 查询有异常记录的设备 |
| 事务 SQL | 知道 begin/commit/rollback | 扣库存、写流水 |
最小 Demo:订单和明细查询
create table order_main (
id bigint primary key,
order_no varchar(64) not null,
user_id bigint not null,
status int not null,
total_amount decimal(12, 2) not null,
created_at timestamp not null
);
create table order_item (
id bigint primary key,
order_id bigint not null,
sku_id bigint not null,
quantity int not null,
price decimal(12, 2) not null
);
select o.order_no, o.total_amount, sum(i.quantity) as item_count
from order_main o
join order_item i on i.order_id = o.id
where o.user_id = 1001
and o.status = 1
group by o.order_no, o.total_amount
order by o.created_at desc;如果这一步都不熟,直接看 MVCC、redo log、WAL 会很虚,因为你还不知道数据库内部在为哪类请求服务。
第二阶段:表设计必须能独立完成
表设计不是把 Java 对象字段原样搬进数据库。你要考虑数据类型、主键、唯一约束、非空、默认值、状态字段、时间字段、审计字段、索引和未来数据增长。
订单表设计示例
create table payment_order (
id bigint primary key,
order_no varchar(64) not null,
user_id bigint not null,
pay_channel varchar(32) not null,
status int not null,
amount decimal(12, 2) not null,
paid_at timestamp null,
created_at timestamp not null,
updated_at timestamp not null,
constraint uk_payment_order_no unique (order_no)
);
create index idx_payment_user_status_time
on payment_order(user_id, status, created_at);为什么要这样设计
| 设计 | 原因 | 不这样会怎样 |
|---|---|---|
order_no 唯一约束 | 防止重复订单号 | 应用并发 Bug 会写入重复订单 |
金额用 decimal | 避免浮点精度误差 | 金额计算出现小数误差 |
| 状态字段单独存 | 状态流转可控 | 无法判断订单生命周期 |
| 时间字段完整 | 方便查询、审计、归档 | 排查问题没有时间线 |
| 联合索引 | 匹配用户订单列表 | 单列索引可能仍然扫描很多行 |
第三阶段:索引要理解到原理层
索引的本质不是“让查询变快的开关”,而是数据库额外维护的一份有序数据结构。它能减少扫描范围,但会占空间,并拖慢写入。
flowchart TD
A["业务 SQL<br/>where user_id and status"] --> B{"有没有匹配索引"}
B -- "有" --> C["沿索引树定位范围"]
C --> D["扫描少量索引项"]
D --> E{"查询字段是否都在索引里"}
E -- "是" --> F["覆盖索引直接返回"]
E -- "否" --> G["通过主键或行定位器回表"]
B -- "没有" --> H["扫描大量数据页"]必须讲清的几个问题
| 问题 | 标准理解 |
|---|---|
| 为什么 B+Tree/B-tree 适合数据库 | 树高低、页内高扇出、key 有序、适合范围扫描 |
| 为什么 Hash 不适合作为通用索引 | 等值快,但不支持范围、排序、前缀匹配 |
| 为什么联合索引有顺序 | B+Tree 按组合 key 排序,前导列决定整体有序性 |
| 覆盖索引为什么快 | 查询字段都在索引里,可以减少回表 |
| 覆盖索引为什么仍可能慢 | 扫描范围很大、深分页、排序、缓存未命中仍会慢 |
| 为什么有索引也可能不用 | 优化器估算全表扫描更便宜、统计信息不准、条件选择性差 |
更多原理看:B+Tree、B-Tree 与数据库索引家族。
第四阶段:执行计划必须会看来源
执行计划不是神秘输出,它来自优化器对统计信息、索引、条件选择性、Join 成本、排序成本的估算。
flowchart TD
A["SQL"] --> B["解析和语义检查"]
B --> C["读取统计信息"]
C --> D["枚举访问路径"]
D --> E["估算扫描行数和成本"]
E --> F["选择执行计划"]
F --> G["执行器按计划访问数据"]MySQL EXPLAIN rows 是怎么来的
rows 不是实际扫描行数,而是优化器根据统计信息估算出来的行数。估算来源包括:
- 表的行数统计。
- 索引基数,也就是不同 key 值的大致数量。
- 条件选择性,比如
status = 1可能命中多少比例。 - 范围条件边界,比如时间范围、主键范围。
- 统计信息是否过期。
如果统计信息不准,rows 就可能偏离真实情况,优化器可能选错索引。详细看:MySQL EXPLAIN。
PostgreSQL EXPLAIN ANALYZE 为什么更适合验证
PostgreSQL 的 EXPLAIN 只看计划,EXPLAIN ANALYZE 会真正执行 SQL 并给出实际耗时和实际行数。它适合验证“优化器估算”和“真实执行”差多少。
explain analyze
select id, asset_no
from asset
where owner_id = 1001
order by created_at desc
limit 20;如果估算 10 行,实际 100000 行,就说明统计信息、数据分布或条件设计有问题。
第五阶段:事务并发必须能解释现象
商业系统里,事务不是为了背 ACID,而是为了让多条 SQL 要么一起成功,要么一起失败,并且并发执行时不把数据改错。
flowchart TD
A["扣库存请求"] --> B["开启事务"]
B --> C["按商品 ID 查询库存并加锁"]
C --> D{"库存是否足够"}
D -- "足够" --> E["扣减库存"]
E --> F["写库存流水"]
F --> G["提交事务"]
D -- "不足" --> H["回滚并返回失败"]必须能解释的问题
| 问题 | 要点 |
|---|---|
| 脏读 | 读到别人未提交的数据 |
| 不可重复读 | 同一事务内两次读同一行结果不同 |
| 幻读 | 同一范围两次读出现新行 |
| 丢失更新 | 两个事务基于同一个旧值更新,后提交覆盖先提交 |
| 死锁 | 两个事务互相等待对方持有的锁 |
| 长事务 | 持锁久、版本无法清理、日志堆积、复制延迟 |
不同数据库实现不同:
| 数据库 | 版本机制重点 |
|---|---|
| MySQL InnoDB | undo log 版本链 + Read View |
| PostgreSQL | heap tuple 多版本 + xmin/xmax + VACUUM |
| Oracle | Undo + SCN 一致性读 |
| SQL Server | 默认锁读,也可用行版本隔离,例如 RCSI |
第六阶段:日志恢复要讲清边界
数据库不会每次提交都把所有数据页完整刷到磁盘。它通过预写日志保证:先把可恢复的信息写安全,再允许数据页后续慢慢刷盘。
flowchart TD
A["事务修改数据"] --> B["生成内存中的脏页"]
B --> C["先写事务日志"]
C --> D["事务提交返回成功"]
D --> E["后台异步刷数据页"]
E --> F["宕机后用日志恢复"]各数据库日志怎么对应
| 数据库 | 关键日志 | 作用 |
|---|---|---|
| MySQL InnoDB | redo log | 崩溃恢复,保证已提交修改能恢复 |
| MySQL | undo log | 回滚和 MVCC 旧版本 |
| MySQL | binlog | 主从复制和时间点恢复 |
| PostgreSQL | WAL | 崩溃恢复、流复制、归档恢复 |
| Oracle | Redo | 崩溃恢复、Data Guard |
| Oracle | Undo | 回滚、一致性读、Flashback 基础 |
| SQL Server | Transaction Log | 回滚、恢复、日志备份、Always On |
这里最容易混淆:日志不是一类东西。redo/WAL/Transaction Log 更偏恢复已提交修改;undo 更偏回滚和版本可见性;binlog 更偏复制和逻辑恢复。
第七阶段:四大关系库差异要能横向比较
不要把 MySQL、PostgreSQL、Oracle、SQL Server 学成四套孤立八股。它们都在解决同一批问题,只是实现取舍不同。
| 维度 | MySQL | PostgreSQL | Oracle | SQL Server |
|---|---|---|---|---|
| 常见业务 | 互联网交易、后台系统 | 复杂 SQL、GIS、JSONB | 金融政企核心 | .NET 企业应用、BI |
| 进程/线程模型 | 多线程服务 | 多进程模型常见 | 实例 + 后台进程 | 服务进程 + 工作线程 |
| 索引叫法 | B+Tree | B-tree | B-tree/Bitmap | 聚集/非聚集 B-tree |
| 版本机制 | undo 版本链 | tuple 多版本 | Undo + SCN | 锁或行版本 |
| 日志 | redo + binlog | WAL | Redo + 归档 | Transaction Log |
| 排查重点 | 慢日志、锁、主从延迟 | VACUUM、膨胀、锁 | 等待事件、AWR、Undo | 等待、TempDB、参数嗅探 |
更多详细对比看:主流关系库差异与选型。
第八阶段:商业项目训练
要判断自己是不是会数据库,不能只看文档。下面这些练习做完,才算真正从“知道概念”走到“能落地”。
本阶段可以直接配合 关系型数据库商业场景训练营 做。训练营把订单支付、医疗资产采集、库存扣减、覆盖索引仍慢、四大数据库建表差异和 UPDATE 原理拆成了可执行步骤;本页负责验收标准,训练营负责动手过程。
练习一:订单支付系统
你要完成:
- 设计订单表、支付表、订单明细表、支付流水表。
- 给订单号、支付流水号加唯一约束。
- 写“创建订单”“支付成功回调”“查询用户订单列表”SQL。
- 给高频查询设计联合索引。
- 用执行计划验证索引是否生效。
- 模拟并发支付回调,保证同一订单只处理一次。
- 解释为什么需要幂等状态条件,例如
where status = 'UNPAID'。
关键点:支付回调不能只靠应用判断,数据库唯一约束和状态更新条件都要兜底。
练习二:医疗数据采集平台
你要完成:
- 设计设备表、采集任务表、采集结果表、异常事件表。
- 设备唯一标识要有唯一约束。
- 采集结果可能是大表,要考虑按时间归档或分区。
- 采集任务要能防重复执行。
- 写“查询某医院某科室最新异常设备”的 SQL。
- 写慢 SQL 后用执行计划定位扫描范围。
- 说明什么时候同步到 Elasticsearch,什么时候仍以关系库为准。
关键点:采集系统写入多、查询维度多,不能只靠单表无限堆数据。
练习三:库存扣减
你要完成:
update inventory
set stock = stock - 1
where sku_id = 10001
and stock > 0;然后解释:
- 为什么
stock > 0要放在 SQL 条件里。 - 为什么要检查影响行数。
- 并发情况下数据库如何加锁。
- 如果事务里还要写流水,事务边界怎么放。
- 如果扣减失败,业务如何返回。
关键点:库存不是先查出来再在 Java 里减完写回那么简单,否则容易丢失更新或超卖。
第九阶段:线上问题排查模板
遇到数据库问题,不要一上来改索引。先分类。
flowchart TD
A["数据库问题"] --> B{"表现是什么"}
B -- "单条 SQL 慢" --> C["看执行计划"]
C --> D["查扫描行数、索引、排序、回表"]
B -- "接口整体慢" --> E["看连接池和数据库等待"]
E --> F["查锁等待、CPU、IO、慢日志"]
B -- "写入慢" --> G["查事务大小和日志刷盘"]
G --> H["查磁盘延迟、复制延迟、大事务"]
B -- "偶发卡死" --> I["查锁、死锁、长事务"]
I --> J["定位阻塞会话和事务边界"]常用判断
| 现象 | 第一怀疑 | 第二怀疑 |
|---|---|---|
| SQL 扫描行数很大 | 索引不匹配 | 条件选择性差 |
| 有索引仍慢 | 回表多、排序、扫描范围大 | 缓存未命中、锁等待 |
| 突然整体慢 | 锁等待、大事务 | 磁盘 IO、连接池耗尽 |
| 从库延迟 | 大事务、从库执行慢 | 并行复制不足 |
| PostgreSQL 表越来越慢 | VACUUM 跟不上 | 长事务阻止清理 |
| SQL Server 偶发慢 | 参数嗅探 | TempDB、Key Lookup |
| Oracle 报快照过旧 | Undo 被覆盖 | 长查询或 Undo 不足 |
第十阶段:面试和知识点如何分开
面试页只应该放标准回答和追问,不应该把所有原理塞在里面。正确方式是:
flowchart TD
A["面试问题"] --> B["30秒标准回答"]
B --> C["追问点"]
C --> D["跳转知识点页"]
D --> E["原理流程图"]
E --> F["Demo和排查案例"]例如面试问:“MySQL 的 redo log 和 binlog 有什么区别?”
标准回答应该简洁:
redo log 是 InnoDB 的物理重做日志,用于崩溃恢复;binlog 是 MySQL Server 层的逻辑日志,主要用于主从复制和时间点恢复。事务提交时需要两阶段提交协调 redo 和 binlog,避免主库恢复结果和从库复制结果不一致。如果追问原理,再跳到:redo log 与 binlog 和 InnoDB 更新事务全过程原理。
学习验收题
如果下面这些问题你能讲清楚,并能写出对应 SQL 或排查命令,数据库主线才算过关。
- 一条
select从客户端到 InnoDB/存储层经历哪些步骤? - MySQL 有几层?三层和五层分别怎么讲?
EXPLAIN rows是实际行数吗?来源是什么?- 覆盖索引为什么仍然可能慢?
- B+Tree 为什么适合 MySQL?Oracle、PostgreSQL、SQL Server 说 B-tree 是什么意思?
- MVCC 为什么能让普通读不阻塞写?
- MySQL undo log 和 MVCC 是什么关系?
- redo log 和 binlog 为什么需要两阶段提交?
- PostgreSQL 为什么需要 VACUUM?长事务会造成什么问题?
- Oracle SCN 在一致性读里有什么作用?
- SQL Server 聚集索引和非聚集索引有什么区别?
- 线上数据库突然慢,你按什么顺序排查?
- ES 和 MySQL 数据一致性失败怎么补偿?
- Redis 和数据库一致性为什么不能靠“同时写成功”解决?
- 分库分表后跨库事务、排序、分页、唯一 ID 怎么处理?
关联学习入口
| 方向 | 入口 |
|---|---|
| 数据库总原理 | 关系型数据库底层原理 |
| 四大关系库对比 | 主流关系库差异与选型 |
| MySQL 主线 | MySQL 从零到生产级掌握 |
| PostgreSQL 主线 | PostgreSQL 从零到生产级掌握 |
| Oracle 主线 | Oracle 从零到生产级掌握 |
| SQL Server 主线 | SQL Server 从零到生产级掌握 |
| Redis 主线 | Redis 从零到生产级掌握 |
| Elasticsearch 主线 | Elasticsearch 从零到生产级掌握 |
数据库从零到精通不是看完一页文档,而是能把“需求怎么建模、SQL 怎么执行、索引为什么生效、事务为什么正确、日志为什么可恢复、线上为什么会慢、不同数据库为什么不同”连成一条线。后续每个具体数据库页都应该围绕这条线展开。
