Skip to content

MySQL、PostgreSQL、Oracle、SQL Server 核心差异与选型

很多同学学数据库会遇到一个困惑:MySQL 说 B+Tree,Oracle、PostgreSQL、SQL Server 说 B-tree;MySQL 说 redo、undo、binlog,PostgreSQL 说 WAL,Oracle 说 Undo、Redo、SCN,SQL Server 说 Transaction Log;看起来名词不同,但又都能支持事务、索引、执行计划和恢复。

这一页的目标不是让你背“数据库 A 比数据库 B 快”,而是建立一套能迁移的理解方式:

先理解关系型数据库共同要解决的问题,再理解每个数据库选择了什么实现方式,最后才能知道为什么排查方法不同。

学习目标

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

  1. MySQL、PostgreSQL、Oracle、SQL Server 都在解决哪些共同问题。
  2. 为什么 MySQL 教材经常说 B+Tree,而其他数据库常说 B-tree。
  3. Oracle、PostgreSQL、SQL Server 说的 B-tree 和普通 B 树是不是一回事。
  4. 事务日志、redo、undo、WAL、binlog、SCN、Transaction Log 分别解决什么问题。
  5. MVCC 在不同数据库中为什么实现不同。
  6. 为什么同一条 SQL 在不同数据库上的执行计划和优化方式不同。
  7. 商业项目中如何按订单、资产、报表、搜索、审计、高可用选择数据库。
  8. 从 MySQL 迁移到 PostgreSQL / Oracle / SQL Server 时最容易踩哪些坑。

先抓住共同主线

四类数据库都要解决这些问题:

mermaid
flowchart TD
    A["业务 SQL"] --> B["解析和权限检查"]
    B --> C["优化器选择执行计划"]
    C --> D["执行器访问表和索引"]
    D --> E["缓存池读取数据页"]
    E --> F["锁和版本控制并发"]
    F --> G["事务日志保证恢复"]
    G --> H["复制、备份、高可用"]

所以不管换成哪种数据库,学习主线都不是变成“重新学一门完全无关的技术”,而是看它在每一层具体怎么实现。

共同问题MySQL InnoDBPostgreSQLOracleSQL Server
数据怎么组织表空间、段、区、页、聚簇索引Database、schema、heap page、tuple表空间、段、区、blockdatabase、file、extent、page
索引怎么定位B+Tree 主键聚簇、二级索引保存主键B-tree 索引指向 heap tupleB-tree 索引指向 rowid聚集索引叶子为数据,非聚集索引定位行
SQL 怎么变计划优化器基于统计信息估算成本成本优化器,重视统计和扩展统计CBO,重视统计、直方图、执行计划优化器、统计信息、计划缓存
并发怎么处理锁 + undo 版本链 MVCCtuple 多版本 + VACUUMUndo + SCN 一致性读锁,或 RCSI/Snapshot 行版本
提交怎么恢复redo log 保证崩溃恢复WAL 先写日志redo log + SCNTransaction Log
复制怎么做binlog 主从复制WAL 流复制Data Guard / redo 传输Always On / 日志传送

为什么同叫关系型数据库,差异这么大

因为“关系模型”定义的是数据和查询的逻辑模型,不规定内部必须怎么存。数据库内部有很多工程取舍:

取舍选择 A选择 B影响
表数据组织聚簇索引组织堆表组织主键查询、二级索引回表方式不同
MVCC 存储undo 保存旧版本行本身保存多版本长事务、清理机制、空间膨胀不同
日志职责恢复日志和复制日志分开WAL 同时服务恢复和复制运维关注点不同
读一致性默认快照读默认语句级快照阻塞体验和版本清理不同
执行计划缓存强调计划复用更重视每次规划场景参数嗅探、计划稳定性不同

换句话说:它们面向的业务目标相似,但内部路线不同。

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

这是面试里最容易混乱的点。

教科书里的 B 树

教科书里的 B 树可以在内部节点和叶子节点都保存 key 和数据。查找某个 key 时,命中内部节点可能就能拿到数据。

mermaid
flowchart TD
    A["根节点<br/>保存 key 和部分数据"] --> B["中间节点<br/>保存 key 和部分数据"]
    A --> C["中间节点<br/>保存 key 和部分数据"]
    B --> D["叶子节点"]
    B --> E["叶子节点"]

它适合说明多路平衡查找树思想,但不等于所有商业数据库索引的完整实现。

MySQL 常说 B+Tree 的原因

MySQL InnoDB 中,B+Tree 更常被称为 B+Tree/B+树,核心特点是:

  1. 非叶子节点主要保存索引键和子节点指针。
  2. 叶子节点保存完整数据或主键。
  3. 叶子节点之间有链表,适合范围扫描。
  4. 主键索引的叶子节点保存整行数据,所以叫聚簇索引。
  5. 二级索引叶子节点保存二级索引键和主键值,再通过主键回表。
mermaid
flowchart TD
    A["根页<br/>索引键 + 子页指针"] --> B["非叶子页"]
    A --> C["非叶子页"]
    B --> D["叶子页<br/>key + 行数据或主键"]
    B --> E["叶子页<br/>key + 行数据或主键"]
    D --> E

为什么这样设计?

设计好处
非叶子节点不放整行数据一个页能放更多 key,树更矮,IO 更少
叶子节点有序链表范围查询、排序、分页更友好
主键聚簇主键查询直接拿到整行
二级索引保存主键避免保存物理地址失效,但可能需要回表

如果不用这种设计,范围查询可能要到处跳,树高可能变大,缓存命中率和 IO 都会变差。

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

Oracle、PostgreSQL、SQL Server 官方语境里常用 B-tree 泛指多路平衡树索引家族。这里的 B-tree 并不等于“内部节点保存完整行数据的普通 B 树”。

可以这样理解:

MySQL 教学里强调 B+Tree,是为了突出叶子层和范围扫描;Oracle、PostgreSQL、SQL Server 说 B-tree,更多是官方术语习惯和索引家族命名。不要把它们误解成简单二叉树,也不要机械认为“只有 MySQL 是 B+Tree,其他数据库就是低级 B 树”。

不同数据库的关键区别不是名字,而是叶子层如何定位数据:

数据库常见 B-tree 叶子层保存什么查询缺列时怎么取完整行
MySQL InnoDB 主键索引完整行数据不需要回表
MySQL InnoDB 二级索引二级索引键 + 主键通过主键回聚簇索引
PostgreSQL B-tree索引键 + TID回 heap 表取 tuple,并做可见性检查
Oracle B-tree索引键 + ROWID通过 ROWID 定位表 block 和行
SQL Server 聚集索引叶子层就是数据行不需要 Lookup
SQL Server 非聚集索引索引键 + 行定位器Key Lookup / RID Lookup

四种数据库的一条查询怎么走

MySQL InnoDB

mermaid
flowchart TD
    A["SQL"] --> B["Parser"]
    B --> C["Optimizer"]
    C --> D["Executor"]
    D --> E["InnoDB Buffer Pool"]
    E --> F["B+Tree 索引页"]
    F --> G["数据页"]
    G --> H["undo 可见性判断"]

高频关注点:

  1. 是否走了合适联合索引。
  2. 是否回表过多。
  3. rows 估算和真实扫描是否偏差。
  4. 是否 Using temporaryUsing filesort
  5. 是否被锁等待、长事务、undo 堆积影响。

PostgreSQL

mermaid
flowchart TD
    A["SQL"] --> B["Parser / Rewriter"]
    B --> C["Planner"]
    C --> D["Executor"]
    D --> E["Shared Buffers"]
    E --> F["B-tree / GIN / GiST 索引"]
    F --> G["Heap Tuple"]
    G --> H["xmin/xmax 可见性判断"]

高频关注点:

  1. EXPLAIN ANALYZE 中估算行数和实际行数差异。
  2. heap tuple 多版本是否导致表膨胀。
  3. VACUUM 是否及时清理死元组。
  4. GIN、GiST、BRIN 等索引是否适合查询类型。
  5. 复杂 SQL、CTE、窗口函数、JSONB 是否被合理优化。

Oracle

mermaid
flowchart TD
    A["SQL"] --> B["Shared Pool 解析和优化"]
    B --> C["CBO 选择计划"]
    C --> D["执行器"]
    D --> E["Database Buffer Cache"]
    E --> F["Index Block"]
    F --> G["Table Block"]
    G --> H["Undo + SCN 一致性读"]

高频关注点:

  1. 执行计划、统计信息、直方图是否准确。
  2. 通过 ROWID 回表是否过多。
  3. Undo 是否足够,是否出现快照过旧问题。
  4. Redo 生成量、日志切换、归档是否影响写入。
  5. AWR、ASH、等待事件如何定位瓶颈。

SQL Server

mermaid
flowchart TD
    A["T-SQL"] --> B["Parser / Algebrizer"]
    B --> C["Optimizer"]
    C --> D["执行计划缓存"]
    D --> E["执行器"]
    E --> F["Buffer Pool"]
    F --> G["聚集/非聚集索引页"]
    G --> H["锁或行版本判断"]

高频关注点:

  1. 聚集索引和非聚集索引设计是否合理。
  2. Key Lookup 是否过多。
  3. 参数嗅探是否导致同一 SQL 忽快忽慢。
  4. 等待类型、阻塞链、死锁图。
  5. TempDB 是否被排序、Hash、版本存储拖垮。

事务日志体系对比

事务日志的核心价值是:数据库不能只把最终结果写到数据文件,还要记录“怎么改的”,这样才能回滚、恢复和复制。

mermaid
flowchart TD
    A["事务修改数据"] --> B["生成事务日志"]
    B --> C["日志先持久化"]
    C --> D["修改缓存中的数据页"]
    D --> E["事务提交"]
    E --> F["后台刷脏页到数据文件"]
    C --> G["宕机恢复可重做"]

不同数据库叫法不同:

数据库日志/版本组件主要解决什么
MySQL InnoDB redo log崩溃恢复,保证提交事务可重做
MySQL undo log回滚和 MVCC 旧版本读取
MySQL binlogServer 层逻辑日志,主从复制和点位恢复
PostgreSQL WAL崩溃恢复、流复制、归档恢复
Oracle Redo崩溃恢复、Data Guard 传输
Oracle Undo回滚、一致性读、Flashback 基础
Oracle SCN数据库全局逻辑时间,判断一致性版本
SQL Server Transaction Log回滚、崩溃恢复、日志备份、Always On

为什么 MySQL 既有 redo 又有 binlog

MySQL 有 Server 层和存储引擎层。InnoDB 的 redo log 属于存储引擎,记录页级物理修改,用于崩溃恢复;binlog 属于 Server 层,记录逻辑变更,用于主从复制、审计和时间点恢复。

如果只有 redo,没有 binlog:

  1. InnoDB 能崩溃恢复。
  2. 但主从复制、CDC、基于 binlog 的数据同步会缺少统一日志。

如果只有 binlog,没有 redo:

  1. 可以知道执行过哪些 SQL 或行变更。
  2. 但宕机时不能高效恢复 InnoDB 缓存页中的已提交修改。

所以 MySQL 提交事务时需要协调 redo 和 binlog,避免一个成功一个失败造成主库和从库不一致。

为什么 PostgreSQL 一个 WAL 能承担更多角色

PostgreSQL 的 WAL 是预写日志,同时用于崩溃恢复、流复制和归档恢复。它的核心原则也是先写日志,再刷数据页。

但这不代表 PostgreSQL 没有版本管理。PostgreSQL 的旧版本主要保存在表的 heap tuple 中,通过 xminxmax 等事务信息判断可见性,后续由 VACUUM 清理死元组。

所以 PostgreSQL 运维特别关注:

  1. 长事务会阻止旧版本清理。
  2. VACUUM 不及时会表膨胀。
  3. WAL 生成过快会影响复制和归档。

MVCC 实现差异

MVCC 的目标是:读操作尽量不要被写操作阻塞,同时读到一个一致的历史版本。

mermaid
flowchart TD
    A["事务开始"] --> B["确定可见性规则"]
    B --> C["读取行"]
    C --> D{"当前版本是否可见"}
    D -- "可见" --> E["返回当前版本"]
    D -- "不可见" --> F["寻找旧版本"]
    F --> G["根据事务状态判断"]
数据库旧版本在哪里清理压力来自哪里
MySQL InnoDBundo log 版本链长事务导致 undo 不能清
PostgreSQLheap 中的旧 tupleVACUUM 不能清死元组导致膨胀
OracleUndo 表空间Undo 不足可能快照过旧
SQL ServerTempDB 版本存储,取决于隔离设置RCSI/Snapshot 增加 TempDB 压力

为什么长事务可怕

长事务不是“慢一点”这么简单。它会让数据库保留更多历史版本:

数据库长事务影响
MySQLundo 版本链变长,purge 不能推进,快照读变慢
PostgreSQLdead tuple 不能被 VACUUM 清掉,表和索引膨胀
OracleUndo 被长查询占用,可能影响一致性读
SQL Server行版本在 TempDB 中保留更久,TempDB 压力增加

这就是为什么线上排查慢 SQL 和空间暴涨时,要看长事务,而不是只看索引。

执行计划为什么会选错

优化器不是神,它依赖统计信息估算。

mermaid
flowchart TD
    A["SQL 条件"] --> B["统计信息"]
    B --> C["估算过滤后行数"]
    C --> D["估算 IO / CPU / 内存成本"]
    D --> E["选择成本最低计划"]
    E --> F["实际执行"]
    F --> G{"估算和实际是否接近"}
    G -- "接近" --> H["计划通常稳定"]
    G -- "偏差大" --> I["可能慢 SQL"]

常见选错原因:

原因表现处理方向
统计信息过期估算行数和实际差很多更新统计信息
数据倾斜热值查询计划不适合冷值直方图、改写 SQL、分离热值
字段相关性多条件独立估算错误扩展统计、联合索引、改写查询
参数嗅探SQL Server 同 SQL 不同参数忽快忽慢Query Store、重新编译、计划治理
函数包列索引不能正常利用函数索引、生成列、改写条件
返回列太多本来可覆盖,结果大量回表覆盖索引或减少返回列

商业场景选型

订单、支付、库存

优先考虑 MySQL、Oracle、SQL Server 这类成熟关系库。关键不是“单条 SQL 多快”,而是事务、唯一约束、状态流转、审计、备份恢复能不能稳。

场景推荐思路
互联网订单MySQL 常见,配合分库分表、缓存、MQ
金融核心交易Oracle 常见,强调稳定性、高可用、审计
微软生态企业系统SQL Server 常见,配合 .NET、报表、BI

医疗资产、采集任务、设备台账

这类系统通常不是极致高并发秒杀,但要求数据准确、可追溯、可查询。

常见设计:

  1. 关系库保存设备、医院、科室、采集任务、采集结果。
  2. Redis 缓存热设备状态和采集进度。
  3. Elasticsearch 承担复杂搜索和日志检索。
  4. MQ 解耦采集结果异步入库和索引同步。
  5. 审计表记录关键状态变化。

报表和分析

不要把在线交易库当无限报表库。复杂统计会大量扫描、排序、聚合,影响交易请求。

可选方案:

  1. 从库承接轻量报表。
  2. 定时 ETL 到数仓或 OLAP。
  3. PostgreSQL 可承担一部分复杂 SQL 和分析需求。
  4. SQL Server + BI 工具适合微软生态报表。
  5. Oracle 在大型政企环境中常配合专门报表库和物化视图。

迁移和多数据库开发常见坑

原因处理
SQL 方言不同分页、函数、日期、递归查询语法不同使用数据库专属适配层
自增主键不同auto_increment、sequence、identity 差异明确 ID 生成策略
隔离级别体验不同默认隔离和 MVCC 实现不同重新验证并发场景
索引语法不同INCLUDE、函数索引、部分索引支持不同按目标库重建索引策略
执行计划差异优化器成本模型不同用真实数据重新压测
大小写和编码标识符、排序规则、字符集不同统一命名和 collation
时间类型timestamp、datetime、timezone 行为不同明确时区和精度
事务日志运维不同binlog、WAL、redo、LDF 管理方式不同建立目标库备份恢复手册

可运行 Demo:同一个业务在不同数据库的建表差异

MySQL

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

PostgreSQL

sql
create table asset_device (
  id bigserial primary key,
  hospital_id varchar(32) not null,
  asset_code varchar(64) not null,
  name varchar(128) not null,
  status smallint not null,
  updated_at timestamp not null,
  constraint uk_hospital_asset unique (hospital_id, asset_code)
);

create index idx_hospital_status_updated
on asset_device (hospital_id, status, updated_at);

Oracle

sql
create table asset_device (
  id number(19) primary key,
  hospital_id varchar2(32) not null,
  asset_code varchar2(64) not null,
  name varchar2(128) not null,
  status number(3) not null,
  updated_at timestamp not null,
  constraint uk_hospital_asset unique (hospital_id, asset_code)
);

create sequence seq_asset_device start with 1 increment by 1;

create index idx_hospital_status_updated
on asset_device (hospital_id, status, updated_at);

SQL Server

sql
create table asset_device (
  id bigint identity(1,1) primary key,
  hospital_id varchar(32) not null,
  asset_code varchar(64) not null,
  name varchar(128) not null,
  status tinyint not null,
  updated_at datetime2 not null,
  constraint uk_hospital_asset unique (hospital_id, asset_code)
);

create index idx_hospital_status_updated
on asset_device (hospital_id, status, updated_at);

你会发现,业务模型相同,但自增、字符串类型、时间类型、索引语法都不同。商业项目不能靠“复制 MySQL SQL 到 Oracle 运行”完成迁移。

同一条列表查询在四种数据库里为什么表现不同

很多人觉得“SQL 一样,索引也差不多,性能应该差不多”。实际不是这样。数据库真正执行时,不只看 SQL 文本,还看表组织方式、索引叶子层、统计信息、可见性判断、回表方式和缓存命中。

以医疗资产列表为例:

sql
select id, hospital_id, asset_code, name, status, updated_at
from asset_device
where hospital_id = ?
  and status = ?
order by updated_at desc
fetch first 20 rows only;

这条 SQL 的目标很明确:先按医院过滤,再按状态过滤,最后取更新时间最新的 20 条。它适合建立联合索引:

text
(hospital_id, status, updated_at)

但四种数据库的“命中索引以后怎么拿到行”不一样。

mermaid
flowchart TD
    A["列表查询<br/>hospital_id + status + order by updated_at"] --> B["优化器估算行数和成本"]
    B --> C["选择索引或全表扫描"]
    C --> D["索引定位候选行"]
    D --> E["读取完整列和判断可见性"]
    E --> F["排序 / Top-N / 返回 20 行"]

MySQL InnoDB:二级索引命中后可能回聚簇索引

MySQL InnoDB 的二级索引叶子层保存的是索引列和主键值。如果查询列不都在二级索引里,就要通过主键回到聚簇索引取整行。

mermaid
flowchart TD
    A["二级索引<br/>hospital_id,status,updated_at"] --> B["找到主键 id"]
    B --> C["回聚簇索引"]
    C --> D["读取整行 name、asset_code 等列"]

如果只返回 20 行,回表通常可接受;如果条件范围很大、深分页、返回很多行,回表会变成大量随机访问。覆盖索引能减少回表,但覆盖索引很宽也会增加写入和缓存成本。

PostgreSQL:Index Only Scan 也要看 Visibility Map

PostgreSQL B-tree 索引叶子项通常保存 key 和 TID,TID 指向 heap tuple。即使查询列都在索引里,数据库还要确认这个 tuple 对当前事务快照是否可见。

mermaid
flowchart TD
    A["B-tree 索引命中"] --> B["得到 TID"]
    B --> C{"heap page 是否 all-visible"}
    C -- "是" --> D["Index Only Scan 可少访问 heap"]
    C -- "否" --> E["访问 heap 检查 xmin/xmax 可见性"]

所以 PostgreSQL 的“覆盖索引”不能照搬 MySQL 逻辑。它还受 VACUUM、Visibility Map、长事务和表膨胀影响。Autovacuum 跟不上时,Index Only Scan 可能退化为大量 heap fetch。

Oracle:索引叶子保存 ROWID,回表看 ROWID 成本

Oracle B-tree leaf block 保存 key 和 ROWID。ROWID 可以快速定位表 block 和行位置。

mermaid
flowchart TD
    A["B-tree Leaf<br/>key + ROWID"] --> B["TABLE ACCESS BY INDEX ROWID"]
    B --> C["读取表块"]
    C --> D["通过 Undo + SCN 做一致性读"]

返回少量行时,ROWID 回表非常高效;返回大量行时,可能造成大量随机读。此时优化器可能认为全表扫描更便宜。Oracle 排查时要特别看 TABLE ACCESS BY INDEX ROWID 次数、逻辑读、物理读、E-Rows/A-Rows 差异和等待事件。

SQL Server:非聚集索引缺列会 Key Lookup

SQL Server 聚集索引叶子层就是数据行,非聚集索引叶子层保存索引键和行定位器。如果查询字段不在非聚集索引里,会出现 Key Lookup。

mermaid
flowchart TD
    A["Nonclustered Index Seek"] --> B["得到聚集键"]
    B --> C["Key Lookup"]
    C --> D["回聚集索引叶子层取完整行"]

少量 Key Lookup 很正常;大量 Key Lookup 会拖慢查询。可以用 include 做覆盖索引,但 include 列越多,索引越宽,写入、备份、Buffer Pool 压力越大。

为什么“走了索引还是慢”

面试和生产排查里,最危险的回答是:“已经走索引了,应该没问题”。走索引只是说明数据库选择了某个访问路径,不代表扫描范围小、回表少、排序少、锁等待少。

慢的原因MySQL 表现PostgreSQL 表现Oracle 表现SQL Server 表现
扫描范围大rows 大,覆盖索引仍扫很多actual rows 大,Buffers 高A-Rows 大,逻辑读高Actual Rows 大,logical reads 高
回表/Lookup 多二级索引回聚簇索引heap fetch 多ROWID 回表多Key Lookup 多
统计信息不准选错索引或 Join 顺序estimated/actual 差距大E-Rows/A-Rows 差距大Estimated/Actual 差距大
排序或临时空间Using filesort、临时表Sort 节点、磁盘临时文件SORT、TEMP 使用高Sort/Hash spill 到 TempDB
版本/锁问题长事务、锁等待、undo 压力VACUUM 被阻塞、dead tuple 多Undo 不足、snapshot too old阻塞、RCSI 版本存储、TempDB

排查顺序应该是:

mermaid
flowchart TD
    A["SQL 慢"] --> B["看真实执行计划"]
    B --> C["比较估算行数和实际行数"]
    C --> D["看逻辑读、物理读、回表或 Lookup"]
    D --> E["看排序、临时空间、Join 算法"]
    E --> F["看锁等待、版本清理、日志和 IO"]
    F --> G["再决定改 SQL、索引、统计信息、表设计或架构"]

从零到精通必须会的迁移思维

如果你从 MySQL 学到 Oracle、PostgreSQL、SQL Server,不能只替换语法,要替换思维模型。

MySQL 经验迁移时要重新理解
覆盖索引减少回表PostgreSQL 还要看 Visibility Map,SQL Server 要看 include,Oracle 要看 ROWID 回表成本
EXPLAIN rowsPostgreSQL/Oracle/SQL Server 也有估算行数,但真实验证方式不同
undo 版本链PostgreSQL 旧版本在 heap tuple 中,Oracle 用 Undo + SCN,SQL Server 可用 TempDB 行版本
redo + binlogPostgreSQL WAL、Oracle Redo、SQL Server Transaction Log 的职责边界不同
深分页慢四种数据库都会慢,但语法和优化手段不同,核心都是避免跳过大量行
自增主键Oracle sequence、PostgreSQL sequence/identity、SQL Server identity 都不保证业务连续

真正的“从零到精通”不是背完每个数据库的名词,而是看到同一类业务问题时,能说清楚:

  1. 事实数据存在哪里。
  2. SQL 经过哪些层。
  3. 优化器为什么选这个计划。
  4. 索引命中后如何定位完整行。
  5. 并发下如何判断可见性。
  6. 提交后靠什么日志恢复。
  7. 慢的时候用什么证据排查。

面试标准回答

MySQL 为什么常说 B+Tree,Oracle、PostgreSQL、SQL Server 为什么常说 B-tree

标准回答:

text
MySQL InnoDB 教学中常说 B+Tree,是为了强调非叶子节点主要保存键和指针、叶子节点保存数据或主键,并且叶子节点有序链表适合范围扫描。Oracle、PostgreSQL、SQL Server 官方文档里常说 B-tree,更多是索引家族和术语习惯,不代表它们使用简单二叉树,也不代表它们就是教科书里内部节点保存完整数据的普通 B 树。真正要比较的是叶子层保存什么以及如何定位数据:InnoDB 主键索引叶子层是整行,二级索引保存主键;PostgreSQL B-tree 保存 TID 回 heap;Oracle 保存 ROWID;SQL Server 聚集索引叶子层是数据行,非聚集索引通过行定位器 Lookup。

追问点:

  1. 为什么 B+Tree 适合范围查询?
  2. 为什么非叶子节点不放整行能降低树高?
  3. 为什么二级索引可能回表?
  4. PostgreSQL 的 heap tuple 可见性检查是什么?

为什么同一条 SQL 在 MySQL、PostgreSQL、Oracle、SQL Server 上性能不同

标准回答:

text
因为 SQL 文本只是逻辑请求,真正性能取决于存储组织、索引叶子层、统计信息、执行计划、可见性判断、回表方式和缓存命中。MySQL InnoDB 二级索引命中后可能通过主键回聚簇索引;PostgreSQL B-tree 通过 TID 回 heap,还要做 MVCC 可见性判断,Index Only Scan 也受 Visibility Map 影响;Oracle B-tree 通过 ROWID 回表,并基于 Undo + SCN 做一致性读;SQL Server 非聚集索引缺列时会 Key Lookup。即使都叫走索引,扫描范围、回表次数、排序、锁等待和版本清理成本都可能完全不同。

追问点:

  1. 为什么 Index Seek 不一定快?
  2. PostgreSQL Index Only Scan 为什么还可能访问 heap?
  3. Oracle ROWID 回表什么时候不如全表扫描?
  4. SQL Server Key Lookup 多了怎么优化?
  5. MySQL 覆盖索引为什么大表场景仍可能慢?

四种数据库事务日志有什么区别

标准回答:

text
事务日志的共同目标是保证提交、回滚、崩溃恢复和复制。MySQL InnoDB redo log 用于崩溃恢复,undo log 用于回滚和 MVCC,binlog 用于复制和时间点恢复;PostgreSQL WAL 同时服务崩溃恢复、流复制和归档恢复;Oracle 通过 Redo 保证恢复和 Data Guard,通过 Undo 做回滚和一致性读,SCN 作为一致性时间点;SQL Server Transaction Log 用于回滚、恢复、日志备份和 Always On。名字不同,本质都是围绕“先写日志,再让数据页异步落盘”来保证故障后可恢复。

追问点:

  1. MySQL 为什么 redo 和 binlog 要两阶段提交?
  2. PostgreSQL 为什么需要 VACUUM?
  3. Oracle 快照过旧和 Undo 有什么关系?
  4. SQL Server 日志暴涨可能是什么原因?

关联学习

知识点说明
关系型数据库底层原理从 SQL 层、优化器、页、索引、事务、日志串起来
MySQL 从零到生产级掌握InnoDB、B+Tree、redo/undo/binlog、主从复制
PostgreSQL 从零到生产级掌握heap tuple、WAL、VACUUM、扩展索引
Oracle 从零到生产级掌握SGA/PGA、Undo、Redo、SCN、Data Guard
SQL Server 从零到生产级掌握聚集索引、事务日志、锁、RCSI、参数嗅探
MySQL B+Tree 与索引家族深入理解 B+Tree、B-tree、Hash、Bitmap、GIN、GiST
MySQL EXPLAIN 执行计划理解 rows、key、type、Extra 等字段来源