7.Innodb底层原理与Mysql日志机制深入剖析

Innodb底层原理与Mysql日志机制深入剖析

    • [📋 知识体系总览](#📋 知识体系总览)
    • 一、MySQL内部组件结构
    • [二、redo log重做日志](#二、redo log重做日志)
      • [✅1. 四大关键参数](#✅1. 四大关键参数)
      • [✅2. redo log 写入磁盘过程分析](#✅2. redo log 写入磁盘过程分析)
      • [✅3. innodb_flush_log_at_trx_commit 写入策略](#✅3. innodb_flush_log_at_trx_commit 写入策略)
    • 三、binlog二进制归档日志
      • [✅1. binlog配置与启用](#✅1. binlog配置与启用)
      • [✅2. 三种日志格式](#✅2. 三种日志格式)
      • [✅3. sync_binlog写入磁盘机制](#✅3. sync_binlog写入磁盘机制)
      • [✅4. binlog文件管理](#✅4. binlog文件管理)
      • [✅5. binlog数据恢复实战](#✅5. binlog数据恢复实战)
      • [✅6. 为什么需要两份日志?](#✅6. 为什么需要两份日志?)
      • [📊 redo log vs binlog 全量对比](#📊 redo log vs binlog 全量对比)
    • [四、undo log回滚日志](#四、undo log回滚日志)
      • [✅1. undo log管理机制](#✅1. undo log管理机制)
      • [✅2. undo log删除时机](#✅2. undo log删除时机)
      • [✅3. 为什么不直接更新磁盘?](#✅3. 为什么不直接更新磁盘?)
    • 五、其他日志
      • [✅1. 错误日志](#✅1. 错误日志)
      • [✅2. 通用查询日志](#✅2. 通用查询日志)
    • [📋 全文总结](#📋 全文总结)
      • [✅1. Server层五大组件](#✅1. Server层五大组件)
      • [✅2. redo log三策略](#✅2. redo log三策略)
      • [✅3. binlog三格式](#✅3. binlog三格式)
      • [✅4. 两份日志的分工](#✅4. 两份日志的分工)
      • [✅5. undo log双重作用](#✅5. undo log双重作用)
      • [✅6. binlog数据恢复](#✅6. binlog数据恢复)
      • [✅7. MySQL日志体系速记](#✅7. MySQL日志体系速记)

📋 知识体系总览

复制代码
Innodb底层原理与Mysql日志机制
├── 一、MySQL内部组件结构
│   ├── ✅1. Server层与存储引擎层架构
│   └── ✅2. 各组件详解(连接器→查询缓存→分析器→优化器→执行器)
├── 二、redo log重做日志
│   ├── ✅1. 四大关键参数
│   ├── ✅2. 写入磁盘过程(write pos / checkpoint)
│   ├── ✅3. innodb_flush_log_at_trx_commit写入策略
│   └── 📊 三种策略对比表
├── 三、binlog二进制归档日志
│   ├── ✅1. binlog配置与启用
│   ├── ✅2. 三种日志格式(STATEMENT/ROW/MIXED)
│   ├── ✅3. sync_binlog写入磁盘机制
│   ├── ✅4. binlog文件管理(生成/删除/查看)
│   └── ✅5. binlog数据恢复实战
├── 四、undo log回滚日志
│   ├── ✅1. undo log管理机制
│   └── ✅2. undo log删除时机
├── 五、其他日志
│   ├── ✅1. 错误日志
│   └── ✅2. 通用查询日志
└── 📋 全文总结

一、MySQL内部组件结构

✅1. Server层与存储引擎层架构

大体来说,MySQL 可以分为 Server 层和存储引擎层两部分。

Server层:主要包括连接器、查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。

存储引擎层:负责数据的存储和提取。其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多个存储引擎。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始成为了默认存储引擎。

📝 核心: MySQL采用插件式存储引擎架构,Server层负责SQL解析和执行计划,存储引擎层负责实际数据存取。这也是为什么MySQL能同时支持多种存储引擎的根本原因。

✅2. 各组件详解

连接器

负责跟客户端建立连接、获取权限、维持和管理连接:

bash 复制代码
[root@192 ~]# mysql -h host[数据库地址] -u root[用户] -p root[密码] -P 3306

📝 关键理解: 权限在连接时读取并缓存,连接期间即使管理员修改权限也不影响已有连接,只有新建连接才会使用新权限。

查询缓存

MySQL 拿到查询请求后,先到查询缓存查 key-value(key=查询语句,value=查询结果)。命中则直接返回。

📝 重要: 查询缓存弊大于利------任何表更新都会清空该表所有缓存。对于更新压力大的数据库,命中率极低。MySQL 8.0已移除查询缓存功能。

因为查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。因此很可能你费劲地把结果存起来,还没使用呢,就被一个更新全清空了。对于更新压力大的数据库来说,查询缓存的命中率会非常低。

sql 复制代码
-- 查看是否开启缓存
show global variables like "%query_cache_type%";
-- 按需使用(仅对特定SQL开启缓存)
select SQL_CACHE * from test where ID=5;
分析器
  • 先做词法分析(识别SQL中的关键字、表名、列名)
  • 再做语法分析(判断SQL是否满足MySQL语法),最终生成语法树。

例如:如果你的语句不对,就会收到"You have an error in your SQL syntax"的错误提醒,比如下面这个语句 from 写成了 "rom"。

sql 复制代码
mysql> select * fro test where id=1;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'fro test where id=1' at line 1
优化器

经过了分析器,MySQL 就知道你要做什么了。在开始执行之前,还要先经过优化器的处理。

  • 优化器是在表里面有多个索引的时候,决定使用哪个索引;
  • 或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序;
  • 以及一些mysql自己内部的优化机制。
执行器

开始执行的时候,要先判断一下你对这个表 T 有没有执行查询的权限,如果没有,就会返回没有权限的错误,如下所示 (在工程实现上,如果命中查询缓存,会在查询缓存返回结果的时候,做权限验证)。

sql 复制代码
mysql> select * from test where id=10;

如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。

二、redo log重做日志

📝 核心: redo log是InnoDB特有的日志,用于实现crash-safe能力------即使数据库异常重启,已提交的事务数据也不会丢失。

✅1. 四大关键参数

参数 说明 默认值
innodb_log_buffer_size redo log buffer大小 默认16M(最大4096M,最小1M)
innodb_log_group_home_dir redo log文件存储位置 ./(数据文件目录)
innodb_log_files_in_group redo log文件个数 2(最大100)
innodb_log_file_size 单个redo log文件大小 48M
sql 复制代码
show variables like '%innodb_log_buffer_size%'; -- redo log buffer大小参数
show variables like '%innodb_log_group_home_dir%'; -- redo log文件存储位置参数
show variables like '%innodb_log_files_in_group%'; -- redo log文件的个数
show variables like '%innodb_log_file_size%'; -- 单个redo log文件大小

如下时redo log文件存储形式:

✅2. redo log 写入磁盘过程分析

redo log 从头开始写,写完一个文件继续写另一个文件,写到最后一个文件末尾就又回到第一个文件开头循环写。

  • write pos:当前记录位置,一边写一边后移
  • checkpoint:当前要擦除的位置,擦除前要先把记录更新到数据文件
  • write pos 和 checkpoint 之间的部分就是空着的可写部分,可以用来记录新的操作。如果 write pos 追上checkpoint,表示redo log写满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把 checkpoint 推进一下。

✅3. innodb_flush_log_at_trx_commit 写入策略

这个参数控制 redo log 的写入策略,有三种可能取值:

  • 设置为0: 每次事务提交时只把 redo log 留在 redo log buffer,数据库宕机可能丢失数据
  • 设置为1(默认值): 每次事务提交时将 redo log 直接持久化到磁盘,数据最安全,线上推荐
  • 设置为2: 每次事务提交时只把 redo log 写到 OS 的 page cache,数据库宕机不丢失,OS宕机丢失

InnoDB 有一个后台线程,每隔 1 秒,把 redo log buffer 中的日志调用 write 写到 page cache,然后调用 fsync 持久化到磁盘。

sql 复制代码
show variables like 'innodb_flush_log_at_trx_commit';
set global innodb_flush_log_at_trx_commit=1;

三、binlog二进制归档日志

📝 核心: binlog是Server层的归档日志,记录所有修改操作,用于数据恢复和主从复制。MySQL 5.7默认关闭,8.0默认打开

✅1. binlog配置与启用

启动binlog记录功能,会影响服务器性能,但如果需要恢复数据或主从复制功能,则好处则大于对服务器的影响。

sql 复制代码
# 查看binlog相关参数
show variables like '%log_bin%';

打开binlog功能,需要修改配置文件my.ini(windows)或my.cnf(linux),然后重启数据库。

在配置文件中的mysqld部分增加如下配置:

ini 复制代码
# my.cnf 配置
log-bin=mysql-binlog           # binlog存放位置
server-id=1                    # 服务器唯一ID,集群环境中每台mysql服务器的id不能一样,不加启动会报错
binlog_format = row            # 日志格式
expire_logs_days = 15          # 自动删除15天前的binlog
max_binlog_size = 200M         # 单个文件大小限制,默认为 1GB

重启数据库后我们再去看data数据目录会多出两个文件,第一个就是binlog日志文件,第二个是binlog文件的索引文件,这个文件管理了所有的binlog文件的目录。

sql 复制代码
show binary logs;              -- 查看binlog文件列表,看有多少binlog文件
show variables like '%log_bin%';

关键字段说明:

  • log_bin: binlog日志是否打开状态
  • log_bin_basename: 是binlog日志的基本文件名,后面会追加标识来表示每一个文件,binlog日志文件会滚动增加
  • log_bin_index: 指定的是binlog文件的索引文件,这个文件管理了所有的binlog文件的目录。
  • sql_log_bin: 控制当前session的SQL是否写入binlog文件,如果想在主库上执行一些操作,但不复制到slave库上,可以通过修改参数sql_log_bin来实现。比如说,模拟主从同步复制异常。

✅2. 三种日志格式

格式 记录方式 优点 缺点
STATEMENT 记录SQL语句 日志量小,IO开销低 函数如UUID()在slave执行结果不一致
ROW 记录每行修改的数据 解决函数、存储过程复制问题 日志量大,性能较差
MIXED 两者结合,自动选择 平衡性能与一致性 ---

📝 面试要点: 推荐使用MIXED格式,MySQL会根据SQL是否包含不确定性函数自动在STATEMENT和ROW之间选择。

✅3. sync_binlog写入磁盘机制

binlog写入磁盘机制主要通过 sync_binlog 参数控制,默认值是 0。

  • 为0的时候,表示每次提交事务都只 write 到page cache,由系统自行判断什么时候执行 fsync 写入磁盘。虽然性能得到提升,但是机器宕机,page cache里面的 binlog 会丢失。
  • 也可以设置为1,表示每次提交事务都会执行 fsync 写入磁盘,这种方式最安全。
  • 还有一种折中方式,可以设置为N(N>1),表示每次提交事务都write 到page cache,但累积N个事务后才 fsync 写入磁盘,这种如果机器宕机会丢失N个事务的binlog。

注意:

发生以下任何事件时, binlog日志文件会重新生成:

  • 服务器启动或重新启动
  • 服务器刷新日志,执行命令flush logs
  • 日志文件大小达到 max_binlog_size 值,默认值为 1GB

✅4. binlog文件管理

重新生成条件:

  • 服务器重启
  • 执行flush logs
  • 文件大小达到max_binlog_size

删除binlog文件

sql 复制代码
-- 删除binlog文件
reset master;      -- 删除所有
purge master logs to 'mysql-binlog.000006';            -- 删除指定文件之前
purge master logs before '2023-01-21 14:00:00';        -- 删除指定日期前

查看binlog内容:

可以用mysql自带的命令工具 mysqlbinlog 查看binlog日志内容

bash 复制代码
# 查看bin-log二进制文件(命令行方式,不用登录mysql)
mysqlbinlog --no-defaults -v --base64-output=decode-rows D:/dev/mysql-5.7.25-winx64/data/mysql-binlog.000007 

# 查看bin-log二进制文件(带查询条件)
mysqlbinlog --no-defaults -v --base64-output=decode-rows D:/dev/mysql-5.7.25-winx64/data/mysql-binlog.000007 start-datetime="2023-01-21 00:00:00" stop-datetime="2023-02-01 00:00:00" start-position="5000" stop-position="20000"

查出来的binlog日志文件内容如下:

sql 复制代码
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#230127 21:13:51 server id 1  end_log_pos 123 CRC32 0x084f390f  Start: binlog v 4, server v 5.7.25-log created 230127 21:13:51 at startup
# Warning: this binlog is either in use or was not closed properly.
ROLLBACK/*!*/;
# at 123
#230127 21:13:51 server id 1  end_log_pos 154 CRC32 0x672ba207  Previous-GTIDs
# [empty]
# at 154
#230127 21:22:48 server id 1  end_log_pos 219 CRC32 0x8349d010  Anonymous_GTID  last_committed=0        sequence_number=1       rbr_only=yes
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 219
#230127 21:22:48 server id 1  end_log_pos 291 CRC32 0xbf49de02  Query   thread_id=3     exec_time=0     error_code=0
SET TIMESTAMP=1674825768/*!*/;
SET @@session.pseudo_thread_id=3/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1/*!*/;
SET @@session.sql_mode=1342177280/*!*/;
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
/*!\C utf8 *//*!*/;
SET @@session.character_set_client=33,@@session.collation_connection=33,@@session.collation_server=33/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
BEGIN
/*!*/;
# at 291
#230127 21:22:48 server id 1  end_log_pos 345 CRC32 0xc4ab653e  Table_map: `test`.`account` mapped to number 99
# at 345
#230127 21:22:48 server id 1  end_log_pos 413 CRC32 0x54a124bd  Update_rows: table id 99 flags: STMT_END_F
### UPDATE `test`.`account`
### WHERE
###   @1=1
###   @2='lilei'
###   @3=1000
### SET
###   @1=1
###   @2='lilei'
###   @3=2000
# at 413
#230127 21:22:48 server id 1  end_log_pos 444 CRC32 0x23355595  Xid = 10
COMMIT/*!*/;
# at 444
。。。

能看到里面有具体执行的修改伪sql语句以及执行时的相关情况。

✅5. binlog数据恢复实战

核心思路:回放执行binlog中记录的SQL。

用binlog日志文件恢复数据其实就是回放执行之前记录在binlog文件里的sql,举一个数据恢复的例子

sql 复制代码
-- 1. 刷新日志,生成新的binlog文件
flush logs;

-- 2. 执行操作(插入数据)
INSERT INTO `test`.`account` (`id`, `name`, `balance`) VALUES ('4', 'zhuge', '666');
INSERT INTO `test`.`account` (`id`, `name`, `balance`) VALUES ('5', 'zhuge1', '888');

-- 3. 误操作删除
delete from account where id > 3;

恢复数据------找到插入SQL的BEGIN前面的at位置和COMMIT后面的at位置:

bash 复制代码
# 按位置恢复
mysqlbinlog --no-defaults --start-position=219 --stop-position=701 --database=test \
  D:/dev/mysql-5.7.25-winx64/data/mysql-binlog.000009 | mysql -uroot -p123456 -v test

# 按时间恢复
mysqlbinlog --no-defaults --start-datetime="2023-1-27 23:32:24" --stop-datetime="2023-1-27 23:34:23" \
  --database=test D:/dev/mysql-5.7.25-winx64/data/mysql-binlog.000009 | mysql -uroot -p123456 -v test

📝 重要: 要恢复大量数据,需 全量备份 + binlog增量恢复 组合使用。仅靠binlog不现实,因为早期binlog会被定期删除。

比如程序员经常说的删库跑路的话题,假设我们把数据库所有数据都删除了要怎么恢复了,如果数据库之前没有备份,所有的binlog日志都在的话,就从binlog第一个文件开始逐个恢复每个binlog文件里的数据,这种一般不太可能,因为binlog日志比较大,早期的binlog文件会定期删除的,所以一般不可能用binlog文件恢复整个数据库的。

一般我们推荐的是每天(在凌晨后)需要做一次全量数据库备份,那么恢复数据库可以用最近的一次全量备份再加上备份时间点之后的binlog来恢复数据。

备份数据库一般可以用mysqldump 命令工具

bash 复制代码
# 备份
mysqldump -u root -p 数据库名>备份文件名;        # 备份整个数据库
mysqldump -u root -p 数据库名 表名字>备份文件名;  # 备份单个表

# 恢复
mysql -u root -p test < 备份文件名

✅6. 为什么需要两份日志?

最开始 MySQL 并没有 InnoDB 引擎,MySQL 自带的 MyISAM 引擎没有 crash-safe 能力,binlog 只能用于归档。而InnoDB 是另一个公司以插件形式引入Mysql后,为了 crash-safe 能力,设计了 redo log。

保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe

📊 redo log vs binlog 全量对比

特性 redo log binlog
所属层 InnoDB引擎层 Server层
记录内容 物理日志:数据页修改 逻辑日志:SQL原始逻辑
写入方式 循环写(空间固定) 追加写(空间无限增长)
主要作用 crash-safe 数据恢复、主从复制
适用引擎 仅InnoDB 所有存储引擎
文件管理 固定大小,循环覆盖 可配置自动删除策略

四、undo log回滚日志

📝 核心: undo log记录数据修改前的状态,用于事务回滚MVCC。它保证了事务的原子性和一致性读。

✅1. undo log管理机制

InnoDB 采用**回滚段(rollback segment)**方式管理 undo log,每个回滚段记录了 1024 个 undo log segment ,每个事务只会使用一个undo log segment。

在MySQL5.5的时候,只有一个回滚段,那么最大同时支持的事务数量为1024个。在MySQL 5.6开始,InnoDB支持最大128个回滚段,故其支持同时在线的事务限制提高到了 128*1024 。

版本 回滚段数 最大并发事务数
MySQL 5.5 1 1024
MySQL 5.6+ 128 128 × 1024

关键参数:

  • innodb_undo_directory: 设置undo log文件路径,默认./
  • innodb_undo_logs: 设置undo log文件内部回滚段的个数,默认值为128。
  • innodb_undo_tablespaces: 设置undo log文件的数量,这样回滚段可以较为平均地分布在多个文件中。设置该参数后,会在路径innodb_undo_directory看到undo为前缀的文件。

✅2. undo log删除时机

  • 新增类型的,在事务提交之后就可以清除掉了。

  • 修改类型的,事务提交之后不能立即清除掉,这些日志会用于mvcc。只有当没有事务用到该版本信息时才可以清除。

✅3. 为什么不直接更新磁盘?

为什么Mysql不能直接更新磁盘上的数据而设置这么一套复杂的机制来执行SQL了?

  • 因为来一个请求就直接对磁盘文件进行随机读写,然后更新磁盘文件里的数据性能可能相当差。
  • 因为磁盘随机读写的性能是非常差的,所以直接更新磁盘文件是不能让数据库抗住很高并发的。
  • Mysql这套机制看起来复杂,但它可以保证每个更新请求都是更新内存BufferPool ,然后顺序写日志文件,同时还能保证各种异常情况下的数据一致性。
  • 更新内存的性能是极高的,然后顺序写磁盘上的日志文件的性能也是非常高的,要远高于随机读写磁盘文件。
  • 正是通过这套机制,才能让我们的MySQL数据库在较高配置的机器上每秒可以抗下几干甚至上万的读写请求。

五、其他日志

✅1. 错误日志

记录数据库启动/停止及运行过程中的严重错误时的相关信息。默认开启,无法关闭

当数据库出现任何故障导致无法正常使用时,建议首先查看此日志。

sql 复制代码
# 查看错误日志存放位置
show variables like '%log_error%';

✅2. 通用查询日志

通用查询日志记录用户的所有操作 ,包括启动和关闭MySQL服务、所有用户的连接开始时间和截止时间、发给 MySQL 数据库服务器的所有 SQL 指令等,如select、show等,无论SQL的语法正确还是错误、也无论SQL执行成功还是失败,MySQL都会将其记录下来。默认关闭,仅在调试时开启。

通用查询日志用来还原操作时的具体场景,可以帮助我们准确定位一些疑难问题,比如重复支付等问题。

  • general_log:是否开启日志参数,默认为OFF,处于关闭状态,因为开启会消耗系统资源并且占用磁盘空间。一般不建议开启,只在需要调试查询问题时开启。
  • general_log_file:通用查询日志记录的位置参数。
sql 复制代码
show variables like '%general_log%';
SET GLOBAL general_log=on;   -- 开启通用查询日志

📋 全文总结

✅1. Server层五大组件

连接器(权限) → 查询缓存(8.0已移除) → 分析器(词法+语法) → 优化器(选索引/定顺序) → 执行器(调引擎接口)

✅2. redo log三策略

innodb_flush_log_at_trx_commit:0(最快但不安全) / 1(默认,最安全) / 2(折中)

✅3. binlog三格式

STATEMENT(日志小但函数不一致) / ROW(安全但日志大) / MIXED(推荐,自动选择)

✅4. 两份日志的分工

redo log = crash-safe(引擎层,循环写),binlog = 数据恢复+主从复制(Server层,追加写)

✅5. undo log双重作用

事务回滚 + MVCC多版本并发控制

✅6. binlog数据恢复

mysqlbinlog --start-position/--start-datetime 回放指定范围日志,配合全量备份使用

✅7. MySQL日志体系速记

日志 作用 开启
redo log crash-safe 默认
binlog 恢复/复制 8.0默认
undo log 回滚/MVCC 默认
错误日志 诊断故障 默认
通用查询日志 审计排查 按需