9054 字
45 分钟
100 道常见 MySQL 数据库题目架构篇
2026-04-16 13:51:28

13. 一条 SQL 查询语句的执行顺序是什么?#

答:

SQL 的书写顺序真正执行顺序是不一样的。 这是面试里非常常见的问题,尤其喜欢结合:

  • where
  • group by
  • having
  • order by
  • limit

来问。


1)SQL 常见书写顺序#

SELECT ...
FROM ...
JOIN ...
ON ...
WHERE ...
GROUP BY ...
HAVING ...
ORDER BY ...
LIMIT ...

这是我们写 SQL 的顺序。


2)逻辑执行顺序#

一条查询 SQL 的典型逻辑执行顺序是:

  1. FROM
  2. ON
  3. JOIN
  4. WHERE
  5. GROUP BY
  6. HAVING
  7. SELECT
  8. DISTINCT
  9. ORDER BY
  10. LIMIT

3)逐步解释#

(1)FROM#

先确定从哪张表取数据。

FROM user

如果是多表查询,会先形成基础数据集。


(2)ON#

如果有连接操作,先根据 ON 条件判断连接关系。

FROM user u
LEFT JOIN orders o ON u.id = o.user_id

这里先根据 u.id = o.user_id 进行匹配。


(3)JOIN#

把连接结果组合起来,生成中间结果集。


(4)WHERE#

对连接后的结果进行条件过滤。

WHERE u.status = 1

这一步是在分组前过滤。


(5)GROUP BY#

把过滤后的结果按指定字段分组。

GROUP BY u.dept_id

(6)HAVING#

对分组后的结果进一步筛选。

HAVING COUNT(*) > 5

这一步和 WHERE 不同,HAVING 是在分组之后执行。


(7)SELECT#

选择最终要输出的列。

SELECT u.dept_id, COUNT(*)

(8)DISTINCT#

如果有 DISTINCT,则在结果集上去重。


(9)ORDER BY#

对结果排序。

ORDER BY COUNT(*) DESC

(10)LIMIT#

最后截取指定条数。

LIMIT 10

4)为什么要理解执行顺序?#

因为很多 SQL 问题都和执行顺序有关。

例如:为什么 WHERE 不能用聚合函数?

因为 WHEREGROUP BY 之前执行,而聚合函数是分组后才有的结果。

错误写法:

SELECT dept_id, COUNT(*)
FROM emp
WHERE COUNT(*) > 5
GROUP BY dept_id;

正确写法:

SELECT dept_id, COUNT(*)
FROM emp
GROUP BY dept_id
HAVING COUNT(*) > 5;

5)一个完整示例#

SELECT dept_id, COUNT(*) AS total
FROM emp
WHERE salary > 5000
GROUP BY dept_id
HAVING COUNT(*) > 2
ORDER BY total DESC
LIMIT 3;

执行逻辑:

  1. emp 表取数据
  2. WHERE salary > 5000 先过滤
  3. dept_id 分组
  4. HAVING COUNT(*) > 2 过滤分组结果
  5. SELECT 选择要返回的列
  6. ORDER BY total DESC 排序
  7. LIMIT 3 取前 3 条

6)总结#

SQL 书写顺序不等于执行顺序。 典型查询逻辑顺序是:

FROM -> ON -> JOIN -> WHERE -> GROUP BY -> HAVING -> SELECT -> DISTINCT -> ORDER BY -> LIMIT

理解这一点,很多 SQL 语法和优化问题都会更清楚。


14. 说说 MySQL 的基础架构?#

答:

MySQL 的逻辑架构通常可以分成三层:

  1. 客户端层
  2. Server 层
  3. 存储引擎层

这个问题主要考察你是否理解: 一条 SQL 从客户端发出后,MySQL 内部是如何分层处理的。


1)客户端层#

这一层主要负责和客户端交互,包括:

  • 建立连接
  • 身份认证
  • 权限校验
  • 网络通信

常见客户端可以是:

  • MySQL 命令行
  • Navicat / DataGrip
  • Java 程序 JDBC
  • Python / Go / PHP 数据库驱动

也就是说,用户不是直接和存储引擎打交道,而是先进入 MySQL 服务端入口。


2)Server 层#

这一层是 MySQL 的核心服务层,负责大多数通用逻辑,和具体存储引擎无关。

主要包括:

(1)连接器#

负责客户端连接、登录认证、权限检查。

(2)查询缓存(老版本)#

MySQL 8.0 已移除,老版本中会先看是否命中缓存。

(3)解析器#

负责 SQL 的词法分析、语法分析。 比如识别:

  • SELECT
  • FROM
  • WHERE

并检查 SQL 语法是否正确。

(4)优化器#

负责生成执行计划,比如:

  • 选择哪个索引
  • 决定 join 顺序
  • 决定是否走全表扫描

(5)执行器#

根据执行计划调用存储引擎接口,真正执行 SQL。

(6)内置函数 / 视图 / 存储过程 / 触发器#

这些跨引擎的功能也在 Server 层实现。


3)存储引擎层#

存储引擎层是真正负责:

  • 数据存储
  • 数据读取
  • 索引管理
  • 事务 / 锁 / 日志(视引擎而定)

MySQL 支持多种存储引擎,例如:

  • InnoDB
  • MyISAM
  • Memory

Server 层通过统一接口调用存储引擎,所以不同引擎对上层查询过程透明。


4)为什么说 MySQL 是插件式存储引擎架构?#

因为 MySQL 把“SQL 解析执行”和“数据存储实现”分离了。 上层是统一的 Server 层,下层可以替换不同存储引擎。

这意味着:

  • 同一个数据库中,不同表甚至可以使用不同引擎
  • 例如一张表用 InnoDB,另一张表用 Memory

5)架构的价值#

这种分层设计的好处有:

  1. 职责清晰

    • Server 层负责 SQL 逻辑
    • 引擎层负责数据存储
  2. 扩展性强

    • 可支持多种存储引擎
  3. 上层统一

    • 客户端使用体验一致

6)总结#

MySQL 基础架构可以概括为:

  • 客户端层:连接、认证、权限
  • Server 层:解析、优化、执行
  • 存储引擎层:存储、索引、事务、日志

一句话总结就是:

Server 层负责“怎么查”,存储引擎层负责“怎么存、怎么取”。


15. 一条 SQL 查询语句在 MySQL 中如何执行的?#

答:

一条 SQL 在 MySQL 中的执行过程,大致可以分为以下几个阶段:

  1. 客户端连接
  2. 权限校验
  3. 解析 SQL
  4. 优化执行计划
  5. 调用存储引擎
  6. 返回结果

1)连接器:建立连接#

客户端先和 MySQL 建立连接。 在这个阶段会完成:

  • TCP 连接
  • 用户名密码认证
  • 权限检查

如果账号密码错误,会直接报错。 如果没有对应数据库权限,也不能继续执行。


2)查询缓存(MySQL 8.0 之前)#

老版本 MySQL 会先看查询缓存是否命中。 如果完全一样的 SQL 已经执行过,并且结果仍有效,就直接返回结果。

不过查询缓存问题很多:

  • 命中率低
  • 表一更新缓存容易失效

所以在 MySQL 8.0 已经被移除。


3)解析器:词法分析 + 语法分析#

如果没有缓存(或者是 8.0),SQL 会交给解析器。

例如:

SELECT name FROM user WHERE id = 1;

解析器要做两件事:

词法分析#

识别出:

  • SELECT 是关键字
  • name 是列
  • FROM 是关键字
  • user 是表
  • WHERE 是条件
  • id=1 是筛选表达式

语法分析#

判断 SQL 是否符合语法规则。 比如拼写错误:

SELEC name FROM user;

会在这里报错。


4)优化器:生成执行计划#

SQL 语法没问题后,进入优化器。 优化器会决定:

  • 走哪个索引
  • 多表连接顺序
  • 是否使用覆盖索引
  • 是否使用临时表
  • 是否全表扫描

例如下面 SQL:

SELECT * FROM user WHERE age = 20 AND name = 'Tom';

如果有多个索引:

  • idx_age
  • idx_name
  • idx_age_name

优化器会评估成本,决定最优执行方案。


5)执行器:真正执行 SQL#

优化器生成执行计划后,执行器开始调用存储引擎接口。

如果是查询语句:

  • 按执行计划去引擎层取数据
  • 再返回给客户端

如果是更新语句:

  • 不仅取数据
  • 还要修改数据
  • 并写相关日志(undo log、redo log、binlog)

6)存储引擎:读取 / 写入数据#

执行器并不直接操作磁盘,而是通过存储引擎接口访问数据。 比如 InnoDB 会负责:

  • 读取数据页
  • 使用索引定位
  • 事务控制
  • 行锁管理
  • redo / undo 记录

7)返回结果#

执行完成后:

  • 如果是 SELECT,将结果返回客户端
  • 如果是 INSERT / UPDATE / DELETE,返回影响行数等信息

8)一句话串起来#

一条 SQL 的执行流程可以概括为:

连接器认证 -> 解析器分析 SQL -> 优化器生成执行计划 -> 执行器调用存储引擎 -> 返回结果


9)如果是更新语句,还会多什么?#

查询语句主要走“解析、优化、读取”。

更新语句还会额外涉及:

  • 事务
  • undo log
  • redo log
  • binlog
  • 两阶段提交

所以面试里如果问的是“查询语句怎么执行”,回答到执行器和引擎层就够了; 如果问“更新语句怎么执行”,就要继续讲日志和事务。


16. MySQL 有哪些常见存储引擎?#

答:

MySQL 支持多种存储引擎,不同存储引擎在:

  • 事务支持
  • 锁粒度
  • 索引结构
  • 性能特性
  • 使用场景

等方面都不一样。

常见的存储引擎主要有:

  • InnoDB
  • MyISAM
  • MEMORY

其中最常用、最重要的是 InnoDB


1)InnoDB#

InnoDB 是 MySQL 默认的存储引擎,也是现代业务系统中最常用的引擎。

特点

  • 支持事务
  • 支持行级锁
  • 支持外键
  • 支持崩溃恢复
  • 支持 MVCC
  • 支持聚簇索引
  • 支持全文索引(MySQL 5.6+)

适用场景

适合绝大多数 OLTP 业务系统,比如:

  • 电商
  • 支付
  • 用户系统
  • 订单系统
  • CRM / ERP

一句话: 有事务和并发需求,优先 InnoDB。


2)MyISAM#

MyISAM 是 MySQL 早期常用的引擎,但现在已不适合作为主流业务表引擎。

特点

  • 不支持事务
  • 不支持外键
  • 只支持表锁
  • 查询性能在某些读多场景较好
  • 保存表总行数,count(*)where 很快
  • 支持全文索引(早期优势)

适用场景

过去常用于:

  • 读多写少系统
  • 日志查询
  • 报表类场景

但因为它不支持事务、并发更新能力差,现代项目中已很少作为核心业务表引擎。


3)MEMORY#

MEMORY 引擎把数据放在内存中。

特点

  • 读写速度非常快
  • 数据在内存中
  • 服务重启后数据丢失
  • 常用于临时数据、中间计算结果

适用场景

  • 临时表
  • 缓存型数据
  • 中间计算数据

不适合存放重要业务数据。


4)常见对比表#

特性InnoDBMyISAMMEMORY
事务支持不支持不支持
行锁支持不支持不支持
表锁支持支持支持
外键支持不支持不支持
崩溃恢复支持不适用
数据存储磁盘磁盘内存
默认引擎

5)一些补充细节#

InnoDB 的哈希索引#

InnoDB 本身主要使用 B+ 树索引,但它有自适应哈希索引能力。 这个哈希索引是自动生成的,不是用户手工创建的。

全文索引#

  • MyISAM 早期支持全文索引
  • InnoDB 从 MySQL 5.6 开始也支持全文索引

6)实际开发怎么选?#

现代项目中绝大多数场景:

  • 默认选 InnoDB

因为它在事务、安全性、并发控制上明显更强。

只有少数特殊场景,才会考虑其他引擎:

  • 内存临时数据:MEMORY
  • 某些特殊兼容需求:MyISAM(现在少见)

7)总结#

MySQL 常见存储引擎有:

  • InnoDB:主流首选
  • MyISAM:老引擎,读多写少时代常见
  • MEMORY:内存引擎,适合临时数据

一句话总结:

现代 MySQL 开发中,绝大多数业务表应优先使用 InnoDB。


17. 存储引擎应该怎么选择?#

答:

在 MySQL 中,选择存储引擎的本质,就是根据业务需求选择合适的数据存储方式。 不同引擎的能力不同,所以要重点看:

  • 是否需要事务
  • 并发更新多不多
  • 是否需要外键
  • 是否在意崩溃恢复
  • 数据是否只做临时使用

在现代 MySQL 开发中,绝大多数场景都应该优先选择 InnoDB


1)优先考虑 InnoDB 的场景#

如果你的业务有以下任何需求,就基本应该选 InnoDB:

  • 需要事务
  • 需要回滚
  • 有并发更新
  • 有较高的数据一致性要求
  • 需要崩溃恢复
  • 需要行级锁
  • 需要外键(虽然业务里不一定常用)

典型业务:

  • 用户系统
  • 订单系统
  • 支付系统
  • 库存系统
  • 管理后台

这些系统都需要:

  • 数据可靠
  • 并发能力强
  • 更新安全

所以首选 InnoDB。


2)可以考虑 MyISAM 的场景#

MyISAM 现在已经不适合作为主流业务表引擎,但在极少数场景下仍可能被提及。

例如:

  • 历史遗留系统
  • 极端读多写少
  • 对事务完全没有要求
  • 某些特殊只读报表

但要注意,现代项目一般并不推荐新系统使用 MyISAM。

原因是:

  • 不支持事务
  • 只支持表锁
  • 崩溃恢复能力差
  • 并发更新能力差

3)可以考虑 MEMORY 的场景#

如果数据只是临时使用,并且可以接受数据库重启后丢失,那么可以考虑 MEMORY。

例如:

  • 临时计算结果
  • 中间缓存表
  • 会话型非关键数据
  • 测试临时数据

但要注意:

  • 它不适合重要业务数据
  • 重启后数据会丢失

4)实际选择思路#

可以简单按下面思路判断:

需要事务吗?

  • 需要:选 InnoDB
  • 不需要:再考虑其他

写操作多吗?

  • 多:优先 InnoDB
  • 少:也通常还是 InnoDB

数据重要吗?

  • 重要:InnoDB
  • 临时数据:MEMORY

是否只是历史老系统?

  • 是:可能还存在 MyISAM
  • 新系统:基本不要选 MyISAM

5)为什么现代项目基本统一用 InnoDB?#

因为 InnoDB 的综合能力最强:

  1. 支持事务
  2. 支持行锁,适合高并发
  3. 崩溃后可恢复
  4. 支持 MVCC
  5. 支持聚簇索引
  6. 官方默认支持和优化最充分

对业务开发来说,统一用 InnoDB 也能减少维护复杂度。


6)一个数据库里能不能混用不同引擎?#

可以。 MySQL 的存储引擎是表级别的,不是数据库级别的。

也就是说,同一个库中:

  • A 表可以用 InnoDB
  • B 表可以用 MEMORY

不过实际开发中,为了统一维护,绝大多数核心业务表会统一使用 InnoDB。


7)总结#

选择存储引擎的基本原则:

  • 大多数业务场景:InnoDB
  • 临时数据:MEMORY
  • MyISAM:仅历史遗留或极特殊场景

一句话概括:

除非你非常明确知道自己为什么不用 InnoDB,否则就用 InnoDB。


18. InnoDB 和 MyISAM 主要有什么区别?#

答:

InnoDB 和 MyISAM 是 MySQL 中最典型的两种存储引擎。 它们在事务、锁、索引、恢复能力等方面差异很大。

这个问题是面试高频题,建议从几个维度系统回答。


1)事务支持#

InnoDB

支持事务,满足 ACID,支持:

  • BEGIN
  • COMMIT
  • ROLLBACK

MyISAM

不支持事务。 一旦执行写操作,通常无法回滚。

区别意义

如果业务涉及:

  • 转账
  • 订单
  • 库存扣减
  • 支付

那必须优先 InnoDB。


2)锁粒度不同#

InnoDB

支持行级锁,并发性能更好。 更新一行,通常只锁那一行(准确说是锁索引记录)。

MyISAM

只支持表级锁。 更新一条记录时,可能锁住整张表。

区别意义

  • 并发写多的系统,MyISAM 性能很差
  • InnoDB 更适合高并发业务

3)外键支持#

InnoDB

支持外键约束

MyISAM

不支持外键

不过需要补充一句: 虽然 InnoDB 支持外键,但很多互联网项目在设计上不一定强依赖数据库外键,而是通过业务层保证关联关系。


4)索引结构不同#

InnoDB

主键索引是聚簇索引,叶子节点直接存整行数据。 辅助索引叶子节点存主键值,需要回表。

MyISAM

索引和数据分离,属于非聚簇索引。 索引叶子节点存的是数据文件地址。


5)崩溃恢复能力#

InnoDB

支持崩溃恢复,因为有:

  • redo log
  • undo log

即使数据库宕机,也能较好恢复已提交事务。

MyISAM

崩溃恢复能力弱。 如果异常宕机,表可能损坏,需要修复。


6)是否保存总行数#

MyISAM

保存表总行数,所以:

SELECT COUNT(*) FROM table;

在没有 where 条件时,执行很快。

InnoDB

不保存精确总行数,所以通常需要扫描统计。

这也是为什么很多人早期会说 MyISAM 在纯统计上更快。


7)主键要求#

InnoDB

如果没有显式定义主键,InnoDB 会:

  • 优先选择非空唯一索引作为聚簇索引
  • 如果也没有,就生成一个隐藏的 6 字节 rowid

MyISAM

可以没有主键,影响没那么大

但从设计规范来说,业务表一般都应该有主键。


8)存储文件不同#

MyISAM

通常对应三个文件:

  • .frm:表结构
  • .MYD:数据文件
  • .MYI:索引文件

InnoDB

表结构和数据存储方式更复杂,可共享表空间,也可独立表空间(.ibd


9)适用场景不同#

InnoDB

适合:

  • OLTP
  • 高并发
  • 事务型业务
  • 核心业务数据

MyISAM

适合:

  • 老系统
  • 极少写、多读
  • 对事务无要求的少量特殊场景

但新项目基本不推荐。


10)总结对比表#

对比项InnoDBMyISAM
事务支持不支持
锁粒度行锁表锁
外键支持不支持
崩溃恢复支持较弱
主键索引聚簇索引非聚簇索引
count(*)不保存总行数保存总行数
并发写性能
默认引擎

11)一句话总结#

InnoDB 强在事务、安全和并发;MyISAM 强在早期简单读场景,但现代业务几乎都应优先选 InnoDB。


19. MySQL 日志文件有哪些?分别有什么作用?#

答:

MySQL 中的日志种类很多,不同日志负责不同的功能。 从面试角度看,最常见的包括:

  1. 错误日志(error log)
  2. 慢查询日志(slow query log)
  3. 一般查询日志(general log)
  4. 二进制日志(binlog)
  5. 重做日志(redo log)
  6. 回滚日志(undo log)

其中前四种偏 MySQL Server 层,后两种偏 InnoDB 引擎层。


1)错误日志(error log)#

错误日志用于记录 MySQL 运行过程中出现的错误、警告和重要事件。

例如:

  • 启动失败
  • 宕机信息
  • 权限异常
  • 表损坏
  • 崩溃恢复信息

作用

  • 排查数据库启动失败
  • 排查运行异常
  • 分析数据库崩溃问题

这是运维排查数据库问题时最先看的日志之一。


2)慢查询日志(slow query log)#

慢查询日志用于记录执行时间超过阈值的 SQL。

例如设置:

long_query_time = 1

表示执行时间超过 1 秒的 SQL 会被记录。

作用

  • 定位慢 SQL
  • 做 SQL 性能优化
  • 发现潜在性能瓶颈

这是性能优化非常关键的日志。


3)一般查询日志(general log)#

一般查询日志会记录客户端发送给 MySQL 的几乎所有 SQL 请求。

包括:

  • 查询语句
  • 更新语句
  • 登录连接信息

作用

  • 调试 SQL
  • 审计请求
  • 排查某些异常请求来源

缺点

  • 日志量很大
  • 对性能有影响

所以线上通常不会长期开启。


4)二进制日志(binlog)#

binlog 是 MySQL Server 层日志,记录所有对数据库有修改的操作,主要包括:

  • INSERT
  • UPDATE
  • DELETE
  • CREATE
  • ALTER
  • DROP

一般不记录普通查询 SELECT

作用

  1. 主从复制
  2. 数据恢复
  3. 增量备份
  4. 审计数据变更

binlog 是 MySQL 高可用体系中非常重要的一种日志。


5)重做日志(redo log)#

redo log 是 InnoDB 特有日志,用于记录对数据页的物理修改。 它的核心作用是:

  • 保证事务持久性
  • 支持崩溃恢复

当事务提交时,不一定马上把数据页写盘,但会先写 redo log。 如果数据库宕机,重启后可以通过 redo log 恢复已提交事务。


6)回滚日志(undo log)#

undo log 也是 InnoDB 特有日志,用于保存数据修改前的旧版本。

作用

  1. 事务回滚
  2. MVCC 多版本并发控制

例如:

  • 执行 update 前记录旧值
  • 如果事务回滚,就把旧值恢复
  • 快照读时也可能通过 undo log 找旧版本数据

7)总结分类#

可以简单分成两类:

Server 层日志#

  • error log
  • slow query log
  • general log
  • binlog

InnoDB 层日志#

  • redo log
  • undo log

8)面试总结回答模板#

如果面试官问“有哪些日志”,推荐这样答:

MySQL 常见日志包括错误日志、慢查询日志、一般查询日志、binlog,以及 InnoDB 特有的 redo log 和 undo log。

错误日志用于排查数据库运行异常;

慢查询日志用于定位慢 SQL;

general log 用于记录所有请求;

binlog 用于主从复制和数据恢复;

redo log 用于保证事务持久性和崩溃恢复;

undo log 用于事务回滚和 MVCC


20. binlogredo log 有什么区别?#

答:

binlogredo log 都是 MySQL 中非常重要的日志,但它们属于不同层次、记录不同内容、解决不同问题

这个问题是高频面试题,建议从多个维度来回答。


1)所属层次不同#

binlog

属于 MySQL Server 层 日志。 所有存储引擎都可以使用 binlog。

redo log

属于 InnoDB 存储引擎层 日志。 只有 InnoDB 才有 redo log。


2)记录内容不同#

binlog

记录的是逻辑操作,例如:

  • 哪条 SQL 被执行了
  • 哪些数据被修改了

它关注的是“做了什么”。

redo log

记录的是物理页修改,也就是数据页被改成了什么样。 它关注的是“页怎么变”。

所以常说:

  • binlog 是逻辑日志
  • redo log 是物理日志

3)作用不同#

binlog 的主要作用

  1. 主从复制
  2. 数据恢复
  3. 增量备份
  4. 审计变更

redo log 的主要作用

  1. 保证事务持久性
  2. 支持崩溃恢复(crash-safe)

4)写入时机不同#

redo log

事务执行过程中就会不断写入 redo log buffer,提交时根据策略刷盘。

binlog

通常是在事务提交阶段写入。

也就是说,redo log 更偏向事务执行过程中的持久性保障;binlog 更偏向提交后的逻辑变更记录。


5)写入方式不同#

redo log

循环写。 因为 redo log 文件大小固定,写满后会覆盖旧内容。

binlog

追加写。 一个 binlog 文件写满后,会生成新的文件,不会覆盖旧文件。


6)文件用途不同#

binlog

适合:

  • 用于同步到从库
  • 用于按时间点恢复
  • 用于分析历史变更

redo log

适合:

  • 宕机恢复当前实例的数据
  • 保证已提交事务不丢失

7)为什么两者都需要?#

很多人会问:有了 binlog 为什么还要 redo log?

原因是两者解决的问题不同:

  • redo log 解决 事务持久性和崩溃恢复
  • binlog 解决 复制和逻辑恢复

redo log 是 InnoDB 为了 crash-safe 必需的。 binlog 是 MySQL 为了复制和备份必需的。 两者不能互相完全替代。


8)总结对比表#

对比项binlogredo log
所属层Server 层InnoDB 层
适用引擎所有引擎仅 InnoDB
日志类型逻辑日志物理日志
写入方式追加写循环写
写入时机提交阶段执行中/提交时
主要作用主从复制、恢复持久性、崩溃恢复

9)一句话总结#

binlog 记录“做了什么”,redo log 记录“页怎么改”;binlog 用于复制恢复,redo log 用于事务持久和 crash-safe。


21. 一条 update 语句是怎么执行的?#

答:

一条 update 语句的执行过程,比普通 select 更复杂,因为它不仅要查数据,还要:

  • 修改数据
  • 加锁
  • 写 undo log
  • 写 redo log
  • 写 binlog
  • 提交事务

这是 MySQL 面试里非常高频的问题,尤其在问到日志、事务、两阶段提交时,通常都会围绕这道题展开。


1)先看一个例子#

UPDATE account SET balance = balance - 100 WHERE id = 1;

这条 SQL 执行时,MySQL 会经历多个步骤。


2)连接器:建立连接、权限校验#

客户端把 SQL 发给 MySQL 后,首先经过连接器:

  • 判断用户是否已连接
  • 检查是否有执行 UPDATE 的权限

如果没有权限,直接报错,不会继续往下执行。


3)解析器:解析 SQL#

解析器负责对 SQL 做:

  • 词法分析
  • 语法分析

识别出:

  • 这是一个 UPDATE
  • 表是 account
  • 修改的是 balance
  • 条件是 id = 1

如果 SQL 语法有错误,会在这里报错。


4)优化器:生成执行计划#

优化器会判断:

  • WHERE id = 1 是否可以走索引
  • 是否命中主键索引
  • 如何找到目标记录成本最低

如果 id 是主键,那么一般会直接走主键索引,效率最高。


5)执行器:调用存储引擎查找目标行#

执行器开始真正执行 SQL。 它会调用 InnoDB 引擎接口去查找 id = 1 这条记录。

如果这一行所在的数据页已经在 Buffer Pool 中,就直接读取。 如果不在,则需要先从磁盘加载到内存。


6)加锁#

因为这是更新语句,不只是读,还要写,所以 InnoDB 会对目标记录加锁。

如果是:

WHERE id = 1

并且 id 是主键,那么通常会加的是对应索引记录的排他锁(X 锁)。

这一步的作用是:

  • 防止其他事务同时修改这行
  • 保证事务隔离性

7)记录 undo log#

正式修改数据前,InnoDB 会先记录 undo log

为什么要先记旧值? 因为如果事务后续失败或者执行 ROLLBACK,就需要借助 undo log 把数据恢复回去。

例如原来:

balance = 1000

现在要改成:

balance = 900

那么 undo log 中会保存旧值相关信息。

undo log 的两个作用:

  1. 事务回滚
  2. MVCC 提供历史版本

8)修改内存中的数据页#

接着 InnoDB 会在内存中把这一行数据改掉。 注意,这时通常改的是 Buffer Pool 中的数据页,不是直接立刻改磁盘文件。

也就是说,数据此时先变成“内存中已修改、磁盘还没同步”的状态。

这类页叫做:脏页


9)写 redo log(prepare 阶段)#

数据页改完后,InnoDB 会把这次修改写入 redo log buffer,随后在合适时机刷到 redo log 文件中。

这一步通常会先把 redo log 写成:

prepare 状态

也就是 redo log 两阶段提交里的第一阶段。

redo log 的作用是:

  • 防止数据库宕机后,已提交事务丢失
  • 保证事务持久性

10)写 binlog#

执行器再把这次更新操作写入 binlog。

binlog 是 Server 层日志,记录的是逻辑上的变更,例如:

  • 修改了哪张表
  • 哪条记录
  • 改成什么值

它主要用于:

  • 主从复制
  • 数据恢复
  • 增量备份

11)提交事务:redo log commit#

binlog 写成功后,执行器通知 InnoDB 提交事务。 此时 redo log 会从 prepare 状态变成 commit 状态。

这一步完成后,事务就真正提交成功了。


12)后续刷脏页到磁盘#

事务提交成功,不代表数据页已经立刻写到表文件里。 真正的数据页刷盘,通常会在后续某个时刻由后台线程完成,例如:

  • checkpoint
  • buffer pool 压力大
  • 数据库正常关闭
  • 后台定时刷盘

这也是为什么 MySQL 可以做到:

  • 事务提交很快
  • 不需要每次都同步刷整页数据

因为它先写日志,再异步刷页。


13)整体流程总结#

一条 update 语句大致流程可以总结为:

  1. 连接器校验权限
  2. 解析器解析 SQL
  3. 优化器选择执行计划
  4. 执行器调用引擎查找目标行
  5. 对目标行加锁
  6. 写 undo log
  7. 修改 Buffer Pool 中的数据页
  8. 写 redo log(prepare)
  9. 写 binlog
  10. 提交事务,redo log 改为 commit
  11. 后续脏页异步刷盘

14)为什么要先写日志再刷数据?#

因为直接写数据页成本高:

  • 数据页随机 IO
  • 刷盘开销大

而写日志通常是顺序 IO,速度快很多。 所以 MySQL 使用的是典型的 WAL(Write Ahead Logging) 思想:

先写日志,再写数据

这样既保证了持久性,也提升了性能。


15)一句话总结#

一条 update 语句本质上是:先定位并加锁,再记录 undo/redo/binlog,最后提交事务,脏页异步落盘。


22. 为什么要两阶段提交?#

答:

MySQL 在执行更新事务时,通常会同时涉及两类重要日志:

  • redo log:InnoDB 层,保证持久性
  • binlog:Server 层,用于主从复制和恢复

为了保证这两类日志的一致性,MySQL 引入了 两阶段提交(Two-Phase Commit)


1)问题背景:为什么会有一致性问题?#

假设没有两阶段提交,而是简单地按某种顺序写日志,可能出现问题。

事务提交时本质上要做两件事:

  1. 写 redo log
  2. 写 binlog

如果这两步中间宕机,就可能导致两份日志状态不一致。


2)情况一:先写 redo log,再写 binlog#

假设执行流程是:

  1. redo log 写成功
  2. 还没来得及写 binlog,MySQL 宕机

会发生什么?

  • redo log 已经落盘
  • 崩溃恢复时,InnoDB 认为事务已提交,可以恢复数据
  • 但 binlog 没有这条记录

结果:

  • 主库本地恢复出了这条数据
  • 从库无法通过 binlog 同步这条变更
  • 主从数据不一致

3)情况二:先写 binlog,再写 redo log#

流程变成:

  1. binlog 写成功
  2. redo log 还没写完,MySQL 宕机

会发生什么?

  • binlog 已记录这次变更
  • 主从复制时,从库会执行这条更新
  • 但主库本地 redo log 不完整,崩溃恢复后可能认为事务没提交

结果:

  • 从库有这条数据
  • 主库恢复后没有
  • 仍然主从不一致

4)本质问题是什么?#

本质问题是:

redo log 和 binlog 分属不同层,写入动作不是天然原子的。

如果没有额外机制,就无法保证:

  • 两者都成功
  • 或两者都失败

于是就需要一种协调机制,把它们的提交状态统一起来,这就是两阶段提交


5)两阶段提交怎么做?#

两阶段提交流程如下:

第一阶段:redo log prepare#

事务执行时,先把 redo log 写入,并标记为:

prepare

此时表示:

  • 事务已经准备好提交
  • 但还不能认定真正提交完成

第二阶段:写 binlog#

redo log prepare 成功后,再写 binlog。

如果 binlog 写成功,说明 Server 层这边也记录成功了。

第三步:redo log commit#

最后再把 redo log 从 prepare 状态改成 commit 状态。

此时事务才算最终提交成功。


6)崩溃恢复时怎么判断事务是否提交?#

如果数据库宕机后恢复,发现某条 redo log 是 prepare 状态,InnoDB 不会立刻认定它提交成功,而是会去检查:

  • 对应的 binlog 是否完整存在

如果 binlog 存在且完整,说明事务其实已经提交成功,只是 redo log commit 来不及写,恢复时会补提交。

如果 binlog 不存在,则说明事务未完整提交,恢复时会回滚。

这就保证了两份日志的一致性。


7)两阶段提交解决了什么问题?#

核心就是解决:

  • 主库 crash-safe
  • 主从复制一致性
  • 基于 binlog 的恢复一致性

一句话说:

它让 redo log 和 binlog 在逻辑上保持同一提交状态。


8)为什么不能只用一种日志?#

不能只用 redo log#

redo log 只能用于当前实例 crash recovery,不能直接做:

  • 主从复制
  • 逻辑恢复
  • 增量备份

不能只用 binlog#

binlog 是逻辑日志,不能高效支持 InnoDB 的页级崩溃恢复,也无法替代 WAL。

所以两者职责不同,必须都保留。


9)一句话记忆#

你可以这样记:

  • redo log 负责“本地不丢”
  • binlog 负责“别人知道”
  • 两阶段提交负责“本地不丢”和“别人知道”必须一致

10)总结#

两阶段提交的核心流程:

  1. redo log 写 prepare
  2. binlog 写入成功
  3. redo log 写 commit

它的目的是:

保证 redo log 和 binlog 的一致性,避免主从不一致和恢复不一致。


23. redo log 是怎么刷入磁盘的?#

答:

redo log 并不是每产生一条就立刻直接写磁盘文件,而是会先写到内存中的 redo log buffer,然后再在合适时机刷入磁盘。

这个问题主要考察你是否理解:

  • redo log 的写入路径
  • 刷盘时机
  • 为什么这样设计

1)redo log 的写入位置#

redo log 的写入大致分两层:

  1. redo log buffer(内存)
  2. redo log file(磁盘)

事务执行时,产生的 redo 记录会先进入 redo log buffer。 之后再根据一定规则,把 buffer 里的内容刷到磁盘 redo log 文件中。

这和数据页也是类似思路:

  • 先改内存
  • 再异步持久化

2)为什么不直接写磁盘?#

因为每次事务修改都直接同步写磁盘,性能会很差。 磁盘 IO 尤其是随机 IO 成本高,而日志写入是顺序写,适合先缓冲后刷盘。

这样设计的好处是:

  • 提高吞吐量
  • 减少磁盘写压力
  • 让事务提交更快

3)redo log 什么时候刷盘?#

常见刷盘时机有以下几种:


(1)事务提交时#

这是最重要的一种情况。 事务提交时,为了保证持久性,redo log buffer 中与该事务相关的日志通常要刷盘。

这里要注意,是否“每次提交都真正 fsync 到磁盘”,和参数有关,最关键的是:

innodb_flush_log_at_trx_commit

这个参数决定提交时 redo log 的刷盘策略。


(2)redo log buffer 空间不够#

redo log buffer 大小有限。 如果 buffer 快写满了,就必须提前把部分内容刷到磁盘,腾出空间。


(3)后台线程定时刷盘#

InnoDB 有后台线程,会周期性地把 redo log buffer 刷到磁盘。 通常是大约每秒触发一次。


(4)数据库正常关闭时#

MySQL 正常关闭前,会把内存中的 redo log 刷盘,保证尽量不丢数据。


(5)触发 checkpoint 时#

当 redo log 可用空间不足时,会推动 checkpoint,把脏页刷盘,并腾出 redo log 的可重用空间。 在这个过程中,也会涉及 redo log 的刷盘和推进。


4)innodb_flush_log_at_trx_commit 三种模式#

这是高频考点。


模式 1:innodb_flush_log_at_trx_commit = 1#

每次事务提交时:

  1. 把 redo log 写到 OS buffer
  2. 立即调用 fsync 刷到磁盘

特点

  • 最安全
  • 崩溃时已提交事务几乎不丢
  • 性能相对低一些

这是默认值。


模式 2:innodb_flush_log_at_trx_commit = 2#

每次事务提交时:

  1. 把 redo log 写到 OS buffer
  2. 不立即 fsync
  3. 由操作系统决定何时刷到磁盘

特点

  • 比模式 1 快
  • MySQL 崩溃数据一般不丢
  • 但如果操作系统崩溃,可能丢失最近日志

模式 0:innodb_flush_log_at_trx_commit = 0#

事务提交时不主动把 redo log buffer 写盘。 通常由后台线程每秒刷一次。

特点

  • 性能最好
  • 最不安全
  • 宕机可能丢失 1 秒左右事务

5)为什么说 redo log 可以保证持久性?#

因为事务提交时,至少 redo log 会先落到更安全的位置。 哪怕数据页还没来得及刷盘,只要 redo log 在,崩溃恢复时就能把已提交事务重做出来。

这就是 redo log 的核心价值:

已提交事务的数据,哪怕数据页没落盘,也能在重启后恢复。


6)redo log 文件为什么是循环写?#

redo log 文件大小是固定的,不会无限增长。 它有两个重要位置:

  • write pos:当前写入位置
  • checkpoint:当前可覆盖位置

redo log 从头写到尾,写满后再回到开头继续写。 如果 write pos 追上 checkpoint,就说明 redo log 空间不够了,必须推动 checkpoint。

这样设计的好处:

  • 控制日志文件大小
  • 保持高效顺序写

7)一句话理解 redo log 刷盘#

可以这样理解:

事务修改先写内存数据页和 redo log buffer,提交时优先保证 redo log 落盘,数据页以后慢慢刷。

这也是 WAL 思想的具体体现。


8)总结#

redo log 的刷盘路径是:

事务修改 -> redo log buffer -> redo log file

常见刷盘时机:

  1. 事务提交
  2. buffer 不足
  3. 后台线程定时
  4. 正常关闭
  5. checkpoint 推进

最关键参数:

  • innodb_flush_log_at_trx_commit

一句话总结:

redo log 通过“先写日志、后刷数据页”来保证事务持久性和高性能。

Comment seems to stuck. Try to refresh?✨