Skip to content

关系型数据库底层原理从零到生产级掌握

这一页解决一个核心问题:为什么你学了 MySQL、PostgreSQL、Oracle、SQL Server 的几个零散命令,仍然觉得“不知道数据库到底怎么工作”。

关系型数据库不是“存表格的工具”这么简单。它要在并发读写、机器宕机、磁盘很慢、数据越来越大、业务不能出错的情况下,同时做到:

  1. 数据能正确保存。
  2. SQL 能查到正确结果。
  3. 多人同时操作不会互相弄乱。
  4. 机器宕机后能恢复。
  5. 数据量很大时仍然尽量快。
  6. 出问题时能定位原因。

一句话建立主线:

关系型数据库 = SQL 层 + 优化器 + 执行器 + 存储结构 + 缓存 + 锁/MVCC + 事务日志 + 恢复复制能力。

只会写 SQL 是第一层;能解释每一层为什么存在、怎么配合、出了问题怎么排查,才算真正学懂。

学习目标

学完这一页,你要能回答:

  1. 数据库为什么不能只是把数据写到文件里。
  2. 一条 SQL 从客户端发出后经过哪些层。
  3. SQL 为什么需要解析、绑定、优化和执行计划。
  4. 为什么“有索引”不等于“一定快”。
  5. 为什么 MySQL 常说 B+Tree,而 Oracle、PostgreSQL、SQL Server 常说 B-tree。
  6. 为什么事务需要 undo、redo、WAL、Transaction Log 这类日志。
  7. MVCC 为什么能让读写尽量不互相阻塞。
  8. 锁和 MVCC 分别解决什么问题。
  9. 执行计划的行数、成本、访问路径从哪里来。
  10. 单表数据大、覆盖索引仍然慢时该怎么拆解。
  11. 不同数据库学习重点为什么不一样。

先用生活例子理解数据库

假设医院有一个资产管理系统,记录设备:

asset_idhospital_idnamestatusupdated_at
1001H001CT 设备正常2026-07-05
1002H001心电监护仪维修中2026-07-05

如果数据只放在 Excel 里,会遇到:

问题后果
多个人同时改后保存的人覆盖前面的人
文件损坏资产数据丢失
数据很多打开、筛选、统计都慢
没有约束同一个设备编号可能重复
查找依赖人工查询条件复杂时很难稳定返回
没有审计不知道谁在什么时候改了什么

数据库就是为了解决这些问题。它不仅保存数据,还负责约束、并发、查询、恢复、权限、优化和审计。

数据库整体分层

商业系统访问数据库时,通常经过这些层:

mermaid
flowchart TD
    A["应用程序<br/>Controller / Service"] --> B["ORM / JDBC<br/>MyBatis、JPA、JDBC"]
    B --> C["连接池<br/>HikariCP 等"]
    C --> D["数据库连接层<br/>认证、权限、会话"]
    D --> E["SQL 层<br/>解析、绑定、重写"]
    E --> F["优化器<br/>选择执行计划"]
    F --> G["执行器<br/>按计划读取和计算"]
    G --> H["存储引擎<br/>页、索引、表、缓存"]
    H --> I["事务层<br/>锁、MVCC、隔离级别"]
    I --> J["日志与磁盘<br/>数据文件、事务日志、复制日志"]

每一层都有存在的理由:

做什么没有这一层会怎样
连接层管理连接、权限、会话变量谁都能访问,连接混乱
SQL 层理解 SQL 语义数据库不知道你要查什么
优化器选择成本较低的执行方式每条 SQL 都只能傻扫表
执行器按计划执行过滤、排序、Join查询无法真正落地
存储层管理页、索引、缓存、磁盘数据无法高效组织
事务层保证并发正确性扣款、库存、状态容易错乱
日志层支持回滚、恢复、复制宕机后不知道哪些数据该保留

一条 SELECT 的全过程

以这个 SQL 为例:

sql
select id, asset_code, name
from asset
where hospital_id = 'H001'
  and status = 1
order by updated_at desc
limit 20;

它不是直接“去文件里找”。数据库会经历下面的过程:

mermaid
flowchart TD
    A["客户端发送 SQL"] --> B["权限和连接检查"]
    B --> C["解析 SQL<br/>生成语法树"]
    C --> D["语义绑定<br/>表、列、类型是否存在"]
    D --> E["SQL 重写<br/>常量折叠、条件转换"]
    E --> F["优化器估算成本"]
    F --> G["选择索引和 Join 顺序"]
    G --> H["生成执行计划"]
    H --> I["执行器读取索引或表页"]
    I --> J["检查可见性和过滤条件"]
    J --> K["排序、分页、返回结果"]

第一步:解析 SQL

数据库先把字符串 SQL 解析成内部结构。比如:

sql
where hospital_id = 'H001' and status = 1

会被理解成:

  1. 有两个过滤条件。
  2. 两个条件之间是 and
  3. hospital_id 是字段名。
  4. 'H001' 是字符串常量。
  5. status 是字段名。
  6. 1 是数字常量。

如果 SQL 写错:

sql
select from asset where;

解析阶段就会失败,根本不会进入执行。

第二步:语义绑定

解析只知道“像不像 SQL”,语义绑定要检查:

  1. asset 表是否存在。
  2. hospital_id 字段是否存在。
  3. status = 1 类型是否能比较。
  4. 当前用户是否有权限查询这些字段。

如果字段写错:

sql
select asset_name_xxx from asset;

语义绑定阶段就会报错。

第三步:优化器选择计划

同一个 SQL 可能有很多执行方式:

方式说明可能问题
全表扫描从头到尾读整张表大表非常慢
单列索引先按一个条件定位可能过滤不够精准
联合索引按多个条件定位并利用排序索引顺序要求高
索引扫描后回表先查索引,再查主表数据回表次数多会慢
覆盖索引查询列都在索引里扫描范围大仍然慢

优化器会根据统计信息估算成本。它不是“绝对聪明”,而是“根据已有统计信息猜一个最划算的方案”。

执行计划的参数从哪里来

很多人看 EXPLAIN 只背字段含义,但不知道这些值怎么来的。

执行计划主要依赖:

来源说明影响
表统计信息表大概多少行、页数多少判断全表扫描成本
索引统计信息索引基数、选择性、层级判断索引是否划算
字段分布某些值是否特别集中判断过滤后剩多少行
SQL 条件等值、范围、排序、分页、Join决定可用访问路径
成本模型CPU、IO、内存、随机读估算决定最终计划
参数值查询具体传什么值可能导致计划变化

以 MySQL EXPLAINrows 为例,它不是“真实返回行数”,而是优化器估算需要扫描的行数。估算来自统计信息和索引选择性。

sql
explain
select *
from asset
where hospital_id = 'H001'
  and status = 1;

如果 hospital_id = 'H001' 的资产占全表 60%,status = 1 又占 80%,优化器可能认为这个条件过滤效果一般。即使有索引,也可能因为回表成本太高而选择全表扫描。

为什么估算会错

原因例子后果
统计信息过期刚导入大量数据但没刷新统计rows 估算严重偏差
数据倾斜90% 数据 status 都是 1以为索引过滤好,实际很差
字段相关性hospital_id 和 department_id 强相关独立估算会错
函数表达式date(created_at)索引可能失效或估算困难
参数嗅探SQL Server 中某参数生成的计划被复用换参数后计划不适合

所以线上优化不能只看“有没有索引”,要看“扫描了多少、回表多少、排序多少、等待多少”。

表、页、行:数据到底怎么放

数据库不会每次读取一行都直接读磁盘上的一个独立小文件。大多数关系型数据库会把数据组织成“页”。

mermaid
flowchart TD
    A["表 Table"] --> B["数据页 Page / Block"]
    B --> C["行 Row / Tuple"]
    C --> D["字段 Column"]
    B --> E["页头、槽位、校验信息"]

为什么要有页?

  1. 磁盘和内存更适合按块读写,不适合每次只读几个字节。
  2. B-tree/B+Tree 索引节点通常也是页。
  3. 缓存管理以页为单位,命中后可以复用。
  4. 日志和恢复也常围绕页修改来组织。

不同数据库页大小不同:

数据库常见页/块大小说明
MySQL InnoDB16KB数据页、索引页常见大小
PostgreSQL8KBheap page、index page
Oracle常见 8KB,也可配置block 是核心单位
SQL Server8KBpage 是基本存储单位

为什么需要缓存池

磁盘比内存慢很多。如果每次查询都直接读磁盘,数据库会非常慢。

数据库会把常用页缓存到内存:

数据库缓存名称
MySQL InnoDBBuffer Pool
PostgreSQLShared Buffers + OS Page Cache
OracleDatabase Buffer Cache
SQL ServerBuffer Pool

查询过程简化如下:

mermaid
flowchart TD
    A["执行器需要读取某个页"] --> B{"缓存池里有吗"}
    B -- "有" --> C["直接从内存读取"]
    B -- "没有" --> D["从磁盘读入缓存池"]
    D --> E["可能淘汰冷页"]
    C --> F["返回行数据或索引项"]
    E --> F

这解释了为什么同一条 SQL 第一次慢、第二次快:第一次可能读磁盘,第二次命中缓存。

也解释了为什么大查询危险:它可能把大量冷数据读进缓存池,挤掉热点页,导致其他业务变慢。

索引为什么能快

索引的核心价值不是“神奇加速”,而是减少扫描范围。

没有索引:

text
要找 hospital_id = H001 的资产
只能从第一行扫到最后一行

有有序索引:

text
先在索引树中定位 H001 的范围
再只扫描这个范围内的记录
mermaid
flowchart TD
    A["查询条件"] --> B{"有没有匹配索引"}
    B -- "没有" --> C["扫描大量表页"]
    B -- "有" --> D["沿索引树定位范围"]
    D --> E["扫描少量叶子节点"]
    E --> F{"查询列是否都在索引里"}
    F -- "是" --> G["覆盖索引返回"]
    F -- "否" --> H["按主键或行地址回表"]

B+Tree、B-tree、B-Tree 到底怎么理解

先澄清叫法:

  1. B-tree 是一类多路平衡搜索树的总称。
  2. B+Tree 是 B-tree 家族里的常见变体,数据集中在叶子节点,叶子节点之间有顺序链路,范围扫描友好。
  3. 很多数据库官方文档说 B-tree index,并不等于它使用教科书里最简单的 B 树形态,也不等于二叉树。

MySQL 为什么常强调 B+Tree

MySQL InnoDB 聚簇索引中:

  1. 主键索引叶子节点保存完整行数据。
  2. 二级索引叶子节点保存二级索引列和主键值。
  3. 范围查询可以沿叶子节点顺序扫描。
mermaid
flowchart TD
    A["根节点<br/>索引键范围"] --> B["中间节点"]
    A --> C["中间节点"]
    B --> D["叶子节点<br/>key + row 或 primary key"]
    B --> E["叶子节点<br/>key + row 或 primary key"]
    C --> F["叶子节点<br/>key + row 或 primary key"]
    D --> E
    E --> F

为什么适合数据库:

特点好处
多路分叉树高低,磁盘 IO 次数少
有序支持范围查询和排序
叶子链表范围扫描连续向后读
页结构和磁盘/缓存页天然匹配

为什么不用普通二叉树

普通二叉树每个节点最多两个孩子。数据量大时树很高:

text
1000万数据,二叉树可能需要很多层
每层一次随机 IO,成本很高

数据库更喜欢多路树,一个页里放很多 key,一次 IO 可以读取很多分支信息,树高更低。

Oracle、PostgreSQL、SQL Server 为什么说 B-tree

它们官方通常使用 B-tree index 这个术语,强调的是“平衡、有序、多路索引结构”。实现细节与 InnoDB 不完全相同:

数据库B-tree 索引叶子通常指向
PostgreSQLheap tuple 的位置,访问时还要做 MVCC 可见性判断
OracleROWID,ROWID 能定位数据块和行
SQL Server 非聚集索引聚集键或 RID
MySQL InnoDB 二级索引主键值,再通过主键回表

所以不要机械说“只有 MySQL 用 B+Tree,其他数据库用普通 B 树”。更准确的回答是:

text
主流关系型数据库普遍使用 B-tree 家族的平衡多路索引结构。MySQL InnoDB 常用 B+Tree 这个说法,是因为其叶子节点保存数据或主键并适合范围扫描。Oracle、PostgreSQL、SQL Server 官方常称 B-tree index,但实现也围绕页、平衡树、叶子项和行定位信息展开。

为什么有索引仍然慢

索引只解决“怎么更快定位候选数据”,不保证最终一定快。

场景为什么慢
扫描范围太大命中了索引,但符合条件的数据太多
回表太多二级索引找到很多主键,再随机查主表
排序不能利用索引需要额外 filesort / sort
临时表group by、distinct、复杂 join 产生中间结果
函数包裹字段索引无法按原始有序结构定位
低区分度字段status 只有几个值,过滤效果差
锁等待SQL 本身不慢,但等别人释放锁
缓存未命中大量随机 IO
返回列太多网络传输和对象构建也耗时

覆盖索引仍然慢怎么办

覆盖索引只减少回表,不减少所有成本。

比如:

sql
select user_id, status, created_at
from order_main
where status = 1
order by created_at desc
limit 100000, 20;

即使有覆盖索引 (status, created_at, user_id),仍然可能慢,因为:

  1. status = 1 命中范围太大。
  2. 深分页要跳过前 100000 条。
  3. 扫描大量索引叶子节点。
  4. 如果并发高,缓存和 CPU 都会被消耗。

优化思路:

方法原理
Seek Method用上一页最后一条的排序键继续查,避免大 offset
增加更精准条件按用户、租户、时间范围缩小扫描
冷热分离历史数据归档,在线表只保留热数据
分区或分表减少单次扫描数据范围
搜索视图复杂筛选分页同步到 ES
预聚合报表类需求提前汇总

事务为什么存在

事务是为了把多条操作变成一个业务整体。

例如支付成功:

sql
update payment set status = 'SUCCESS' where pay_no = 'P001';
update order_main set status = 'PAID' where order_no = 'O001';
insert into account_log (...) values (...);

这三步必须一起成功或一起失败。否则会出现:

失败点后果
支付成功但订单未更新用户付了钱但订单仍待支付
订单更新但流水没写对账查不到依据
流水写了但事务失败没回滚账务错误

事务的 ACID:

特性解释
Atomicity 原子性要么都成功,要么都失败
Consistency 一致性事务前后满足业务和约束规则
Isolation 隔离性并发事务之间不能互相破坏
Durability 持久性提交后宕机也不能丢

undo、redo、WAL、binlog、Transaction Log 怎么区分

数据库日志经常让人混乱,因为不同数据库名字不同。

先按用途理解:

用途解决什么问题常见名称
回滚事务失败后恢复旧值undo、回滚段
MVCC 旧版本快照读需要读旧版本undo 或 tuple 版本
崩溃恢复宕机后重做已提交修改redo、WAL、Transaction Log
复制同步把变更传给副本binlog、WAL、redo/归档日志、transaction log
时间点恢复恢复到某个时间binlog、WAL archive、归档日志

MySQL 的典型日志关系

mermaid
flowchart TD
    A["执行 UPDATE"] --> B["生成 undo<br/>用于回滚和 MVCC"]
    B --> C["修改 Buffer Pool 中的数据页"]
    C --> D["写 redo prepare<br/>用于崩溃恢复"]
    D --> E["写 binlog<br/>用于复制和恢复"]
    E --> F["redo commit"]
    F --> G["事务提交成功"]

如果没有 redo:

  1. 提交后数据页还没刷盘。
  2. 机器宕机。
  3. 数据库不知道哪些页修改应该恢复。

如果没有 undo:

  1. 事务执行一半失败。
  2. 数据库不知道怎么恢复旧值。
  3. 快照读也缺少旧版本来源。

如果没有 binlog:

  1. 从库无法按事务复制。
  2. 无法做基于 binlog 的时间点恢复。
  3. CDC 同步链路缺少变更来源。

MVCC 到底解决什么

如果没有 MVCC,读和写很容易互相阻塞。

场景:

  1. 事务 A 正在更新资产状态。
  2. 事务 B 只是查询资产列表。
  3. 如果 B 必须等 A 提交才能读,查询并发会很差。

MVCC 的目标是:

让读操作尽量读取一个一致的历史版本,而不是总被写操作阻塞。

简化流程:

mermaid
flowchart TD
    A["事务开始"] --> B["生成快照<br/>记录哪些事务可见"]
    B --> C["读取某一行"]
    C --> D{"当前版本是否对快照可见"}
    D -- "可见" --> E["返回当前版本"]
    D -- "不可见" --> F["查找旧版本"]
    F --> D

不同数据库实现不同:

数据库MVCC 旧版本来源
MySQL InnoDBundo log 版本链
PostgreSQL表中保留多个 tuple 版本,VACUUM 后清理
OracleUndo + SCN 一致性读
SQL Server可使用 tempdb 版本存储支持行版本隔离

锁和 MVCC 的关系

MVCC 不是不要锁。它主要优化“读写并发”,但写写冲突仍然要靠锁。

场景主要机制
普通一致性读MVCC
更新同一行行锁
防止插入破坏范围条件间隙锁 / 范围锁等实现
表结构变更元数据锁
唯一约束冲突唯一索引 + 锁

比如两个用户同时扣库存:

sql
update sku_stock
set stock = stock - 1
where sku_id = 1001 and stock > 0;

数据库必须保证同一行库存不能被两个事务同时无序修改,否则会超卖。这时只靠 MVCC 不够,必须有写锁控制。

隔离级别为什么有多个

隔离越强,并发越低;隔离越弱,异常越多。数据库提供多个隔离级别,是让业务在正确性和性能之间选择。

隔离级别可能解决可能代价
Read Uncommitted几乎不等别人提交可能脏读
Read Committed不读未提交数据同一事务两次读可能不同
Repeatable Read同一事务多次快照读一致实现更复杂,可能更多版本保留
Serializable最像串行执行并发最低,冲突多

商业系统不要只背定义,要结合业务:

业务关注点
订单支付状态流转和幂等比盲目提高隔离级别更重要
库存扣减条件更新、行锁、唯一流水、防超卖
报表查询可接受稍旧快照,避免阻塞在线交易
余额账户需要更严格事务、流水和对账

UPDATE 的全过程

以 MySQL InnoDB 思路为例,其他数据库名称不同但主线类似:

sql
update asset
set status = 2
where asset_id = 1001;
mermaid
flowchart TD
    A["收到 UPDATE"] --> B["解析、优化、生成计划"]
    B --> C["通过索引定位目标行"]
    C --> D["加行锁"]
    D --> E["写 undo 保存旧值"]
    E --> F["修改内存中的数据页"]
    F --> G["写 redo 日志"]
    G --> H["事务提交时写复制日志"]
    H --> I["提交成功"]
    I --> J["后台择机刷脏页到磁盘"]

注意:提交成功不等于数据页已经立刻写入磁盘。数据库通常先保证日志落盘,再由后台把脏页刷回数据文件。这样性能更好,也能通过日志恢复。

不同数据库的学习重点

MySQL

MySQL 学习主线:

  1. Server 层和 InnoDB 分层。
  2. B+Tree、聚簇索引、二级索引、回表。
  3. Buffer Pool、change buffer、自适应哈希等。
  4. undo、redo、binlog 和两阶段提交。
  5. MVCC、Read View、锁和间隙锁。
  6. 主从复制、延迟、GTID、半同步。
  7. 慢 SQL、EXPLAIN、索引设计、大表治理。

适合:互联网业务系统、订单、用户、支付、库存、后台管理。

PostgreSQL

PostgreSQL 学习主线:

  1. database、schema、role 的权限模型。
  2. heap tuple 多版本,xminxmax
  3. VACUUM、Autovacuum、表膨胀。
  4. WAL、检查点、流复制。
  5. B-tree、GIN、BRIN、GiST 等索引。
  6. EXPLAIN ANALYZE BUFFERS 真实执行计划。
  7. JSONB、窗口函数、CTE、扩展能力。

适合:复杂 SQL、强约束、GIS、JSONB、报表、数据治理平台。

Oracle

Oracle 学习主线:

  1. Instance 和 Database 的关系。
  2. SGA、PGA、Buffer Cache、Shared Pool。
  3. Tablespace、Segment、Extent、Block。
  4. Undo、Redo、SCN、一致性读。
  5. ROWID、B-tree、Bitmap 索引。
  6. AWR、ASH、执行计划、等待事件。
  7. RAC、Data Guard、归档日志和恢复。

适合:金融、政企、运营商、大型核心交易系统。

SQL Server

SQL Server 学习主线:

  1. MDF、NDF、LDF 文件。
  2. 8KB Page、Extent、Buffer Pool。
  3. 聚集索引、非聚集索引、Heap、RID Lookup、Key Lookup。
  4. Transaction Log、恢复模型、Always On。
  5. 锁、阻塞、死锁、RCSI 行版本。
  6. 参数嗅探、执行计划缓存。
  7. TempDB、等待统计、Query Store。

适合:微软生态、.NET 企业系统、BI 和报表系统。

商业项目中怎么建立数据库能力

订单支付系统

必须关注:

  1. 订单号、支付单号唯一约束。
  2. 状态机不能随便跳转。
  3. 支付回调必须幂等。
  4. 更新订单和写流水要在事务里。
  5. 高频查询按用户、状态、时间建联合索引。
  6. 对账、报表与在线交易库隔离。

医疗数据采集与资产平台

必须关注:

  1. 医院、科室、设备、资产编码的唯一性。
  2. 采集任务和采集结果要有幂等键。
  3. 大批量写入要分批,避免大事务。
  4. 资产当前状态适合关系库,复杂搜索同步 ES。
  5. 字典、权限、机构信息要有缓存,但数据库是事实源。
  6. 采集异常要保留流水,便于追溯。

搜索和报表

必须关注:

  1. 不要让在线库承担所有复杂检索和统计。
  2. 搜索视图同步 ES。
  3. 报表可走从库、数仓或预聚合表。
  4. 同步失败必须有重试和补偿。
  5. 数据一致性要明确是强一致还是最终一致。

可运行 Demo:用执行计划理解索引

下面用 MySQL 语法演示,核心思想适用于其他关系库。

sql
create table asset (
  id bigint primary key auto_increment,
  asset_code varchar(64) not null,
  hospital_id varchar(32) not null,
  department_id varchar(32) not null,
  status tinyint not null,
  name varchar(128) not null,
  updated_at datetime not null,
  unique key uk_asset_code (asset_code),
  key idx_hospital_status_time (hospital_id, status, updated_at),
  key idx_status_time (status, updated_at)
) engine = InnoDB default charset = utf8mb4;

查询某医院正常资产:

sql
explain
select id, asset_code, name
from asset
where hospital_id = 'H001'
  and status = 1
order by updated_at desc
limit 20;

你要看:

EXPLAIN 字段怎么理解
type访问方式,是否从 ALL 降到 range/ref
possible_keys理论上可能用哪些索引
key实际选择哪个索引
rows估算扫描多少行,不是真实返回数
filtered扫描后预计剩余比例
Extra是否 filesort、temporary、using index

如果 key 不是你预期的索引,不要马上强制索引。先问:

  1. 统计信息是否过期。
  2. 查询条件是否真的有选择性。
  3. 联合索引顺序是否匹配。
  4. 是否因为 select * 导致回表成本高。
  5. 是否排序方向和索引顺序不匹配。
  6. 是否数据量太小,优化器觉得全表更便宜。

线上慢查询排查流程

mermaid
flowchart TD
    A["发现接口慢"] --> B["确认是否数据库慢"]
    B --> C{"单 SQL 慢还是整体慢"}
    C -- "单 SQL 慢" --> D["查看 SQL 和参数"]
    D --> E["看执行计划"]
    E --> F["分析扫描、排序、回表、Join"]
    C -- "整体慢" --> G["看连接、CPU、IO、锁、日志刷盘"]
    G --> H["确认是否大事务、锁等待、磁盘瓶颈"]
    F --> I["改索引、SQL、分页或表设计"]
    H --> J["限流、拆事务、扩容或故障切换"]
    I --> K["用真实数据复测"]
    J --> K

常见误区

误区为什么错正确做法
会写 SQL 就会数据库不懂索引、事务、日志,上线会出问题按执行链路学习
建索引越多越好索引会占空间,拖慢写入围绕查询模式建联合索引
有索引一定快扫描范围、回表、排序、锁都会慢看执行计划和真实耗时
覆盖索引一定快深分页和大范围扫描仍然慢seek 分页、缩小范围、归档
事务越大越安全大事务会放大锁、日志、复制延迟缩短事务,分批处理
数据库能替代所有组件搜索、缓存、报表都有边界MySQL/PG + Redis + ES + MQ 合理组合
主从复制就是强一致异步复制会延迟明确读写一致性策略

面试标准回答

关系型数据库怎么从零学到深入

text
我会按 SQL、表设计、索引、执行计划、事务并发、日志恢复、高可用和线上排查这条线学习。先理解数据库不是简单文件存储,而是通过 SQL 层、优化器、执行器、存储页、缓存池、锁和 MVCC、事务日志来保证查询效率、并发正确性和故障恢复。然后再分别看 MySQL、PostgreSQL、Oracle、SQL Server 的实现差异。

为什么有索引还会慢

text
索引只是提供一种访问路径,不代表扫描量一定小。即使用了索引,也可能因为范围太大、低区分度、回表太多、深分页、排序临时表、统计信息不准、锁等待、缓存未命中或返回数据太多而慢。排查时要看执行计划的访问方式、key、rows、Extra,再结合慢日志、锁等待、IO 和业务参数判断。

B+Tree 和 B-tree 怎么回答

text
主流关系型数据库普遍使用 B-tree 家族的平衡多路索引结构。MySQL InnoDB 常强调 B+Tree,是因为聚簇索引叶子节点保存完整行,二级索引叶子节点保存主键,叶子节点有序适合范围扫描。Oracle、PostgreSQL、SQL Server 官方常称 B-tree index,它们的叶子项通常保存 ROWID、tuple 位置、聚集键或 RID。面试时不要简单说其他数据库就是普通 B 树,而要说明它们都是围绕页、平衡树、叶子定位和范围扫描优化的 B-tree 家族实现。

MVCC 和锁的关系

text
MVCC 主要解决读写并发,让普通读可以读取一致的历史版本,减少读写互相阻塞;锁主要解决写写冲突、唯一性、范围保护和结构变更。MVCC 不能替代锁,比如两个事务更新同一行库存仍然必须加锁,否则会出现覆盖更新或超卖。

关联知识点

知识点继续学习
MySQL 从零到生产级掌握MySQL 主线
MySQL 执行计划字段来源EXPLAIN 执行计划
MySQL B+Tree 与索引家族B+Tree 与索引家族
MySQL redo、undo、binlogredo log 与 binlog
PostgreSQL 从零到生产级掌握PostgreSQL 主线
Oracle 从零到生产级掌握Oracle 主线
SQL Server 从零到生产级掌握SQL Server 主线
Redis 与数据库一致性缓存一致性
MySQL 与 ES 一致性MySQL 与 ES 一致性

本章小结

关系型数据库从零到深入,关键不是背某个数据库的几个命令,而是建立“SQL 请求如何变成数据访问”的全过程模型。你要能把 SQL 层、优化器、执行器、页、索引、缓存、事务、锁、MVCC、日志、复制和排查串起来。这样再学 MySQL、PostgreSQL、Oracle、SQL Server 时,看到不同术语也不会乱,因为你知道它们都在解决同一批底层问题,只是实现方式和工程取舍不同。