13. 一条 SQL 查询语句的执行顺序是什么?
答:
SQL 的书写顺序和真正执行顺序是不一样的。 这是面试里非常常见的问题,尤其喜欢结合:
wheregroup byhavingorder bylimit
来问。
1)SQL 常见书写顺序
SELECT ...FROM ...JOIN ...ON ...WHERE ...GROUP BY ...HAVING ...ORDER BY ...LIMIT ...这是我们写 SQL 的顺序。
2)逻辑执行顺序
一条查询 SQL 的典型逻辑执行顺序是:
FROMONJOINWHEREGROUP BYHAVINGSELECTDISTINCTORDER BYLIMIT
3)逐步解释
(1)FROM
先确定从哪张表取数据。
FROM user如果是多表查询,会先形成基础数据集。
(2)ON
如果有连接操作,先根据 ON 条件判断连接关系。
FROM user uLEFT 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 104)为什么要理解执行顺序?
因为很多 SQL 问题都和执行顺序有关。
例如:为什么 WHERE 不能用聚合函数?
因为 WHERE 在 GROUP BY 之前执行,而聚合函数是分组后才有的结果。
错误写法:
SELECT dept_id, COUNT(*)FROM empWHERE COUNT(*) > 5GROUP BY dept_id;正确写法:
SELECT dept_id, COUNT(*)FROM empGROUP BY dept_idHAVING COUNT(*) > 5;5)一个完整示例
SELECT dept_id, COUNT(*) AS totalFROM empWHERE salary > 5000GROUP BY dept_idHAVING COUNT(*) > 2ORDER BY total DESCLIMIT 3;执行逻辑:
- 从
emp表取数据 WHERE salary > 5000先过滤- 按
dept_id分组 HAVING COUNT(*) > 2过滤分组结果SELECT选择要返回的列ORDER BY total DESC排序LIMIT 3取前 3 条
6)总结
SQL 书写顺序不等于执行顺序。 典型查询逻辑顺序是:
FROM -> ON -> JOIN -> WHERE -> GROUP BY -> HAVING -> SELECT -> DISTINCT -> ORDER BY -> LIMIT理解这一点,很多 SQL 语法和优化问题都会更清楚。
14. 说说 MySQL 的基础架构?
答:
MySQL 的逻辑架构通常可以分成三层:
- 客户端层
- Server 层
- 存储引擎层
这个问题主要考察你是否理解: 一条 SQL 从客户端发出后,MySQL 内部是如何分层处理的。
1)客户端层
这一层主要负责和客户端交互,包括:
- 建立连接
- 身份认证
- 权限校验
- 网络通信
常见客户端可以是:
- MySQL 命令行
- Navicat / DataGrip
- Java 程序 JDBC
- Python / Go / PHP 数据库驱动
也就是说,用户不是直接和存储引擎打交道,而是先进入 MySQL 服务端入口。
2)Server 层
这一层是 MySQL 的核心服务层,负责大多数通用逻辑,和具体存储引擎无关。
主要包括:
(1)连接器
负责客户端连接、登录认证、权限检查。
(2)查询缓存(老版本)
MySQL 8.0 已移除,老版本中会先看是否命中缓存。
(3)解析器
负责 SQL 的词法分析、语法分析。 比如识别:
SELECTFROMWHERE
并检查 SQL 语法是否正确。
(4)优化器
负责生成执行计划,比如:
- 选择哪个索引
- 决定 join 顺序
- 决定是否走全表扫描
(5)执行器
根据执行计划调用存储引擎接口,真正执行 SQL。
(6)内置函数 / 视图 / 存储过程 / 触发器
这些跨引擎的功能也在 Server 层实现。
3)存储引擎层
存储引擎层是真正负责:
- 数据存储
- 数据读取
- 索引管理
- 事务 / 锁 / 日志(视引擎而定)
MySQL 支持多种存储引擎,例如:
- InnoDB
- MyISAM
- Memory
Server 层通过统一接口调用存储引擎,所以不同引擎对上层查询过程透明。
4)为什么说 MySQL 是插件式存储引擎架构?
因为 MySQL 把“SQL 解析执行”和“数据存储实现”分离了。 上层是统一的 Server 层,下层可以替换不同存储引擎。
这意味着:
- 同一个数据库中,不同表甚至可以使用不同引擎
- 例如一张表用 InnoDB,另一张表用 Memory
5)架构的价值
这种分层设计的好处有:
-
职责清晰
- Server 层负责 SQL 逻辑
- 引擎层负责数据存储
-
扩展性强
- 可支持多种存储引擎
-
上层统一
- 客户端使用体验一致
6)总结
MySQL 基础架构可以概括为:
- 客户端层:连接、认证、权限
- Server 层:解析、优化、执行
- 存储引擎层:存储、索引、事务、日志
一句话总结就是:
Server 层负责“怎么查”,存储引擎层负责“怎么存、怎么取”。
15. 一条 SQL 查询语句在 MySQL 中如何执行的?
答:
一条 SQL 在 MySQL 中的执行过程,大致可以分为以下几个阶段:
- 客户端连接
- 权限校验
- 解析 SQL
- 优化执行计划
- 调用存储引擎
- 返回结果
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_ageidx_nameidx_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)常见对比表
| 特性 | InnoDB | MyISAM | MEMORY |
|---|---|---|---|
| 事务 | 支持 | 不支持 | 不支持 |
| 行锁 | 支持 | 不支持 | 不支持 |
| 表锁 | 支持 | 支持 | 支持 |
| 外键 | 支持 | 不支持 | 不支持 |
| 崩溃恢复 | 支持 | 弱 | 不适用 |
| 数据存储 | 磁盘 | 磁盘 | 内存 |
| 默认引擎 | 是 | 否 | 否 |
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 的综合能力最强:
- 支持事务
- 支持行锁,适合高并发
- 崩溃后可恢复
- 支持 MVCC
- 支持聚簇索引
- 官方默认支持和优化最充分
对业务开发来说,统一用 InnoDB 也能减少维护复杂度。
6)一个数据库里能不能混用不同引擎?
可以。 MySQL 的存储引擎是表级别的,不是数据库级别的。
也就是说,同一个库中:
- A 表可以用 InnoDB
- B 表可以用 MEMORY
不过实际开发中,为了统一维护,绝大多数核心业务表会统一使用 InnoDB。
7)总结
选择存储引擎的基本原则:
- 大多数业务场景:InnoDB
- 临时数据:MEMORY
- MyISAM:仅历史遗留或极特殊场景
一句话概括:
除非你非常明确知道自己为什么不用 InnoDB,否则就用 InnoDB。
18. InnoDB 和 MyISAM 主要有什么区别?
答:
InnoDB 和 MyISAM 是 MySQL 中最典型的两种存储引擎。 它们在事务、锁、索引、恢复能力等方面差异很大。
这个问题是面试高频题,建议从几个维度系统回答。
1)事务支持
InnoDB
支持事务,满足 ACID,支持:
BEGINCOMMITROLLBACK
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)总结对比表
| 对比项 | InnoDB | MyISAM |
|---|---|---|
| 事务 | 支持 | 不支持 |
| 锁粒度 | 行锁 | 表锁 |
| 外键 | 支持 | 不支持 |
| 崩溃恢复 | 支持 | 较弱 |
| 主键索引 | 聚簇索引 | 非聚簇索引 |
| count(*) | 不保存总行数 | 保存总行数 |
| 并发写性能 | 高 | 低 |
| 默认引擎 | 是 | 否 |
11)一句话总结
InnoDB 强在事务、安全和并发;MyISAM 强在早期简单读场景,但现代业务几乎都应优先选 InnoDB。
19. MySQL 日志文件有哪些?分别有什么作用?
答:
MySQL 中的日志种类很多,不同日志负责不同的功能。 从面试角度看,最常见的包括:
- 错误日志(error log)
- 慢查询日志(slow query log)
- 一般查询日志(general log)
- 二进制日志(binlog)
- 重做日志(redo log)
- 回滚日志(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 层日志,记录所有对数据库有修改的操作,主要包括:
INSERTUPDATEDELETECREATEALTERDROP
一般不记录普通查询 SELECT
作用
- 主从复制
- 数据恢复
- 增量备份
- 审计数据变更
binlog 是 MySQL 高可用体系中非常重要的一种日志。
5)重做日志(redo log)
redo log 是 InnoDB 特有日志,用于记录对数据页的物理修改。 它的核心作用是:
- 保证事务持久性
- 支持崩溃恢复
当事务提交时,不一定马上把数据页写盘,但会先写 redo log。 如果数据库宕机,重启后可以通过 redo log 恢复已提交事务。
6)回滚日志(undo log)
undo log 也是 InnoDB 特有日志,用于保存数据修改前的旧版本。
作用
- 事务回滚
- 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. binlog 和 redo log 有什么区别?
答:
binlog 和 redo log 都是 MySQL 中非常重要的日志,但它们属于不同层次、记录不同内容、解决不同问题。
这个问题是高频面试题,建议从多个维度来回答。
1)所属层次不同
binlog
属于 MySQL Server 层 日志。 所有存储引擎都可以使用 binlog。
redo log
属于 InnoDB 存储引擎层 日志。 只有 InnoDB 才有 redo log。
2)记录内容不同
binlog
记录的是逻辑操作,例如:
- 哪条 SQL 被执行了
- 哪些数据被修改了
它关注的是“做了什么”。
redo log
记录的是物理页修改,也就是数据页被改成了什么样。 它关注的是“页怎么变”。
所以常说:
binlog是逻辑日志redo log是物理日志
3)作用不同
binlog 的主要作用
- 主从复制
- 数据恢复
- 增量备份
- 审计变更
redo log 的主要作用
- 保证事务持久性
- 支持崩溃恢复(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)总结对比表
| 对比项 | binlog | redo 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 的两个作用:
- 事务回滚
- 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 语句大致流程可以总结为:
- 连接器校验权限
- 解析器解析 SQL
- 优化器选择执行计划
- 执行器调用引擎查找目标行
- 对目标行加锁
- 写 undo log
- 修改 Buffer Pool 中的数据页
- 写 redo log(prepare)
- 写 binlog
- 提交事务,redo log 改为 commit
- 后续脏页异步刷盘
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)问题背景:为什么会有一致性问题?
假设没有两阶段提交,而是简单地按某种顺序写日志,可能出现问题。
事务提交时本质上要做两件事:
- 写 redo log
- 写 binlog
如果这两步中间宕机,就可能导致两份日志状态不一致。
2)情况一:先写 redo log,再写 binlog
假设执行流程是:
- redo log 写成功
- 还没来得及写 binlog,MySQL 宕机
会发生什么?
- redo log 已经落盘
- 崩溃恢复时,InnoDB 认为事务已提交,可以恢复数据
- 但 binlog 没有这条记录
结果:
- 主库本地恢复出了这条数据
- 从库无法通过 binlog 同步这条变更
- 主从数据不一致
3)情况二:先写 binlog,再写 redo log
流程变成:
- binlog 写成功
- 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)总结
两阶段提交的核心流程:
- redo log 写 prepare
- binlog 写入成功
- redo log 写 commit
它的目的是:
保证 redo log 和 binlog 的一致性,避免主从不一致和恢复不一致。
23. redo log 是怎么刷入磁盘的?
答:
redo log 并不是每产生一条就立刻直接写磁盘文件,而是会先写到内存中的 redo log buffer,然后再在合适时机刷入磁盘。
这个问题主要考察你是否理解:
- redo log 的写入路径
- 刷盘时机
- 为什么这样设计
1)redo log 的写入位置
redo log 的写入大致分两层:
- redo log buffer(内存)
- 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
每次事务提交时:
- 把 redo log 写到 OS buffer
- 立即调用
fsync刷到磁盘
特点
- 最安全
- 崩溃时已提交事务几乎不丢
- 性能相对低一些
这是默认值。
模式 2:innodb_flush_log_at_trx_commit = 2
每次事务提交时:
- 把 redo log 写到 OS buffer
- 不立即 fsync
- 由操作系统决定何时刷到磁盘
特点
- 比模式 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常见刷盘时机:
- 事务提交
- buffer 不足
- 后台线程定时
- 正常关闭
- checkpoint 推进
最关键参数:
innodb_flush_log_at_trx_commit
一句话总结:
redo log 通过“先写日志、后刷数据页”来保证事务持久性和高性能。