文章目录
- 一、前言
- 1.1 MySQL体系结构
- 1.2 MySQL日志分类
- 1.3 其他几种日志
- 1.3.1 查询日志
- 1.3.2 慢查询日志
- 1.3.3 错误日志
- 二、bin log 二进制日志
- 2.1 bin log简介
- 2.2 binlog日志格式
- 2.3 日志删除
- 2.4 写入/刷盘机制
- 三、undo log 回滚日志
- 3.1 undo log简介
- 3.2 隐藏字段 —— 事务ID(TRX_ID)、ROLL_PTR
- 3.3 版本链
- 四、redo log 重做日志
- 4.1 redo log详解
- 4.2 redo log的写入过程、刷盘时机
- 4.3 redo log file 的结构
- 4.4 什么是 crash-save
- 4.5 redo log细节
- 五、补充
- 5.1 binlog与redolog对比、逻辑日志与物理日志
- 5.2 update语句的执行流程
- 5.3 两阶段提交
- 5.4 总结、使用场景
- binlog的应用场景
- undolog的使用场景
- redolog的使用场景
MySQL作为最流行的开源数据库,其重要性不言而喻。日志是mysql数据库的重要组成部分,记录着数据库运行期间各种状态信息。常见的日志有以下几种:
作为开发,我们重点需要关注的是 二进制日志bin log(归档日志)、 事务日志redo log(重做日志) 和 undolog(回滚日志),本文接下来会详细介绍这三种日志。
- 二进制binlog(归档日志):是Server层生成的日志,主要用于数据备份和主从复制;
- undolog(回滚日志):是Innodb存储引擎层生成的日志,实现了事务中的原子性,主要用于事务回滚和MVCC;
- 事务日志redolog(重做日志):是Innodb存储引擎层生成的日志,实现了事务中的持久性,主要用于掉电等故障恢复。
下面就带着这个问题,看看这三种日志是怎么工作的。
一、前言
1.1 MySQL体系结构
1)连接层
最上层是一些客户端和链接服务,包含本地sock 通信和大多数基于客户端/服务端工具实现的类似于TCP/IP的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
2)服务层
第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化,部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程、函数等。在该层,服务器会解析查询并创建相应的内部解析树,并对其完成相应的优化如确定表的查询的顺序,是否利用索引等,最后生成相应的执行操作。如果是select语句,服务器还会查询内部的缓存,如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。
3)引擎层
存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API和存储引擎进行通信。不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选取合适的存储引擎。数据库中的索引是在存储引擎层实现的。
4)存储层
数据存储层,主要是将数据(如: redolog、undolog、数据、索引、二进制日志、错误日志、查询日志、慢查询日志等)存储在文件系统之上,并完成与存储引擎的交互。
和其他数据库相比,MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎上,插件式的存储引擎架构,将查询处理和其他的系统任务以及数据的存储提取分离。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。
其中slowlog、binlog、errorlog、relaylog归属于MySQL服务层;undolog、redolog归属于引擎层,为innodb所特有。
1.2 MySQL日志分类
MySQL有不同类型的日志:慢查询日志、通用查询日志、错误日志、事务日志、二进制日志等几大类,在MySQL8之后又新增了两种日志——中继日志、数据定义语句日志。其中比较重要的是二进制日志binlog (归档日志) 、事务日志redo log(重做日志) 和 undo log(回滚日志)。
MySQL日志主要包括八种
- 慢查询日志(slow query log):记录所有执行时间超过long_query_time的所有查询,方便对查询进行优化
- 通用查询日志(general log):记录索引连接的起始时间和终止时间,以及连接发送给数据库服务的所有指令,对复原操作的实际场景、发现问题、数据库操作的审计都有帮助
- 二进制日志(bin log):记录所有更改数据的语句,用于主从服务器之间的数据同步、服务器遇到故障时数据的无损恢复
- 错误日志(error log):记录MySQL服务的启动、运行或停止MySQL服务时出现的问题,方便了解服务器的状态,从而对服务器进行维护
- 中继日志(relay log):用于主从服务器架构,从服务器用来存放主服务器二进制日志内容的一个中间件文件。从服务器通过读取中继日志的内容,来同步主服务器上的操作
- 回滚日志(undo log):是Innodb存储引擎层生成的日志,实现了事务中的原子性,主要用于事务回滚和MVCC
- 重做日志(redo log):是Innodb存储引擎层生成的日志,实现了事务中的持久性,主要用于掉电等故障恢复
- 数据定义语句日志:记录数据定义语句执行的元数据操作
除了二进制日志外,其他日志均为文本文件。默认情况下,所有日志均创建于MySQL数据目录中
1.3 其他几种日志
1.3.1 查询日志
查询日志中记录了客户端的所有操作语句(包括所有的增删改查、DDL、DML、DQL语句),而二进制日志不包含查询数据的SQL语句。默认情况下,查询日志是未开启的。如果需要开启查询日志,可以设置以下配置:
如果想要禁用查询日志,可将general_log
设置为0,而后重启MySQL服务sudo systemctl restart mysql
1.3.2 慢查询日志
慢查询日志记录了所有执行时间超过参数 long_query_time 设置值并且扫描记录数不小于 min_examined_row_limit 的所有SQL语句的日志,默认未开启。long_query_time 默认为10秒,最小为0,精度可以到微秒。如果要开启慢查询日志,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息:
# 开启MySQL慢日志查询开关
slow_query_log=1
# 执行时间参数,设置慢日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志
long_query_time=2
同理,如果想要禁用慢查询日志,可将slow_query_log
设置为0,而后重启MySQL服务sudo systemctl restart mysql
1.3.3 错误日志
错误日志是MySQL中最重要的日志之一,它记录了当mysqld启动和停止时,以及服务器在运行过程中发生任何严重错误时的相关信息。当数据库出现任何故障导致无法正常使用时,建议首先查看此日志。
该日志是默认开启的,默认存放目录/var/log/,默认的日志文件名为mysqld.log。查看日志位置:
-- 登录mysql,查看系统变量
show variables like '%log_error';
二、bin log 二进制日志
2.1 bin log简介
二进制日志(BINLOG)记录了所有的DDL(数据定义语言,创建库、表)语句和DML(数据操纵语言,增删改)语句,但不包括数据查询(SELECT、SHOW)语句。
作用:1.灾难时的数据恢复;2.MySQL的主从复制。
- binlog用于记录数据库执行的DDL、DML操作信息(不包括查询),以二进制的形式保存在磁盘中。binlog是mysql的逻辑日志,并且由Server层进行记录,使用任何存储引擎的mysql数据库都会记录binlog日志。
- binlog是通过追加的方式进行写入的,可以通过max_binlog_size参数设置每个binlog文件的大小,当文件大小达到给定值之后,会生成新的文件来保存日志。
在MySQL8版本中,默认二进制日志是开启着的,涉及到的参数如下:
show variables like '%log_bin';
常用的5.7版本可能只有以下参数:
mysql> SELECT VERSION();
+-----------+
| VERSION() |
+-----------+
| 5.7.35 |
+-----------+
1 row in set (0.00 sec)
mysql> show variables like '%log_bin';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin | OFF |
| sql_log_bin | ON |
+---------------+-------+
2 rows in set (0.00 sec)
2.2 binlog日志格式
binlog日志有三种格式:分别为STATMENT、ROW和MIXED,具体格式及特点如下:
- STATMENT:基于SQL语句的复制,每一条会修改数据的sql语句会记录到binlog中。
- 优点:不需要记录每一行的变化,减少了binlog日志量,节约了IO,从而提高了性能;
- 缺点:在某些情况下会导致主从数据不一致,比如执行sysdate()、sleep()等。
- ROW:基于行的复制,不记录每条sql语句的上下文信息,仅需记录哪条数据被修改了。
- 优点:不会出现某些特定情况下的存储过程、或function、或trigger的调用和触发无法被正确复制的问题;
- 缺点:会产生大量的日志,尤其是alter table的时候会让日志暴涨
- MIXED:基于 STATMENT 和 ROW 两种模式的混合复制,一般的复制使用STATEMENT模式保存binlog,对于STATEMENT模式无法复制的操作使用ROW模式保存binlog
日志格式 | 含义 |
---|---|
STATEMENT | 基于SQL语句的日志记录,记录的是SQL语句,对数据进行修改的SQL都会记录在日志文件中 |
ROW | 默认格式,基于行的日志记录,记录是每一行的数据变更 |
MIXED | 混合了STATEMENT和ROW两种格式,默认采用STATEMENT,在某些特殊情况下会自动切换为ROW进行记录 |
show variables like '%binlog_format%';
-- 如果想修改二进制日志格式
-- 1.vim /etc/my.cnf
-- 2.往文件内添加 binlog_format=STATEMENT
-- 3.systemctl restart mysqld
具体日志格式详解
由于日志是以二进制方式存储的,不能直接读取,需要通过二进制日志查询工具mysqlbinlog来查看,具体语法:
mysqlbinlog [options] log-files
参数选项:
-d, --database=name 指定数据库名称,只列出指定的数据库相关操作
-o, --offset=# 忽略掉日志中的前n行命令
-v, --verbose 将行事件(数据变更)重构为SQL语句
-vv 将行事件(数据变更)重构为SQL语句,并输出注释信息
mysqlbinlog -v binlog.00002
mysql中有score记录成绩,执行 update score set math = math + 1;
ROW格式下,执行mysqlbinlog -v binlog.00002
,得到如下内容
STATEMENT格式下,执行mysqlbinlog binlog.00003
,得到如下内容
2.3 日志删除
对于比较繁忙的业务系统,每天生成的binlog数据巨大,如果长时间不清除,将会占用大量磁盘空间。可以通过以下几种方式清理日志:
MySQL指令 | 含义 |
---|---|
reset master; | 删除全部binlog日志,删除之后,日志编号将从binlog.000001重新开始 |
purge master logs to ‘binlog.******’; | 删除******编号之前的所有日志 |
purge master logs before ‘yyyy-mm-dd hh24:mi:ss’; | 删除日志为“yyyy-mm-ddhh24:mi:ss"之前产生的所有日志 |
2.4 写入/刷盘机制
事务执行过程中,先把日志写入binlog cache,事务提交时,再把binlog cache写到binlog文件中。一个事务的binlog不能被拆开,确保一次性写入,系统将给每个线程分配一块内存作为binlog cache
对于InnoDB存储引擎而言,只有在事务提交时才会记录binlog,那么binlog什么时候才会将内存中的数据刷到磁盘呢?其实mysql是通过sync_binlog参数控制binlog的刷盘时机,取值范围是0-N【write和fsync的时机,由参数sync_binlog控制,默认为0】
- 0:每次提交事务都只write,由系统自行判断什么时候执行fsync。虽然性能得到提升,但是机器宕机,page cache里的binlog会丢失(不去强制要求,由系统自行判断何时写入磁盘)
- 1:每次提交事务都会执行fsync,如同redolog刷盘流程一样(每次commit的时候都要将binlog写入磁盘)
- N:每次提交事务都write,但累计N个事务后才fsync(每N个事务,才会将binlog写入磁盘)
在出现IO瓶颈时,将sync_binlog设置成一个较大的值,能提升性能。同样的,若机器宕机会丢失最近N个事务的binlog日志
三、undo log 回滚日志
3.1 undo log简介
undo log(回滚日志):是Innodb存储引擎层生成的日志,实现了事务中的原子性,主要用于事务回滚和MVCC。
它是InnoDB存储引擎在insert、update、delete的时候产生的便于数据回滚的日志。在数据更新之前,MySQL就需要先把更新前的数据记录到 undo log 日志中,当事务回滚时,可以利用 undo log 来进行回滚。作用包含两个——提供回滚、MVCC(多版本并发控制)。undo log主要分为两种:
- insert undo log:当insert的时候,产生的undo log日志只在回滚时需要,在事务提交后,可被立即删除(因为这种log只是对本事务可见,其他事务不可见,所以当事务提交后,这种类型的undo log就会被系统直接删除回收,也就是该undo log占用的undo页面链表被释放)。
- update undo log:update、delete的时候,产生的undo log日志不仅在事务回滚时需要,在快照读时也需要(也就是MVCC),所以不能在事务提交后马上删除,只在提交后放入undo log的链表,等待purge线程进行最后的删除。
事务需要保证原子性,也是说事务中的操作要么全部完成,要么什么也不做。如果事务执行到一半,出错了怎么办-回滚。但是怎么回滚呢,靠 undo 日志。undo 日志就是我们执行sql的逆操作
- undo 日志有两个作用:提供回滚和多个行版本控制(MVCC)
- 数据页里一行数据的格式 见3.3版本链,其中 roll_point 会指向一个undo 日志
- undo 日志一般会在事务提交时被删除,但是如果 undo 日志为 MVCC 服务 则暂时保留
- 一个事务会产生多个 undo 日志,mysql有专门的 undo 页 保存 undo 日志。innodb 会为每一个事务单独分配 undo 页链表(最多分配 4 个链表)
比如现在Tom的账户余额有100,现在有一个事务需要把Tom的账户余额更新为300,大致的流程如下图:
3.2 隐藏字段 —— 事务ID(TRX_ID)、ROLL_PTR
当我们创建了上面的这张表,我们在查看表结构的时候,就可以显式的看到 id、user_name、balance、wealth 这四个字段。 实际上除了这四个字段以外,InnoDB还会自动的给我们添加三个隐藏字段,分别是:
隐藏字段 | 含义 |
---|---|
DB_TRX_ID | 最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID。 |
DB_ROLL_PTR | 回滚指针,指向这条记录的上一个版本,用于配合undo log,指向上一个版本。 |
DB_ROW_ID | 隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段。 |
而上述的前两个字段是肯定会添加的, 是否添加最后一个字段DB_ROW_ID,得看当前表有没有主键,如果有主键,则不会添加该隐藏字段。
3.3 版本链
不同事务或相同事务对同一条记录进行修改,会导致该记录的undolog生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。
然后,有四个并发事务同时在访问这张表。
四、redo log 重做日志
事务日志redo log(重做日志):是Innodb存储引擎层生成的日志,实现了事务中的持久性,主要用于掉电等故障恢复。比如MySQL实例挂了或宕机了,重启时,InnoDB存储引擎会使用redo log恢复数据
- redo log是物理日志,记录页的物理修改操作:在某个数据页做了什么修改,比如对x表空间中的N数据页ZZ偏移量的地方做了AAAA更新,每当执行一个事务就会产生这样的一条或者多条物理日志
- 保证数据的持久性:redo log会在事务提交时将日志存储到磁盘redo log file,保证日志的持久性。同时mysql会将数据写入磁盘,保证数据的持久性【在事务提交时,只要先将redo log 持久化到磁盘即可,可以不需要等到将缓存在 Buffer Pool 里的脏页数据持久化到磁盘;当系统崩溃时,虽然脏页数据没有持久化,但是redo log已经持久化,接着MySQL重启后,可以根据redo log的内容,将所有数据恢复到最新的状态】
介绍下 缓冲池与数据页的概念
- 缓冲池(buffer pool):主内存中的一个区域,里面可以缓存磁盘上经常操作的真实数据,在执行增删改查操作时,先操作缓冲池中的数据(若缓冲池没有数据,则从磁盘加载并缓存),以一定频率刷新到磁盘,从而减少磁盘IO,加快处理速度
- 数据页(page):是InnoDB 存储引擎磁盘管理的最小单元,每个页的大小默认为 16KB。页中存储的是行数据
MySQL中数据是以页为单位,你查询一条记录,会从硬盘把一页的数据加载出来,加载出来的数据叫数据页,会放入到Buffer Pool中。后续的查询都是先从Buffer Pool中找,没有命中再去硬盘加载,减少硬盘IO开销,提升性能。
更新表数据的时候,也是如此,发现Buffer Pool里存在要更新的数据,就直接在Buffer Pool里更新。然后会把在某个数据页上做了什么修改记录到重做日志缓存(redo log buffer)里,接着刷盘到redo log文件里。同时,InnoDB引擎会在适当的时候,将这个操作记录更新到磁盘里面。
redo log的更新流程如下,以一次update操作为例
- 执行Update操作
- 先将原始数据读从磁盘读取到内存,修改内存中的数据。
- 生成一条重做日志写入redo log buffer ,纪录数据被修改后的值。
- 当事物提交时,需要将redo log buffer中的内容刷新到redo log file。
- 事物提交后,也会将内存中修改的数据写入到磁盘。
4.1 redo log详解
什么是 redo log?为了方便理解,先举个来自极客时间的例子:
还记得《孔乙己》这篇文章,饭店掌柜有一个粉板,专门用来记录客人的赊账记录。如果赊账的人不多,那么他可以把顾客名和账目写在板上。但如果赊账的人多了,粉板总会有记不下的时候,这个时候掌柜一定还有一个专门记录赊账的账本。
如果有人要赊账或者还账的话,掌柜一般有两种做法:
- 一种做法是直接把账本翻出来,把这次赊的账加上去或者扣除掉;
- 另一种做法是先在粉板上记下这次的账,等打烊以后再把账本翻出来核算。
在生意红火柜台很忙时,掌柜一定会选择后者,因为前者操作实在是太麻烦了。首先,你得找到这个人的赊账总额那条记录。你想想,密密麻麻几十页,掌柜要找到那个名字,可能还得带上老花镜慢慢找,找到之后再拿出算盘计算,最后再将结果写回到账本上。
这整个过程想想都麻烦。相比之下,还是先在粉板上记一下方便。你想想,如果掌柜没有粉板的帮助,每次记账都得翻账本,效率是不是低得让人难以忍受?
为什么需要 redo log?
为什么需要写Redo Log Buffer 和 Redo Log Flle?而不是直接持久化到磁盘?
跟上述案例类似,在 MySQL 中,如果每一次的更新要写进磁盘,这么做会带来严重的性能问题:
- 因为 Innodb 是以页为单位进行磁盘交互的,而一个事务很可能只修改一个数据页里面的几个字节,这时将完整的数据页刷到磁盘的话,太浪费资源了!
- 一个事务可能涉及修改多个数据页,并且这些数据页在物理上并不连续,使用随机 IO 写入性能太差!
为了解决这个问题,MySQL 的设计者就用了类似掌柜粉板的思路来提升更新效率。这种思路在 MySQL 中叫 WAL(Write-Ahead Logging),意思就是:先写 redo log 日志,后写磁盘。日志和磁盘就对应上面的粉板和账本。
具体到 MySQL 是这样的:有记录需要更新,InnoDB 把记录写到 redo log 中,并更新内存中的数据页,此时更新就算完成。同时,后台线程会把操作记录更新异步到磁盘中的数据页。
PS:当需要更新的数据页在内存中时,就会直接更新内存中的数据页;不在内存中时,在可以使用 change buffer(篇幅有限,后面写文章再聊) 的情况下,就会将更新操作记录到 change buffer 中,并将这些操作记录到 redo log 中;如果此时有查询操作,则触发 merge 操作,返回更改后的记录值。
有些人说 InnoDB 引擎把日志记录写到 redo log 中,redo log 在哪,不也是在磁盘上么?
对,这也是一个写磁盘的过程,但是与更新过程不一样的是,更新过程是在磁盘上随机 IO,费时。 而写 redo log 是在磁盘上顺序 IO,效率要高。
PPS:redo log 的存在就是把全局的随机写,变换为局部的顺序写,从而提高效率。
4.2 redo log的写入过程、刷盘时机
redo log 记录了事务对数据页做了哪些修改。它包括两部分:分别是内存中的日志缓冲(redo log buffer)和磁盘上的日志文件(redo log file)。
mysql 每执行一条 DML 语句,先将记录写入 redo log buffer,后续某个时间点再一次性将多个操作记录写到 redo log file。也就是我们上面提到的 WAL 技术。
计算机操作系统告诉我们:用户空间下的缓冲区数据是无法直接写入磁盘的。因为中间必须经过操作系统的内核空间缓冲区(OS Buffer)。
所以,redo log buffer 写入 redo logfile 实际上是先写入 OS Buffer,然后操作系统调用 fsync() 函数将日志刷到磁盘。过程如下:
mysql 支持三种将 redo log buffer 写入 redo log file 的时机,可以通过 innodb_flush_log_at_trx_commit 参数配置,各参数值含义如下:建议设置成1,这样可以保证MySQL 异常重启之后数据不丢失。
参数值 | 含义 |
---|---|
0(延迟写) | 事务提交时不会将 redo log buffer 中日志写到 os buffer,而是每秒写入os buffer 并调用 fsync() 写入到 redo logfile 中。也就是说设置为0时 是(大约)每秒刷新写入到磁盘中的,当系统崩溃,会丢失1秒钟的数据。 |
1(实时写、实时刷新) | 事务每次提交都会将 redo log buffer 中的日志写入 os buffer 并调用 fsync() 刷到 redo logfile 中。这种方式即使系统崩溃也不会丢失任何数据,但是因为每次提交都写入磁盘,IO的性能差。 |
2(实时写、延迟刷新刷新) | 每次提交都仅写入到 os buffer,然后是每秒调用 fsync() 将 os buffer 中的日志写入到 redo log file。 |
4.3 redo log file 的结构
InnoDB 的 redo log 是固定大小的。比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么 redo log file 可以记录 4GB 的操作。从头开始写。写到末尾又回到开头循环写。如下图:
上图中,write pos 表示 redo log 当前记录的 LSN (逻辑序列号) 位置,一边写一遍后移,写到第 3 号文件末尾后就回到 0 号文件开头; check point 表示数据页更改记录刷盘后对应 redo log 所处的 LSN(逻辑序列号) 位置,也是往后推移并且循环的。
PS:check point 是当前要擦除的位置,它与数据页中的 LSN 应当是一致的。
write pos 到 check point 之间的部分是 redo log 的未写区域,可用于记录新的记录;check point 到 write pos 之间是 redo log 已写区域,是待刷盘的数据页更改记录。
当 write pos 追上 check point 时,表示 redo log file 写满了,这时候有就不能执行新的更新。得停下来先擦除一些记录(擦除前要先把记录刷盘),再推动 check point 向前移动,腾出位置再记录新的日志。
4.4 什么是 crash-save
有了 redo log ,即在 InnoDB 存储引擎中,事务提交过程中任何阶段,MySQL 突然奔溃,重启后都能保证事务的完整性,已提交的数据不会丢失,未提交完整的数据会自动进行回滚。这个能力称为 crash-safe,依赖的就是 redo log 和 undo log 两个日志。
比如:重启 innodb 时,首先会检查磁盘中数据页的 LSN ,如果数据页的 LSN 小于日志中 check point 的 LSN ,则会从 checkpoint 开始恢复。
4.5 redo log细节
- redo log是物理日志(并非sql执行语句 sql语句是逻辑操作),顺序写入,性能比较高
- 重做日志是innodb存储引擎产生的
- 重做日志由redo logo buffer和redo log file构成,即由 内存中的重做日志缓存、日志文件构成
五、补充
5.1 binlog与redolog对比、逻辑日志与物理日志
redo log 和 binlog 主要有三种不同:
- redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。
- redo log 是物理日志(并非sql执行语句 sql语句是逻辑操作),记录的是在某个数据页上做了什么修改,比如"对XXX表空间中的YYY数据页ZZZ偏移量的地方做了AAA更新" ;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如 “给 ID=2 这一行的 age 字段加1”。
- redo log 是循环写的,空间固定会用完;binlog是可以追加写入的。追加写是指 binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
逻辑日志:可以简单理解为记录的就是sql语句
物理日志:因为mysql数据最终是保存在数据页中的,物理日志记录的就是数据页变更
5.2 update语句的执行流程
了解了binlog、redolog两种日志的概念,再来看看执行器和 InnoDB 引擎在执行 update 语句时的流程:
- 执行器取 id = 2 的行数据。ID 是主键,引擎用树搜索找到这一行。如果这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,再返回。
- 执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口写入这行新数据。
- 引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。
- 执行器生成这个操作的 binlog,并把 binlog 写入磁盘。
- 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,redo log 会写入 binlog 的文件名和位置信息来保证 binlog 和 redo log 的一致性,更新完成。
整个过程如下图所示,其中橙色框表示是在 InnoDB 内部执行的,绿色框表示是在执行器中执行的:
5.3 两阶段提交
在执行更新语句过程中,会记录redolog、binlog两块日志,以基本的事务为单位。
redolog在事务执行过程中可以不断写入,binlog只在提交事务时写入
为了解决日志之间的逻辑一致问题,InnoDB存储引擎使用两阶段提交方案
由于 redo log 和 binlog 是两个独立的逻辑,如果不用两阶段提交,要么就是先写完 redo log 再写 binlog,或者采用反过来的顺序。我们看看这两种方式会有什么问题。
仍然用前面的 update 语句来做例子。假设当前 id=2 的行,字段 age 的值是 22,再假设执行update 语句过程中在写完第一个日志后,第二个日志还没有写完期间发生了 crash,会出现什么情况呢?
- 先写redo log 后写binlog。假设在redo log写完,binlog 还没有写完的时候,MySQL进程异常重启。由于我们前面说过的,redo log 写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行 age 的值是 22。
但是 binlog 没写完就 crash 了,这时 binlog 里面并没有记录这个语句。因此,之后备份日志的时候,存起来的 binlog 里面就没有这条语句。
等到需要用这个binlog 来恢复临时库的话,由于这个语句的 binlog 丢失,这个临时库就会少了这一次更新,恢复出来的这一行 age 值就是 22,与原库的值不同。
- 先写 binlog 后写 redo log。如果在 binlog 写完之后 crash,由于 redo log 还没写,崩溃恢复以后这个事务无效,所以 age 的值是 22。但是 binlog 里面已经记录了"把从 22 改成 23" 这个日志。所以,在之后用 binlog 来恢复的时候就多了一个事务出来,恢复出来的这一行 age 的值就是 23,与原库的值不同。
所以,如果不使用"两阶段提交",数据库的状态就有可能和用 binlog 恢复出来的不一致。
另外:sync_binlog 这个参数建议设置成 1,表示每次事务的binlog都持久化到磁盘,这样可以保证 MySQL 异常重启之后 binlog 不丢失。
5.4 总结、使用场景
- 二进制binlog(归档日志):是Server层生成的日志,主要用于数据备份和主从复制;
- undolog(回滚日志):是Innodb存储引擎层生成的日志,实现了事务中的原子性,主要用于事务回滚和MVCC;
- 事务日志redolog(重做日志):是Innodb存储引擎层生成的日志,实现了事务中的持久性,主要用于掉电等故障恢复。
binlog的应用场景
- 主从复制:在Master端开启binlog,然后将binlog 发送到各个Slave端,Slave端重放binlog从而达到主从数据一致。
- 数据恢复:通过使用mysqlbinlog工具来恢复数据。
undolog的使用场景
- 事务回滚
- MVCC
redolog的使用场景
掉电等故障恢复
redo log一旦提交意味着持久化了,但是有时候需要对其进行rollback操作,那就需要undo log
主从:写数据时只写主库,在读数据时只读从库,这样即使写请求会锁表或者锁记录,也不会影响读请求的执行;
参考:
MySQL面试题(最全、超详细)—— 定位慢查询、undo log与redo log
MVCC 原理分析、MySQL是如何解决幻读的
《MySQL》系列 - 十张图详解 MySQL 日志(建议收藏)
MySQL进阶(日志)——MySQL的日志 & bin log (归档日志) & 事务日志redo log(重做日志) & undo log(回滚日志)