本文详细介绍了MySQL中的三种重要日志:binlog(用于主从复制和数据恢复)、redo log(事务持久性保障)和undo log(回滚和MVCC)。讲解了它们的区别、用途和工作原理,强调了在事务管理和数据一致性中的关键作用。
日志系统是保证数据安全、实现事务特性和数据恢复的核心组件。
目录概览:
Mysql中的三种log
什么是binlog
binlog是做什么的?
什么是redo log
binlog和redo log
大家有疑惑,binlog和redo log都是用来“恢复”的,他俩有什么区别呢?
目的?
数据内容?
写入时机?
大小和持久性?
什么是undo log
undo log的作用是什么?
redo log 重做日志(Mysql存储引擎InnoDB的事务日志)
undo log 回滚日志(Mysql存储引擎InnDB的事务日志)
binlog归档日志(数据库Server层二进制逻辑日志、和什么引擎无关)
MySQL中的三种log
最后写入 binlog 文件,redo事务状态变更为commit
什么是binlog
定义:binlog是二进制格式的日志文件,是MySQL Server层实现的日志,记录所有更改数据的SQL语句(逻辑日志)。
特点:
- 服务器层面,与存储引擎无关
- 追加写入,不会覆盖旧日志
- 主要用于复制和恢复
应用场景:
- 主从复制
- 数据恢复(基于时间点的恢复)
- 数据审计
binlog的三种格式:
STATEMENT:记录SQL语句原文
-
- 优点:日志量小
- 缺点:某些函数(如UUID())可能导致主从生成的数据不一致
ROW:记录每行数据的变更
-
- 优点:精确记录变更,主从一致
- 缺点:日志量大(如批量update)
MIXED:混合模式
一般情况下用STATEMENT,可能引起不一直时自动切换为ROW
进入正文!
binlog其实在日常的开发中听的次数很多,因为很多时候数据的更新就依赖着binlog。
举个简单的例子:
我们的数据是保存在数据库里边的,现在我们对某个商品的某个字段的内容进行了更改(数据库变更),而用户检索出来的数据是走搜索引擎的。为了让用户能够收到最新的数据,我们需要把引擎的数据也更新掉。
通俗来讲 —— 数据库的变更,搜索引擎的数据也要变更。
于是,我们就会监听binlog的变更,如果binlog有变更了,那我们就需要将变更写入到对应的数据源当中。
进一步讲讲,到底什么是binlog?
binlog记录了数据库表结构和表数据的变更,比如:Update、delete、insert、truncate、create...不会记录Select,因为Select并不会对表结构以及表的数据行发生更改行为。
binlog日志长什么样?
binlog我们可以简单理解为:存储这每条变更的SQL语句(表结构、表数据)
从下图中可以看到,binlog不止SQL,还有XID(事务ID)等信息
binlog日志是做什么的?
- MySQL在实际开发场景中往往都是(主从复制)一主多从结构的,从服务器需要与主服务器的数据保持一致,这就是通过binlog日志来实现的
- 数据库的数据被“干掉了”,我们可以通过binlog日志来对数据进行恢复。
因为binlog记录了数据库表的变更,所以我们可以用binlog进行复制(主从复制)和恢复数据。
什么是redo log
定义:Redo log是InnDB存储引擎的特有的日志,记录的是物理级别的“页”修改操作,用于保证事务的持久性。
特点:
- 循环写入,固定大小
- 记录的是:“在某个数据页上做了什么修改”
- 采用WAL(Write-Ahead Logging)技术
应用场景:
- 数据库崩溃恢复
- 实现事务的持久性
- 提高写入性能(随机写转为顺序写)
为什么使用WAL技术?
WAL(Write Ahead Logging)即预写式日志,其核心原则是:任何数据修改必须先写入日志,再写入磁盘。这样做的好处有:
- 减少磁盘I/O:将随机写转为顺序写,提高性能
- 保证数据安全:及时系统崩溃,也能通过redo log恢复
- 实现事务的持久性:确保已提交事务不会丢失
进入正文!
假设有这么一条SQL:
update user_table set name='张三' where id = '9'
MySQL执行这条SQL语句时,应该是先把 id=9 的这条数据查出来,再将name字段改掉。这个逻辑没问题吧?
但实际上MySQL的基本存储结构是“页”(记录都存在页里面),所以MySQL是先把这条记录所在的页找到,然后把该页加载到内存中,再将对应的数据进行修改。
现在就可能存在一个问题:如果在内存中把数据修改了,还没来得及落到磁盘,而此时的数据库挂了怎么办?显然...这次的更改将会丢失。
如果每个请求都需要将数据立马落到磁盘之上,那速度会很慢,MySQL可能会扛不住。所以MySQL是怎么做的呢?
MySQL引入了redo log,内存写完之后,会写一份redo log,这份redo log记载着这次在某个页上做了什么修改(操作)。
其实在写redo log的时候,也会有buffer,先是写buffer,再真正落到磁盘中的。至于说buffer什么时候落到磁盘中,有配置供我们配置,根据配置来决定。
写redo log也是需要写磁盘的,但它的好处就是顺序IO(顺序ID比随机IO速度更快)
所以,redo log存在的作用是:当我们在对数据进行修改时,写完内存后,但数据还没有真正写入到磁盘的时候,此时如果数据库异常,我们可以根据redo log来对数据进行恢复。因为redo log是顺序IO,写入的速度很快,并且redo log记录的是物理变化(xxx页做了xxx修改),文件的体积很小,恢复速度很快。
binlog与redo log的区别
binlog和redo log都是用来“恢复”的,他们的区别是什么呢?
主要区别:
- 目的:
- binlog:binlog主要用于恢复数据的修改操作,记录了对数据库进行的所有修改SQL语句,如:INSERT、UPDATE、DELETE、TRUNCATE等。它是用于数据备份、主从复制以及高可用性方案的关键组成部分。
- redo log:redo log用于数据库事务的持久性,记录了对数据库的逻辑变化。它主要用于确保在宕机或崩溃时,可以通过重做日志来重新执行已提交的事务,保证数据的一致性。
- 数据内容:
- binglog:binlog以文本形式记录SQL语句或SQL语句的逻辑表示。它包含了对数据库的增删改操作的详细信息,可以用来重做这些操作。
- redo log:redo log是一个循环的、预分配的固定长度的二进制文件,记录了对数据库页的物理修改操作。它包含了物理上的页地址、修改前后的数据值等,用于重做被修改的页。
- 写入时机:
- binlog:binlog是在事务提交之后才会被写入磁盘,它记录了已经完成的事务。
- redo log:redo log是在事务执行过程中被写入磁盘,这样可以确保事务在提交之前,对数据库的修改已经被记录下来。
- 大小和持久性:
- binglog:binlog的大小是由配置参数binlog_max_size控制的,它可以被删除或轮换。它是非持久性的,即在数据库重启后会被清空,需要依靠备份来进行数据恢复。
- redo log:redo log的大小是固定的,当redo log被写满时,会被清空 并重用空间。它是持久性的,即在数据库重启后会被保留,可以确保数据的一致性。
总体而言:
-
- binlog 主要用于记录已完成的操作,以实现数据备份、复制和恢复等功能。
- redo log 主要用于事务的持久性,以确保数据的一致性。它们在日志内容、写入时机和持久性等方面有所不用,但都是MySQL中重要的日志机制,为数据库的可靠性和可恢复性提供了支持。
什么是undo log
定义:undo log是InnDB存储引擎特有的日志,记录事务发生前的数据状态,用于事务回滚和MVCC实现。
特点:
- 逻辑日志,记录SQL执行前后的数据状态
- 存储在系统表空间的回滚段中
- 事务提交后不会立即删除,可能被MVCC使用
应用场景:
- 事务回滚
- 实现MVCC(多版本并发控制)
- 提保证事务的原子性
undo log是做什么的?
undo log的两个主要作用 —— 回滚和多版本控制(MVCC)
在数据修改的时候,不仅记录了redo log,还记录了undo log,如果因为某些原因导致事务失败或回滚了,可以用undo log进行回滚
undo log主要存储的也是逻辑日志,比如我们要insert一条数据了,那undo log会记录的一条对应的delete日志。我们要update一条记录时,它会记录一条对应相反的update记录。
因为undo log存储着修改之前的数据,相当于一个前版本,MVCC实现的是读写不阻塞,读的时候只要返回前一个版本的数据就行了。
redo log 重做日志(MySQL 存储引擎 InnoDB 的事务日志)
我们知道MySQL数据存在磁盘中,每次读写数据需做磁盘I/O,并发场景下性能较差。为此 MySQL 引入缓存 Buffer Pool 做优化。其包含磁盘中部分数据页(Page)的映射,来缓解数据库的磁盘压力。
当从数据库读取数据时,首先从缓存中读取,缓存中没有,则从磁盘读取后访日缓存。当向数据库写入数据时,先向缓存中写,此时缓存中的数据页数据会变更,该数据页叫脏页,Buffer Pool中修改完数据后会按照设定的策略再定期刷到磁盘中去,这个过程叫刷脏页。
那么问题来了,如果Buffer Pool中修改的数据还没有及时的刷到磁盘,MySQL宕机重启,就会导致数据丢失,无法保证事务的持久性,怎么办?
redo log解决了这个问题,就是说数据库在修改数据时,会把更新记录先写到redo log中,再去修改 Buffer Pool 中的数据,当提交事务时,调用fsynv把 redo log 输入磁盘。至于缓存中更新的数据文件何时刷入磁盘,则由后台线程异步处理。
注意:此时 redo log 的事务状态是prepare,还未真正提交成功,要等 binlog 日志写入磁盘完成后才会变为 commit,事务才算真正提交成功。
redo log 的写入方式?
redo log 采用大小固定,循环写入的方式,当写满后,会重新从头开始循环写,类似一个环状。这样设计的原因是 redo log 记录的是数据页上的修改,如果 Buffer Pool 中数据页已经刷到磁盘,这些记录就失效了,新日志会将这些失效的记录覆盖擦除。
注意:redo log 满了,在擦除之前,要确保这些要被擦除的记录都已经刷到磁盘中去了。在擦除旧记录释放新空间期间,不能再接收新的更新请求,此时 Mysql 性能会下降。因此高并发情况下,合理调整 redo log 的大小很重要。
crash-safe 能力是什么?
Innodb 引擎有 crash-safe 能力,即事务提交过程中的任何阶段,MySQL 宕机重启后都能保证事务的完整性,已提交的数据不会丢失。这种能力是通过 redo log 保证的,MySQL 宕机重启,系统将自动检查 redo log,将修改还未写入磁盘的数据从 redo log 恢复到 MySql中。
undo log 回滚日志(MySQL 存储引擎 InnoDB 的事务日志)
undo log 记录的是数据修改之前的状态,属于逻辑日志,起到回滚的作用,是保证事务原子性的关键。
举个例子:
假如更新 ID=1 记录的 name 字段, name 的原始数据为“张三”,先改 name 为“李四”事务执行 update user_table set name=李四 where ID=1 语句时,先在 undo log 中记录一条相反逻辑的 update user_table set name=张三 where id=1 的记录,这样当某些原因导致事务失败,就可以借助 undo log 将数据回滚到事务执行之前的状态。
那么问题来了:同一个事务的一条记录被多次修改,难道每次都要把数据修改前的状态写 undo log 吗?
不会,因为 undo log 只记录事务开始前数据的原始版本,当再次对这样数据修改时,产生的修改记录会写到redo log。undo log 负责回滚,redo log 负责前滚。
啥是回滚和前滚?
(1)回滚
未提交的事务,即事务未执行 commit。但事务内修改的脏页中,有一部分已刷盘。此时数据库宕机重启,需要回滚来将先前那部分已经刷盘的脏块从磁盘上撤销。
(2)前滚
未完全提交的事务,即事务已经执行 commit,但该事务内修改的脏页中只有一部分数据被刷盘,另一部分还在 buffer pool,此时数据库宕机重启,就要用前滚来将未来得及刷盘的数据从 redo log 中恢复出来并刷盘。
bin log 归档日志(数据库 Server 层二进制逻辑日志、和什么引擎无关)
bin log 记录了用户对数据库所有 sql 操作(不包含查询语句,因为这类操作对数据本身没有修改)。之所以可以称为归档日志,是因为它不会像 redo log 那样循环擦除之前的记录,而是会一直记录日志。一个 bin log 文件默认最大容量1G(可通过 max_binlog_size 参数修改),单个日志超过最大值则会新创建一个文件继续写。
注意:日志可能是基于事务来记录的,而事务不应该跨文件记录,如果 binlog 日志文件达到了最大值但刚好事务还没有提交,此时则不会创建新文件记录,而是继续增大日志。因此 max_binlog_size 的值和实际的 binlog 文件大小不一定相等。
经过上述介绍,binlog 主要用就是主从同步和数据库基于时间点的还原。
redo log 与 bin log 的区别?
什么是 redo log 两阶段提交,为什么要这么做?
更新内存后引擎层写 redo log 将状态改成 prepare 为提交第一阶段,Server 层写 bin log,将状态改成 commit 为提交第二阶段。 两阶段提交目的是确保 binl og 和 redo log 数据一致性。
如果不是两阶段提交可能会出现什么情况?
1)假设先写 redo log 再写 bin log,即 redo log 没有 prepare 阶段,写完直接置为commit,然后再写 bin log。如果写完 redo log 后还没写完 bin log 数据库宕机了,重启后系统自动用 redo log 恢复,此时会造成磁盘上数据页数据比 bin log 上的记录数据多,数据不一致。
2)假设先写 bin log 再写 redo log,如果写完 bin log 没写完 redo log 数据库宕机了,那么 bin log 上的记录就会比磁盘上数据页的记录多一些,下次用 bin log 恢复数据,恢复后的数据和原来的数据不一致。
描述一下 redo log 容灾恢复过程?
如果 redo log 是完整(commit 状态)的,直接用 redo log 恢复;
如果 redo log 是预提交 prepare 但不是 commit 状态,此时要去判断 binlog 是否完整,如果完整(commit)那就提交 redo log,再用 redo log 恢复,不完整就回滚事务。
执行一条SQL的过程
- 连接器:建立连接,验证权限
- 查询缓存:检查是否有缓存(MySQL 8.0已移除)
- 分析器:词法分析、语法分析
- 优化器:生成执行计划,选择索引
- 执行器:调用存储引擎接口执行
- 存储引擎:
-
- 读操作:从内存或磁盘读取数据
- 写操作:
- 记录undo log(用于回滚)
- 更新内存数据
- 记录redo log(prepare状态)
- 记录binlog
- 提交事务,redo log改为commit状态
执行计划解析
执行计划(EXPLAIN)是优化SQL的重要工具,关键字段含义:
- id:查询的序列号,表示执行顺序
- select_type:查询类型(SIMPLE, PRIMARY, SUBQUERY等)
- table:访问的表
- type:访问类型(从好到差:system > const > eq_ref > ref > range > index > ALL)
- possible_keys:可能使用的索引
- key:实际使用的索引
- key_len:使用的索引长度
- ref:索引的哪一列被使用
- rows:预估需要读取的行数
- Extra:额外信息(Using index, Using temporary, Using filesort等)
慢SQL优化策略
- 分析执行计划:使用EXPLAIN找出性能瓶颈
- 优化索引:
- 确保查询使用合适的索引
- 避免索引失效(如函数转换、隐式类型转换)
- 考虑复合索引的最左前缀原则
- 重写SQL:
- 简化复杂查询
- 避免SELECT *
- 使用JOIN替代子查询(视情况而定)
- 数据库设计优化:
- 合理的表结构设计
- 适当的数据类型选择
- 考虑分库分表(数据量大时)
- 参数调优:
- 调整缓冲区大小
- 优化排序参数等