一、redo日志简介
在事务的实现机制上,MySQL采用的WAL(Write-ahead logging,预写式日志)机制来实现的,所有的修改都先被写入到日志中,然后再被应用到系统中,通常包含redo和undo两部分信息。
redo log称为重做日志,每当有操作时,在数据变更之前将操作写入redo log,这样当发生掉电之类的情况时系统可以在重启后继续操作。
undo log称为撤销日志,当一些变更执行到一半无法完成时,可以根据撤销日志恢复到变更之前的状态。
MySQL中用redo log来在系统Crash重启之类的情况时修复数据(事务的持久性),而undo log来保证事务的原子性。
二、redo日志参数调节
首先查看MySQL的数据目录,使用命令:
SHOW VARIABLES LIKE 'datadir'
进入/var/lib/mysql 目录进行查看,如下:
可以使用下面几个启动参数来调节redo日志:
- innodb_log_group_home_dir,该参数指定了redo日志文件所在的目录,默认就是当前的数据目录;
select @@basedir; -- basedir:该参数指定了安装 MySQL 的安装路径(mysql安装目录),填写全路径可以解决相对路径所造成的问题。
select @@datadir; -- datadir:该参数指定MySQL的数据文件的存放目录,数据库文件即我们常说的 MySQL data 文件。
@x 是 用户自定义的变量
@@x 是 global或session变量 (@@global @@session )
- innodb_log_file_size,该参数指定了每个redo日志文件的大小,默认值为48MB;
- innodb_log_files_in_group,该参数指定redo日志文件的个数,默认值是2,最大值为100;
所有磁盘上的redo日志文件可以不只一个,而是以一个日志文件组的形式出现。这些文件以ib_logfile[数字] (数字是0、1、2、3....)的形式命名进行。在将redo日志写入日志文件组时,是从ib_logfile0开始写,如果ib_logfile0写满了,就接着ib_logfile1写。同理:ib_logfile1写满了就去写ib_logfile2,依次类推。如果写到最后一个文件也写满了,就重新转到ib_logfile0继续写(覆盖写)。
三、redo日志作用
在真正访问MySQL数据之前,需要把在磁盘上的页缓存到内存中的Buffer Pool之后才可以访问。如果我们只在内存的Buffer Pool中修改了页面,假设在事务提交后突然发生了某个故障,导致内存中的数据都失效了,那么这个已经提交了的事务对数据库中所做的更改也就跟着丢失了,这是我们所不能忍受的。那么如何保证这个持久性呢?一个很简单的做法就是在事务提交完成之前把该事务所修改的所有页面都刷新到磁盘,但是这个做法有以下问题:
- 刷新一个完整的数据页太浪费了
有时候仅仅修改了某个页面中的一个字节,但是在InnoDB中是以页为单位来进行磁盘IO的,也就是说在该事务提交时不得不将一个完整的页面从内存中刷新到磁盘,而且一个页面默认是16KB大小,只修改一个字节就要刷新16KB的数据到磁盘上显然是太浪费了。
- 随机IO刷起来比较慢
一个事务可能包含很多语句,即使是一条语句也可能修改许多页面,该事务修改的这些页面可能并不相邻,这就意味着在将某个事务修改的Buffer Pool中的页面刷新到磁盘时,需要进行很多的随机IO,随机IO比顺序IO要慢,尤其对于传统的机械硬盘来说。
怎么办呢?我们只是想让已经提交了的事务对数据库中数据所做的修改永久生效,即使后来系统崩溃,在重启后也能把这种修改恢复出来。所以我们其实没有必要在每次事务提交时就把该事务在内存中修改过的全部页面刷新到磁盘,只需要把修改了哪些东西记录一下就好。
例如:某个事务将系统0号表空间中的第100号页面中偏移量为100处的那个字节的值由1改成2我们只需要记录一下即可:
将第0号表空间的100号页面的偏移量为100处的值更新为2。
以上述内容也被称之为重做日志,英文名为redo log,也可以称之为redo日志。与在事务提交时将所有修改过的内存中的页面刷新到磁盘中相比,只将该事务执行过程中产生的redo日志刷新到磁盘的好处如下:
- redo日志占用的空间非常小,存储表空间ID、页号、偏移量以及需要更新的值所需的存储空间是很小的。
- redo日志是顺序写入磁盘的
在执行事务的过程中,每执行一条语句,就可能产生若干条redo日志,这些日志是按照产生的顺序写入磁盘的,也就是使用顺序IO。
四、redo日志格
redo日志本质上只是记录了一下事务对数据库做了哪些修改。 InnoDB针对事务对数据库的不同修改场景定义了多种类型的redo日志,但是绝大部分类型的redo日志都有下边这种通用的结构:
各个部分的详细释义如下:
type:该条redo日志的类型,redo日志设计大约有53种不同的类型日志。
space ID:表空间ID。
page number:页号。
data:该条redo日志的具体内容。
五、简单的redo日志类型
如果某张表没有主键,并且没有定义不允许存储NULL值的UNIQUE键,那么InnoDB会自动为表添加一个名为row_id的隐藏列作为主键。
为这个row_id隐藏列进行赋值的方式如下:
- 内存中维护一个全局变量,当向某个包含row_id隐藏列的表中插入一条记录时,就会把这个全局变量的值当做新记录的row_id的值,并且把这个全局变量+1;
- 每当这个全局变量的值为256的倍数时,就会将该变量的值刷新到系统表空间页号为7的页面中一个名为Max Row Id的属性中。此时需要把这次对这个页面的修改以redo日志的形式记录下来
- 当系统启动时,会将这个Max Row Id属性加载到内存中。
InnoDB把这种极其简单的redo日志称之为物理日志,并且根据在页面中写入数据的多少划分了几种不同的redo日志类型:
MLOG_1BYTE(type=1)
表示在页面的某个偏移量处写入1字节的redo日志类型。
MLOG_2BYTE(type=2)
表示在页面的某个偏移量处写入2字节的redo日志类型。
MLOG_4BYTE(type=4)
表示在页面的某个偏移量处写入4字节的redo日志类型。
MLOG_8BYTE(type=8)
表示在页面的某个偏移量处写入8字节的redo日志类型
我们上边提到的Max Row ID属性实际占用8个字节的存储空间,所以在修改页面中的该属性时,会记录一条类型为MLOG_8BYTE的redo日志,MLOG_8BYTE的redo日志结构如下所示:
offset代表在页面中的偏移量
六、复杂的redo日志类型
有时候执行一条语句会修改非常多的页面,包括系统数据页面和用户数据页面(用户数据指的就是聚簇索引和二级索引对应的B+树)。以一条INSERT语句为例,它除了要向B+树的页面中插入数据,也可能更新系统数据Max Row ID的值,不过对于我们用户来说,平时更关心的是语句对B+树所做更新:
表中包含多少个索引,一条INSERT语句就可能更新多少棵B+树。
针对某一棵B+树来说,既可能更新叶子节点页面,也可能更新非叶子节点页面,也可能创建新的页面(在该记录插入的叶子节点的剩余空间比较少,不足以存放该记录时,会进行页面的分裂,在非叶子节点页面中添加目录项记录)。
画一个复杂的redo日志的示意图像如下这样:
总之:redo日志会把事务在执行过程中对数据库所做的所有修改都记录下来,在之后系统崩溃重启后可以把事务所做的任何修改都恢复出来。
七、redo日志的写入过程
redo log block和日志缓冲区
InnoDB为了更好的进行系统崩溃恢复,把生成的redo日志都放在了大小为512字节的块(block)中。
前面提到,为了解决磁盘速度过慢的问题而引入了Buffer Pool。同理,写入redo日志时也不能直接直接写到磁盘上,实际上在服务器启动时就向操作系统申请了一大片称之为redo log buffer的连续内存空间,翻译成中文就是redo日志缓冲区(内存),我们也可以简称为log buffer。这片内存空间被划分成若干个连续的redo log block,我们可以通过启动参数innodb_log_buffer_size来指定log buffer的大小,该启动参数的默认值为16MB。
redo日志刷盘时机
redo日志什么时候被刷新到磁盘中呢?例如:
- 事务提交时,为了保证持久性,必须要把修改这些页面对应的redo日志刷新到磁盘。
可以使用参数 innodb_flush_log_at_trx_commit 控制:
value有三个值可以选择:
0:当该系统变量值为0时,表示在事务提交时不立即向磁盘中同步redo日志,这个任务是交给后台线程做的。
这样很明显会加快请求处理速度,但是如果事务提交后服务器挂了,后台线程没有及时将redo日志刷新到磁盘,那么该事务对页面的修改会丢失。
1:当该系统变量值为1时,表示在事务提交时需要将redo日志同步到磁盘,可以保证事务的持久性。1也是innodb_flush_log_at_trx_commit的默认值。
2:当该系统变量值为2时,表示在事务提交时需要将redo日志写到操作系统的缓冲区中,但并不需要保证将日志真正的刷新到磁盘。
这种情况下如果数据库挂了,操作系统没挂的话,事务的持久性还是可以保证的,但是操作系统也挂了的话,那就不能保证持久性了。
- InnoDB认为如果当前写入log buffer的redo日志量已经占满了log buffer总容量的大约一半左右,就需要把这些日志刷新到磁盘上。
- 后台有一个线程,大约每秒都会刷新一次log buffer中的redo日志到磁盘。
- 正常关闭服务器时等等。
崩溃后的恢复
- 恢复机制
在服务器不挂的情况下,redo日志简直就是个大累赘,不仅没用,反而让性能变得更差。但是万一数据库挂了,就可以在重启时根据redo日志中的记录就可以将页面恢复到系统崩溃前的状态。
MySQL可以根据redo日志中的各种信息,来确定恢复的起点和终点。然后将redo日志中的数据,以哈希表的形式,将一个页面下的放到哈希表的一个槽中。之后就可以遍历哈希表,因为对同一个页面进行修改的redo日志都放在了一个槽里,所以可以一次性将一个页面修复好(避免了很多读取页面的随机IO)。并且通过各种机制,避免无谓的页面修复,比如已经刷新的页面,进而提升崩溃恢复的速度。
- 崩溃后的恢复为什么不用binlog?
- 者使用方式不一样
binlog 会记录表所有更改操作,包括更新删除数据,更改表结构等等,主要用于人工恢复数据,而 redo log 对于我们是不可见的,它是 InnoDB 用于保证 crash-safe 能力的,也就是在事务提交后MySQL崩溃的话,可以保证事务的持久性,即事务提交后其更改是永久性的。
总之:binlog 是用作人工恢复数据,redo log 是 MySQL 自己使用,用于保证在数据库崩溃时的事务持久性。
- redo log 是 InnoDB 引擎特有的,binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。
- redo log是物理日志,记录的是“在某个数据页上做了什么修改”,恢复的速度更快;binlog是逻辑日志,记录的是这个语句的原始逻辑,比如“给ID=1这条数据A字段加1 ”;
- redo log是“循环写”的日志文件,redo log 只会记录未刷盘的日志,已经刷入磁盘的数据都会从 redo log 这个有限大小的日志文件里删除。binlog 是追加日志,保存的是全量的日志。
- 最重要的是,当数据库crash 后,想要恢复未刷盘但已经写入 redo log 和 binlog 的数据到内存时,binlog 是无法恢复的。虽然 binlog 拥有全量的日志,但没有一个标志让 innoDB 判断哪些数据已经入表(写入磁盘),哪些数据还没有。
例如,binlog中记录了两条日志:
记录1:给ID=1这条数据A字段加1
记录2:给ID=1这条数据A字段加1
在记录1入表后,记录2未入表时,数据库crash。重启后,只通过 binlog 数据库无法判断这两条记录哪条已经写入磁盘,哪条没有写入磁盘,不管是两条都恢复至内存,还是都不恢复,对 ID=2 这行数据来说,都不对。
但 redo log 不一样,只要刷入磁盘的数据,都会从 redo log 中抹掉,数据库重启后,直接把 redo log 中的数据都恢复至内存就可以了。
标签:事务,log,MySQL,磁盘,日志,redo,页面 From: https://blog.51cto.com/u_14291296/6270188