重做日志(redo log)、回滚日志(undo log)、二进制日志(binlog)、错误日志(errorlog)、慢查询日志(slow query log)、一般查询日志(general log),中继日志(relay log)。其中重做日志和回滚日志与事务操作相关只有InnoDB引擎支持,二进制日志是通用日志也与事务操作有一定的关系,这三种日志,对理解MySQL中的事务操作有着重要的意义。
一、重做日志(Redo log)
事务具备原子性、一致性、隔离性、持久性;其中事务的一致性,是通过undo log 来保障的,而事务的持久性,则是通过redo log来实现的;
采用事务日志的形式可以帮助提高事务的效率。事务日志采用的是追加的方式,因此写日志的操作是磁盘上一小块区域内的顺序I/O,而不像原始数据,需要随机I/O在磁盘的多个地方不断移动磁头,所以采用事务日志的方式相对要块的多。事务日志持久以后,内存中被修改的数据在后台可以慢慢地刷回到磁盘。目前大多数的磁盘引擎都是这样实现的,我们通常称之为预写式日志(Write-Ahead Logging),修改数据需要写两次磁盘。
重做日志是物理格式的日志,记录的是物理数据页面的修改的信息,其当更新一条数据时,InnoDB会找到要更新的行数据,把做了什么修改记录写到redolog中,并把这行数据更新到内存中,整个过程就算完成了。
redolog是固定大小的(如下图),所以它只能循环记录做了什么修改,write pos为当前记录的位置,check point为当前可以擦除的位置,代表更新的行已经完成数据库的磁盘更改。
如果此时宕机了, 内存中更改后的行数据已经不再了, 但是redolog还是存在的,是可以恢复,这也就是mysql的crash-safe特性。
1.2 redo log的写入
redo log的写入在事物开始之后逐步写盘的。之所以说重做日志是在事务开始之后逐步写入重做日志文件,而不一定是事务提交才写入重做日志缓存,原因就是,重做日志有一个缓存区Innodb_log_buffer,Innodb_log_buffer的默认大小为8M(这里设置的16M),Innodb存储引擎先将重做日志写入innodb_log_buffer中。
然后会通过以下三种方式将innodb日志缓冲区的日志刷新到磁盘:
- 1、Master Thread 每秒一次执行刷新Innodb_log_buffer到重做日志文件。
- 2、每个事务提交时会将重做日志刷新到重做日志文件。
- 3、当重做日志缓存可用空间 少于一半时,重做日志缓存被刷新到重做日志文件
由此可以看出,重做日志通过不止一种方式写入到磁盘,尤其是对于第一种方式,Innodb_log_buffer到重做日志文件是Master Thread线程的定时任务。因此重做日志的写盘,并不一定是随着事务的提交才写入重做日志文件的,而是随着事务的开始,逐步开始的。
另外引用《MySQL技术内幕 Innodb 存储引擎》(page37)上的原话:
即使某个事务还没有提交,Innodb存储引擎仍然每秒会将重做日志缓存刷新到重做日志文件。
这一点是必须要知道的,因为这可以很好地解释再大的事务的提交(commit)的时间也是很短暂的。
二、回滚日志(undo log)
在数据修改的时候,不仅记录了redo,还记录了相对应的 undo,如果因为某些原因导致事务失败或回滚了,可以借助该 undo 进行回滚。
undo log 和 redo log 记录物理日志不一样,它是逻辑日志。可以认为当 delete 一条记录时,undo log 中会记录一条对应的 insert 记录,反之亦然,当 update 一条记录时,它记录一条对应相反的 update 记录。
有时候应用到行版本控制的时候,也是通过 undo log 来实现的:当读取的某一行被其他事务锁定时,它可以从 undo log 中分析出该行记录以前的数据是什么,从而提供该行版本信息,让用户实现非锁定一致性读取。
三、二进制日志(bin log)
最早接触 binlog 是做数据库主从同步的时候,知道是通过同步 binlog 实现的。binlog 是 没有 MySQL sever 层维护的一种二进制日志,与 innodb 引擎中的 redo/undo log 是完全不同的日志。其主要是用来记录对 MySQL 数据更新或潜在发生更新的 SQL 语句,并以 “事务”的形式保存在磁盘中。
binlog 主要有以下作用:
复制:MySQL 主从复制在 Master 端开启 binlog,Master 把它的二进制日志传递给 slaves 并回放来达到 master-slave 数据一致的目的
数据恢复:通过 mysqlbinlog 工具恢复数据
增量备份
这里要了解的几个bin log的知识点:
(1)binlog 不会记录不修改数据的语句,比如Select或者Showbinlog 会重写日志中的密码,保证不以纯文本的形式出现
(2)MySQL 8 之后的版本可以选择对 binlog 进行加密
(3)具体的写入时间:在事务提交的时候,数据库会把 binlog cache 写入 binlog 文件中,但并没有执行fsync()操作,即只将文件内容写入到 OS 缓存中。随后根据配置判断是否执行 fsync。
(4)删除时间:保持时间由参数expire_logs_days配置,也就是说对于非活动的日志文件,在生成时间超过expire_logs_days配置的天数之后,会被自动删除。
3.1 bin log 的存储形式
3.1.1 ROW格式(默认)
Row 格式仅保存记录被修改细节,不记录 sql 语句上下文相关信息。新版本的 MySQL 默认是 Row 格式。
优点:能非常清晰的记录下每行数据的修改细节,不需要记录上下文相关信息,因此不会发生某些特定情况下的存储过程、函数或者触发器的调用触发无法被正确复制的问题,任何情况都可以被复制,且能加快从库重放日志的效率,保证从库数据的一致性
缺点:由于所有的执行的语句在日志中都将以每行记录的修改细节来记录,因此,可能会产生大量的日志内容,干扰内容也较多。比如一条 update 语句,如修改多条记录,则 binlog 中每一条修改都会有记录,这样造成 binlog 日志量会很大,特别是当执行alter table之类的语句的时候,由于表结构修改,每条记录都发生改变,那么该表每一条记录都会记录到日志中,实际等于重建了表。
3.1.2 Statement 格式
每一条会修改数据的 sql 都会记录在 binlog 中。
优点:只需要记录执行语句的细节和上下文环境,避免了记录每一行的变化,在一些修改记录较多的情况下相比 Row 格式能大大减少 binlog 日志量,节约 IO,提高性能。
另外还可以用于实时的还原。
主从版本可以不一样,从服务器版本可以比主服务器版本高。
缺点:为了保证 sql 语句能在 slave 上正确执行,必须记录上下文信息,以保证所有语句能在 slave 得到和在 master 端执行时候相同的结果。
另外,主从复制时,存在部分函数(如 sleep)及存储过程在 slave 上会出现与 master 结果不一致的情况,而相比 Row 记录每一行的变化细节,绝不会发生这种不一致的情况。
3.1.3 Mixed 格式
以上两种格式混合。
经过前面的对比,可以发现 Row 和 Statement 各有优势,如果可以根据 sql 语句取舍可能会有更好地性能和效果。Mixed 便是以上两种形式的结合。不过,新版本的 MySQL 对 Row 模式也做了优化,并不是所有的修改都会完全以 Row 形式来记录,像遇到表结构变更的时候就会以 Statement 模式来记录,如果 SQL 语句确实就是 update 或者 delete 等修改数据的语句,那么还是会记录所有行的变更;因此,现在一般使用 Row 即可。
3.2 主从复制
复制是 MySQL 最重要的功能之一,MySQL 集群的高可用、负载均衡和读写分离都是基于复制来实现。复制步骤如下:
(1)Master 将数据改变记录到二进制日志(binary log)中。
(2)Slave 上面的 IO 进程连接上 Master,并请求从指定日志文件的指定位置(或者从最开始的日志)之后的日志内容。
(3)Master 接收到来自 Slave 的 IO 进程的请求后,负责复制的 IO 进程会根据请求信息读取日志指定位置之后的日志信息,返回给 Slave 的 IO 进程。返回信息中除了日志所包含的信息之外,还包括本次返回的信息已经到 Master 端的 binlog 文件的名称以及 binlog 的位置。
(4)Slave 的 IO 进程接收到信息后,将接收到的日志内容依次添加到 Slave 端的 relaylog 文件的最末端,并将读取到的 Master 端的 binlog 的文件名和位置记录到 masterinfo 文件中,以便在下一次读取的时候能够清楚的告诉 Master 从某个 binlog 的哪个位置开始往后的日志内容
(5)Slave 的 SQL 进程检测到 relaylog 中新增加了内容后,会马上解析 relaylog 的内容成为在 Master 端真实执行时候的那些可执行的内容,并在自身执行。
四、事务日志运行原理
4.1 事务日志写入顺序
第一阶段: InnoDB Prepare阶段。此时SQL已经成功执行,并生成事务ID(xid)信息及redo和undo的内存日志。此阶段InnoDB会写事务的redo log。
但要注意的是,此时redo log只是记录了事务的所有操作日志,并没有记录提交(commit)日志,因此事务此时的状态为Prepare,即prepare commit。此阶段对binlog不会有任何操作。
第二阶段:commit 阶段,这个阶段又分成两个步骤。第一步写binlog(先调用write()将binlog内存日志数据写入文件系统缓存,再调用fsync()将binlog文件系统缓存日志数据永久写入磁盘)
第二步完成事务的提交(commit),此时在redo log中记录此事务的提交日志(增加commit 标签)。
可以看出,此过程中是先写redo log再写binlog的。但需要注意的是,在第一阶段并没有记录完整的redo log(不包含事务的commit标签),而是在第二阶段记录完binlog后再写入redo log的commit 标签。还要注意的是,在这个过程中是以第二阶段中binlog的写入与否作为事务是否成功提交的标志。
顺序是:prepare阶段(写redo log 、undo log),commit阶段(写binlog,如果成功,redo log变成commit阶段)
通过上述MySQL内部XA的两阶段提交就可以解决binlog和redo log的一致性问题。数据库在上述任何阶段crash,主从库都不会产生不一致的错误。
4.2 数据崩溃恢复过程
整体流程是,如果开启binlog,会先将redolog 标记为prepare commit,然后记录binlog,binlog记录成功后将redolog标记为commi;也成为2pc两段式提交;如果没有binlog的话,直接记录redolog就行,就不必2pc阶段了;
2pc的原因:redolog是为了保证主库的数据不丢失,而binlog是为了保证从库的数据不丢失;所以分以下三种情况:
(1)redolog prepare commit 记录完成发生crash;此时没有记录binlog,所以会直接丢弃该条数据,实现主从的一致;
(2)redolog prepare commit记录完成,binlog记录完成,发生crash;此时的redolog不是commit状态,它会扫描最后一个binlog文件,并提取其中的事务ID(xid),然后 将 redo log没有记录commit 标签的xid和Binlog中提取的xid做比较,如果在Binlog中存在,则提交该事务,否则回滚该事务。
(3)redolog commit且bin log记录完成后crash,直接恢复就可以
五、问答
5.1 bin log 和 redo log的区别
- redo log是 innodb存储引擎特有的;binlog是server层,属于共有的;
- redo log是物理日志,记录在某个数据页的修改;binlog是逻辑日志;
- redo log是一个环,循环写;binlog是追加写,不会覆盖以前的日志
- redo log用于异常重启恢复;binlog用于备份,主从复制
5.2 为什么不用redo log 恢复数据,而使用binlog?
redo log是一个固定大小的环,当事务提交成功的时候,redo log中的数据会被刷进磁盘,此时redo log中的这部分数据就可以被新的redo log 来覆盖掉了,所以redo log 仅仅保存的是一部分的事务日志,不是全量日志不能用于恢复数据
5.3 redo log 和 bin log 的一致性是怎么保证的?
bin log 主要用于MySQL集群主从复制,所以为了保证主从数据的一致性,所以只有当事务真正提交成功的时候才会写入bin log ,而bin log 写入成功的时候,才会真正更新redo log的状态为已提交
