MySQL 核心原理笔记

整理自技术问答,涵盖 MySQL 架构、日志机制、事务原理、MVCC、锁机制、崩溃恢复、主从复制、SQL调优等核心面试知识点。

一、MySQL 整体架构

1.1 层级划分

层级

组件

职责

Server 层

连接器、解析器、优化器、执行器

连接管理、SQL 解析与优化、权限校验

Server 层

Binlog(归档日志)

主从复制、基于时间点的数据恢复

InnoDB 层

存储引擎

数据存储、事务、MVCC、崩溃恢复

InnoDB 层

Redo Log(重做日志)

事务持久性,崩溃恢复时重做

InnoDB 层

Undo Log(回滚日志)

事务回滚、MVCC 快照读

1.2 关键认知锚点

  • Redo Log 是 InnoDB 引擎层的物理日志,记录“在某个数据页的某个偏移量处写入什么值”。

  • Binlog 是 Server 层的逻辑日志,记录 SQL 语句或行变更,所有存储引擎共用。

  • Undo Log 是逻辑日志,记录修改前的旧值,用于回滚和 MVCC。

二、三大日志详解

2.1 Redo Log(重做日志)

属性

说明

归属层

InnoDB 存储引擎

日志类型

物理日志(数据页 + 偏移量 + 值)

核心作用

保障事务持久性(Durability),崩溃恢复时重做

写入时机

事务执行过程中持续写入(WAL 机制)

存储位置

ib_logfile0ib_logfile1(循环写)

关键参数

innodb_flush_log_at_trx_commit

2.2 Binlog(归档日志)

属性

说明

归属层

Server 层(所有引擎共用)

日志类型

逻辑日志(SQL 语句或行变更)

核心作用

主从复制基于时间点的数据恢复

写入时机

事务提交时写入

存储位置

mysql-bin.xxxxxx(追加写,可设置过期时间)

关键参数

sync_binlogexpire_logs_days

格式

STATEMENT / ROW(推荐) / MIXED

2.3 Undo Log(回滚日志)

属性

说明

归属层

InnoDB 存储引擎

日志类型

逻辑日志(记录修改前的旧值)

核心作用

事务回滚(原子性)、MVCC 快照读

写入时机

数据修改前写入

存储位置

Undo 表空间ibdata1 或独立 .ibu 文件)

2.4 三种日志职责对比

日志类型

日志格式

归属层

核心职责

Redo Log

物理(页 + 偏移量)

InnoDB

崩溃恢复重做(持久性)

Undo Log

逻辑(旧值)

InnoDB

事务回滚 + MVCC(原子性)

Binlog

逻辑(SQL/行变更)

Server

主从复制 + 时间点恢复

三、两阶段提交(2PC)

3.1 为什么需要两阶段提交?

核心目标:保证 Redo LogBinlog 的数据一致性,确保主库和从库的数据最终一致,避免事务数据不一致、数据丢失问题。

3.2 两阶段提交流程

事务执行
    │
    ▼
① Redo Log 写入 → 状态标记为 PREPARE
    │
    ▼
② Binlog 写入 → 落盘成功
    │
    ▼
③ Redo Log 状态 → 升级为 COMMIT
    │
    ▼
④ 返回客户端:事务提交成功 ✅

3.3 各阶段宕机恢复策略

宕机时间点

Redo Log 状态

Binlog 状态

恢复策略

步骤①之前

事务丢失,数据不变

步骤①后、②前

PREPARE

不完整/缺失

回滚(利用 Undo Log)

步骤②后、③前

PREPARE

完整

提交(重做 Redo Log)

步骤③后

COMMIT

完整

重做(数据页补全)

核心结论Binlog 是崩溃恢复时的“最终仲裁者”。Binlog 完整 → 提交;Binlog 不完整 → 回滚。

四、一条 UPDATE 语句的完整执行流程

UPDATE users SET age = 25 WHERE id = 1;

4.1 完整流程图

客户端发送 UPDATE 语句
        │
        ▼
【Server层】连接器 → 解析器 → 优化器 → 执行器
        │
        ▼
【InnoDB层】加载数据页到 Buffer Pool
        │
        ▼
【InnoDB层】写入 Undo Log(记录旧值,用于回滚)
        │
        ▼
【InnoDB层】修改 Buffer Pool 中的数据页(内存,变为脏页)
        │
        ▼
【InnoDB层】写入 Redo Log(PREPARE,物理修改)
        │
        ▼
【Server层】写入 Binlog(逻辑变更)
        │
        ▼
【InnoDB层】Redo Log 状态升级为 COMMIT(两阶段提交完成)
        │
        ▼
【Server层】返回客户端:执行成功 ✅
        │
        ▼
【后台异步】Redo Log / Binlog 根据刷盘策略落盘
        │
        ▼
【后台异步】脏页刷回磁盘(Checkpoint 触发)

4.2 各阶段详解

阶段

位置

动作

关键点

SQL 解析

Server - 解析器

词法/语法解析,生成解析树

-

SQL 优化

Server - 优化器

生成执行计划,选择索引

决定使用哪个索引

加载数据

InnoDB - Buffer Pool

从磁盘加载数据页到内存

如果已在 Buffer Pool 则跳过

写入 Undo

InnoDB - Undo 表空间

记录修改前的旧值

用于回滚和 MVCC

修改内存

InnoDB - Buffer Pool

在内存中修改数据

数据页变为“脏页”

写入 Redo

InnoDB - Redo Log Buffer

记录物理修改,状态 PREPARE

WAL 核心步骤

写入 Binlog

Server - Binlog Cache

记录逻辑变更

两阶段提交的第二步

Redo Commit

InnoDB - Redo Log

状态升级为 COMMIT

两阶段提交完成

返回成功

Server

向客户端返回成功

此时数据可能还在内存中

刷盘(异步)

InnoDB/Server

日志和数据页落盘

sync_binlog / flush_log_at_trx_commit 控制

五、刷盘策略与数据安全性

5.1 Redo Log 刷盘参数

innodb_flush_log_at_trx_commit = 1  -- 每次事务提交强制落盘(最安全,性能最低)
innodb_flush_log_at_trx_commit = 2  -- 每秒落盘(可能丢 1 秒数据)
innodb_flush_log_at_trx_commit = 0  -- 每秒落盘(可能丢 1 秒数据,性能最好)

5.2 Binlog 刷盘参数

sync_binlog = 1   -- 每次事务提交强制落盘(最安全)
sync_binlog = 0   -- 由操作系统决定(性能最好,风险最高)
sync_binlog = N   -- 每 N 个事务提交后刷盘(可接受少量丢失)

5.3 "双一"配置

双一配置 = innodb_flush_log_at_trx_commit=1 + sync_binlog=1

  • 效果:事务提交时,Redo Log 和 Binlog 都强制落盘,实现数据零丢失。

  • 代价:性能开销最大(频繁磁盘 IO)。

  • 适用场景:金融、支付等对数据零丢失有严格要求的系统。

六、主从复制与高可用

6.1 主从复制原理

主库 (Master)
    │
    ├── 事务提交 → 写入 Binlog
    │
    ▼
从库 (Slave) I/O 线程
    │
    ├── 读取主库 Binlog
    ├── 写入 Relay Log(中继日志)
    │
    ▼
从库 (Slave) SQL 线程
    │
    ├── 读取 Relay Log
    ├── 重放 SQL → 数据同步完成

6.2 主库宕机且 Binlog 丢失时的风险

风险类型

说明

数据丢失

从库未同步的最新事务将永久丢失

复制中断

从库请求的 Binlog 文件/位置已永久丢失 → ERROR 1236

脑裂

旧主库恢复后重新上线,与新主库同时接受写入,数据冲突

数据不一致

主从数据无法对齐,需要人工介入修复

6.3 预防措施

措施

说明

sync_binlog=1

保证 Binlog 实时落盘(最关键)

双一配置

配合 Redo Log 刷盘,保证事务持久性

半同步复制

至少一个从库收到 Binlog 后主库才返回成功

定期备份

全量备份 + Binlog 备份,并定期演练恢复

GTID 开启

简化故障转移,跟踪事务执行状态

合理设置过期时间

expire_logs_daysbinlog_expire_logs_seconds

七、关键参数速查

参数

默认值

说明

推荐值

innodb_flush_log_at_trx_commit

1

Redo Log 刷盘策略

1(金融级)

sync_binlog

1

Binlog 刷盘策略

1(金融级)

binlog_format

ROW

Binlog 记录格式

ROW

expire_logs_days

0

Binlog 过期天数

7-30 天

innodb_log_file_size

48M

Redo Log 文件大小

1G-4G(根据业务调整)

innodb_log_files_in_group

2

Redo Log 文件个数

2-4

八、InnoDB vs MyISAM

对比维度

InnoDB

MyISAM

事务支持

支持 ACID

不支持

行级锁

支持

仅表级锁

外键约束

支持

不支持

崩溃恢复

通过 Redo Log 恢复

易损坏,需修复

MVCC

支持

不支持

全文索引

(5.6+)

适用场景

高并发、OLTP

读多写少、数据仓库

九、补充:WAL 技术

9.1 什么是 WAL?

WAL(Write-Ahead Logging,预写日志) 是一种确保数据完整性的技术,核心思想是:先写日志,再写数据

9.2 WAL 为什么能提升性能?

对比

写 Redo Log

写数据页

IO 类型

顺序 IO(追加写)

随机 IO(随机寻址)

速度

极快

极慢(几十倍差距)

时机

事务提交时同步写

后台异步刷盘

WAL 用快速的顺序日志写,替代了慢速的随机数据页写,大幅提升了数据库写入性能。

十、MVCC 多版本并发控制

10.1 MVCC 核心概念

MVCC(Multi-Version Concurrency Control,多版本并发控制) 是 InnoDB 实现高并发、读写不加锁的核心机制,仅作用于快照读,核心作用是解决读写冲突、实现事务隔离、降低锁竞争

MVCC 仅存在于 InnoDB 引擎,MyISAM 不支持;主要依托 Undo Log(版本数据)Read View(读视图) 实现。

10.2 核心前置知识

10.2.1 隐藏字段

InnoDB 每条数据行都会自带 3 个隐藏字段,是 MVCC 的基础:

  • DB_TRX_ID:6 字节,修改当前行数据的最新事务 ID

  • DB_ROLL_PTR:7 字节,回滚指针,指向 Undo Log 中的上一个旧版本数据

  • DB_ROW_ID:6 字节,行唯一标识(无主键时使用)

10.2.2 快照读 & 当前读

读取类型

定义

对应 SQL

锁机制

快照读

读取数据历史版本,不加锁,依托 MVCC

普通 SELECT(不加锁)

无锁,高并发

当前读

读取数据最新版本,加锁,阻塞并发修改

INSERT、UPDATE、DELETE、SELECT ... FOR UPDATE

加行锁/表锁

10.3 版本链机制

当事务多次修改同一行数据时,每次修改都会生成一条 Undo Log 旧版本数据,通过 DB_ROLL_PTR 串联形成版本链

数据更新流程:修改数据前,先将旧数据存入 Undo Log → 更新内存新数据 → 记录事务 ID 和回滚指针,形成版本链表。

10.4 Read View 读视图

Read View 是事务快照读的核心判断依据,用来筛选版本链中当前事务可见的数据版本,包含 4 个核心字段:

  • m_ids:当前系统中所有活跃未提交的事务 ID 集合

  • min_trx_id:m_ids 中的最小事务 ID

  • max_trx_id:开启当前事务时,系统即将分配的下一个事务 ID

  • creator_trx_id:当前事务自身的 ID

10.5 版本可见性判断规则

遍历版本链,根据数据版本的 DB_TRX_ID 判断是否可见:

  1. 若版本事务ID = 当前事务ID:可见(自己修改的数据)

  2. 若版本事务ID < min_trx_id:可见(事务已提交)

  3. 若版本事务ID >= max_trx_id:不可见(事务开启后新产生的数据)

  4. 若 min_trx_id < 版本事务ID < max_trx_id:判断是否在 m_ids 中,不在则可见,在则不可见

10.6 不同隔离级别下的 MVCC

隔离级别

Read View 生成时机

解决问题

存在问题

读已提交 RC

每次 SELECT 都会生成新 Read View

解决脏读

存在不可重复读

可重复读 RR(默认)

事务内第一次 SELECT 生成 Read View,全程复用

解决脏读、不可重复读

存在幻读(InnoDB 通过间隙锁兜底解决)

十一、MySQL 锁机制

11.1 锁的核心作用

数据库锁是解决并发事务竞争资源的核心机制,保障多事务并发操作时的数据一致性、完整性,避免数据脏写、数据错乱。InnoDB 锁机制配合 MVCC,实现了读写分离、读不加锁、写加锁的高并发能力。

11.2 锁的层级分类

11.2.1 表级锁

锁定整张数据表,开销小、加锁快、无死锁,并发极低。

  • 适用引擎:MyISAM 默认表锁,InnoDB 也支持

  • 特点:加锁后其他事务无法读写整表数据

  • 场景:全表更新、批量数据操作

11.2.2 行级锁(InnoDB 核心)

锁定单行数据,开销大、加锁慢、会产生死锁,并发极高。

  • 适用引擎:仅 InnoDB 支持

  • 特点:仅锁定操作行,其他行可正常读写

  • 核心前提必须走索引!无索引会降级为表锁

11.2.3 元数据锁(MDL)

操作表结构时自动加锁,防止 DDL 与 DML 并发冲突,保证表结构一致性。

11.3 锁的读写分类

锁类型

别称

兼容性

场景

共享锁(S锁)

读锁

读读兼容、读写互斥、写写互斥

SELECT ... LOCK IN SHARE MODE

排他锁(X锁)

写锁

全互斥(读写、写写、读写出均阻塞)

UPDATE、DELETE、INSERT、SELECT ... FOR UPDATE

11.4 意向锁(InnoDB 特有)

意向锁是表级辅助锁,解决行锁与表锁的冲突检测问题,无需手动加锁,InnoDB 自动维护。

  • 意向共享锁(IS):事务要加行S锁,先加表IS锁

  • 意向排他锁(IX):事务要加行X锁,先加表IX锁

核心作用:快速判断整张表是否存在行锁,避免遍历全表锁,提升锁检测效率。

11.5 特殊锁:间隙锁 & 临键锁(解决幻读)

11.5.1 间隙锁(Gap Lock)

锁定索引数据之间的空白区间,不锁定已有数据,防止其他事务在间隙中插入数据,专门解决幻读问题,仅 RR 隔离级别生效。

11.5.2 临键锁(Next-Key Lock)

InnoDB 默认行锁算法,是 记录锁 + 间隙锁 的结合,锁定当前索引行 + 左侧间隙,彻底杜绝幻读。

11.6 死锁成因与解决

11.6.1 死锁产生四大条件

  • 互斥条件:锁资源独占,不可共享

  • 请求与保持:持有锁的同时请求其他锁

  • 不剥夺条件:锁不会被强制释放

  • 环路等待:多个事务循环等待对方锁资源

11.6.2 死锁解决方案

  • 统一 SQL 执行顺序,避免循环等待

  • 缩短事务生命周期,尽早提交/回滚

  • 避免大事务、批量操作拆分

  • 开启死锁检测,超时自动释放锁

十二、面试高频问答(含 MVCC/锁机制)

Q1:Redo Log 和 Binlog 的区别?

对比

Redo Log

Binlog

归属层

InnoDB 引擎

Server 层

日志类型

物理日志(页+偏移量)

逻辑日志(SQL/行)

写入方式

循环写(覆盖)

追加写(可归档)

核心作用

崩溃恢复(持久性)

主从复制、时间点恢复

是否必须

InnoDB 必须

可选择性开启

Q2:为什么需要两阶段提交?

保证 Redo Log 和 Binlog 的数据一致性,避免主库和从库数据不一致。Binlog 是崩溃恢复时的“最终仲裁者”,通过两阶段提交确保两份日志要么同时成功、要么同时回滚。

Q3:事务提交后数据就立刻落盘了吗?

不是。事务提交后,Redo Log 和 Binlog 会根据参数配置优先落盘,但数据页本身仍存在 Buffer Pool 内存中,由后台线程异步刷盘落地。宕机时可通过 Redo Log 重做恢复未落地数据。

Q4:什么时候用 InnoDB,什么时候用 MyISAM?

InnoDB:绝大多数 OLTP 业务场景,需要事务、行锁、高并发、数据安全、崩溃恢复的场景。 MyISAM:纯读多写少、静态数据、简单数据仓库场景,目前基本被 InnoDB 全面替代。

Q5:MVCC 的实现原理是什么?

依托 InnoDB 行隐藏字段、Undo Log 版本链、Read View 读视图实现。事务快照读时,通过 Read View 规则遍历版本链,筛选出当前事务可见的数据版本,实现无锁读、事务隔离。

Q6:为什么会产生幻读?如何解决?

RR 隔离级别下,普通行锁仅锁定已有数据,无法锁定空白区间,其他事务可插入新数据,引发幻读。InnoDB 通过间隙锁+临键锁锁定索引间隙,彻底解决幻读问题。

十三、SQL调优金字塔体系(核心调优全集)

13.1 调优核心本质

SQL调优的终极本质只有两点:减少扫描行数减少回表/临时表/文件排序。所有调优手段均围绕这两个核心展开,以下为完整金字塔分层调优体系(从顶层架构到底层排查,优先级从高到低)。

🏛️ 金字塔第一层:表结构与存储引擎(决定性能上限)

建表阶段的问题是根本性问题,SQL写法优化无法弥补表结构设计缺陷,直接决定数据库性能天花板。

优化点

具体策略

为什么有效

存储引擎

必须用 InnoDB(5.6+ 默认,禁止使用 MyISAM)

InnoDB 支持行锁、事务、MVCC、崩溃恢复,高并发读写性能远优于表锁机制的 MyISAM。

字段类型

能用 int 不用 varchar;能用 varchar(20) 不用 varchar(255);datetime/timestamp 替代字符串日期

字段类型越小,B+树单页可存储的索引条目越多,索引树高度越低,磁盘I/O次数越少,查询效率越高。

范式与反范式

禁止业务高峰期 JOIN 超过3张表;适当字段冗余(反范式),将高频关联字段冗余至主表

空间换时间,彻底消除多表关联带来的临时表、笛卡尔积、回表查询开销,大幅提升查询效率。

冷热分离

将3个月以上历史冷数据迁移至归档表/历史库,热表维持百万级数据量

保证高频读写的热表数据量可控,索引命中率最高、扫描行数最少,避免大表索引失效、查询卡顿。

📐 金字塔第二层:索引设计(最高效调优手段)

索引是SQL调优的核心武器,合理的索引设计可直接将查询耗时从数百毫秒降至微秒级,核心是精准命中、避免失效、杜绝回表。

索引原则

核心内容

反例/避坑要点

最左前缀匹配

联合索引(a,b,c),查询必须包含最左列a才能走完整索引;WHERE a=1 AND c=3 仅命中a列索引

跳过联合索引最左列,直接查询后续列,索引完全失效,触发全表扫描

覆盖索引

SELECT 查询字段全部包含在索引中,执行计划 Extra 显示 Using index,无需回表

禁止 SELECT *,只查询业务必需字段,避免大量回表I/O开销

高区分度前置

联合索引将区分度高的字段(user_id、order_id)放最左,低区分度字段(gender、status)后置

低区分度字段前置会导致索引过滤性极差,扫描行数过多,MySQL直接放弃索引走全表扫描

索引下推(ICP)

联合索引内字段可在索引层完成过滤,无需回表过滤,Extra 显示 Using index condition

MySQL自动生效,无需手动配置,核心是建立合理的联合索引结构

杜绝隐式转换

查询条件字段类型与入参类型严格一致

varchar类型字段传入数字、int字段传入字符串,均会触发索引失效

杜绝索引列函数操作

禁止对索引列使用函数、运算、模糊前置匹配

DATE(create_time)、like '%xxx' 会直接导致索引失效,必须改写为区间查询

✍️ 金字塔第三层:SQL 写法规范(规避低效写法)

规范的SQL写法可以引导优化器选择最优执行计划,避免人为造成的索引失效、临时表、文件排序等性能问题。

问题场景

低效写法

高效优化方案

核心原理

大偏移分页

LIMIT 1000000, 10

游标分页:WHERE id > last_id LIMIT 10延迟关联:JOIN 子查询仅查主键分页

规避MySQL遍历大量无效数据、频繁回表的开销,大幅减少扫描行数

多表JOIN

关联字段无索引、类型不一致、大表驱动小表

关联字段类型一致且双方建索引,严格遵循小表驱动大表

杜绝全表关联扫描、笛卡尔积,从根源减少数据扫描量

全字段查询

SELECT * FROM 表名

明确列出业务所需字段,配合覆盖索引

减少网络传输、内存占用,彻底消除回表查询开销

OR/IN 查询

大量OR拼接、大数量IN子查询

静态值用UNION ALL,大数据量用EXISTS替代IN

OR极易触发索引失效,大IN查询会生成临时表,拖慢查询

负向查询

NOT IN、!=、NOT LIKE

业务层面改写为区间查询(>、<、BETWEEN)

负向查询几乎不走索引,全表扫描概率极高,性能极差

EXISTS vs IN

大小表使用颠倒,导致扫描行数激增

外表小、内表大 → EXISTS;外表大、内表小 → IN

核心是控制外层扫描数据量,优先减少循环扫描次数

排序分组

ORDER BY/GROUP BY 字段无索引,触发文件排序

索引顺序与排序、分组字段顺序严格一致

利用索引有序特性,避免内存/磁盘额外排序,消除Using filesort

🔬 金字塔第四层:执行计划与故障排查(兜底验证)

通过执行计划和慢查询日志,精准定位慢SQL根因,验证前三层优化效果,是调优的核心排查手段。

13.4.1 EXPLAIN 核心关注三列

列名

核心关注点

优化结论

type

ALL(全表扫描)、index(全索引扫描)

严重性能告警,必须优化索引;最优级别为 ref、range

rows

预估扫描数据行数

扫描行数远大于返回行数,说明索引过滤性极差,需优化条件/索引

Extra

Using temporary(临时表)、Using filesort(文件排序)

性能杀手,必须重写SQL;Using index 为最优覆盖索引状态

13.4.2 慢查询日志核心配置

slow_query_log = ON                  # 开启慢查询日志
long_query_time = 1                  # 执行超过1秒的SQL记录为慢SQL
log_queries_not_using_indexes = ON   # 记录所有未走索引的SQL

13.4.3 紧急慢SQL处理

SHOW PROCESSLIST;   # 查看当前数据库所有线程
KILL 线程ID;         # 杀死阻塞、卡死的慢SQL线程

📊 终极慢SQL排查决策树

遇到慢SQL严格按照以下流程排查,杜绝盲目优化,精准定位问题:

① 先看 EXPLAIN 执行计划
    ├── Extra 有 Using temporary/filesort? → 重写SQL,消除排序、临时表
    ├── type = ALL / key = NULL? → 为WHERE、JOIN字段新增优化索引
    └── rows 预估行数极大? → 排查隐式转换、优化WHERE筛选条件

② 再对比扫描行数与返回行数
    ├── 扫描10万行、返回10行 → 索引过滤性差,调整联合索引顺序/优化SQL
    └── 扫描行数与返回行数接近 → SQL性能优秀,无需优化

③ 最后排查数据库整体压力
    ├── QPS极高 → 引入Redis缓存减轻DB压力
    ├── 锁等待严重 → 优化长事务、缩小锁范围、提升索引命中率
    └── 单表数据千万/亿级 → 冷热分离、分库分表架构优化

💡 SQL调优黄金核心法则

  1. 永远用 EXPLAIN 说话,拒绝直觉调优,所有优化必须基于执行计划验证。

  2. 调优本质=减少I/O开销:覆盖索引减回表、精准索引减扫描、优化SQL减临时表/排序。

  3. SQL优化到极限后,用架构破局:单表优化无效时,通过缓存、分表、冷热分离解决性能瓶颈。

笔记整理于 2026 年 7 月,涵盖MySQL架构、日志、事务、MVCC、锁机制、全套SQL调优体系

两块二每分钟