redo log(重做日志)

InnoDB修改数据的基本流程,如果我们想修改DB上某行数据的时候,InnoDB会把数据从磁盘读取到内存的缓冲池上进行修改,这时数据在内存被修改,与磁盘中的数据有差异,我们把这样差距的数据称之为脏页。InnoDB并不是把脏页每次都要刷新到磁盘,如果每次都要刷新到磁盘就会产生海量io操作,会严重的损耗InnoDB的处理性能。但是现在脏页和磁盘中的数据存在差异,如果现在DB发生crash就会导致数据丢失。为了解决这个问题,就出现的了redo log。redo log属于物理日志(储存了数据被修改的值),redo log 是 InnoDB 引擎特有的日志。

MySQL 里经常说到的 WAL 技术,WAL 的全称是Write-Ahead Logging(预写日志),用于保证数据操作的原子性和持久性。它的关键点就是先写日志,再写磁盘。redo日志什么时候会刷新到磁盘? InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做。

InnoDB 的 redo log 是固定大小的,比如配置一组4个文件,每个文件1G那就一共4G。从头开始写,写到末尾就又回到开头循环写。如同所示:

wirte pos 是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头。checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。

write pos 和 checkpoint 之间的是还空着的部分,可以用来记录新的操作。如果write pos 追上 checkpoint,表示满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把 checkpoint 推进一下。

磁盘存储

默认是两个文件,存在datadir目录中,文件名分别为ib_logfile0ib_logfile1,查看data dir可以通过命令select @@datadir;

1
2
3
4
5
6
mysql> select @@datadir;
+---------------------------------------+
| @@datadir |
+---------------------------------------+
| /home/mysql/data3001/dbs3001/ |
+---------------------------------------+

文件数量和每个文件的大小可以通过变量innodb_log_files_in_groupinnodb_log_file_size来设置,这两个变量都是只读变量,只能通过在配置文件中修改并重启的方式生效。redo log文件的总大小(innodb_log_file_size * innodb_log_files_in_group)一般建议配置为可以处理一个小时写操作的量,数值越大则通过checkpoint刷新的次数越少,就越能降低磁盘IO。

配置文件(my.cnf)

1
2
3
[mysqld]
innodb_log_files_in_group=2
innodb_log_file_size=1048576000

查看变量

1
2
3
4
5
6
7
mysql> show variables like 'innodb_log_file%';
+---------------------------+------------+
| Variable_name | Value |
+---------------------------+------------+
| innodb_log_file_size | 1048576000 |
| innodb_log_files_in_group | 2 |
+---------------------------+------------+

查看磁盘文件:

1
2
cd /home/mysql/data3001/dbs3001/ 
ls -ls

工作原理

redo log本身也由两部分所构成即重做日志缓冲(redo log buffer)和重做日志文件(redo log file)。这样的设计同样也是为了调和内存与磁盘的速度差异。InnoDB写入磁盘的策略可以通过innodb_flush_log_at_trx_commit这个参数来控制。

当该值为1时,当然是最安全的,但是数据库性能会受一定影响。事物提交的时候必须调用一次fsync函数。

为0时性能较好,但是会丢失掉master thread还没刷新进磁盘部分的数据。

这里我想简单介绍一下master thread,这是InnoDB一个在后台运行的主线程,从名字就能看出这个线程相当的重要。它做的主要工作包括但不限于:刷新日志缓冲,合并插入缓冲,刷新脏页等。master thread大致分为每秒运行一次的操作和每10秒运行一次的操作。master thread中刷新数据,属于checkpoint的一种。所以如果在master thread在刷新日志的间隙,DB出现故障那么将丢失掉这部分数据。

当该值为2时,当DB发生故障能恢复数据。但如果操作系统也出现宕机,那么就会丢失掉,文件系统没有及时写入磁盘的数据。

这里说明一下,innodb_flush_log_at_trx_commit设为非0的值,并不是说不会在master thread中刷新日志了。master thread刷新日志是在不断进行的,所以redo log写入磁盘是在持续的写入。

bin log (归档日志)

逻辑日志(储存了逻辑sql修改的语句)

MySQL分Server层和引擎层,Server层的日志是bin log。引擎有自己的日志,比如InnoDB引擎日志是redo log。MySQL自带的引擎是MyISAM,MyISAM引擎没有crash-safe能力,bin log只能用于归档。InnoDB就是另外一个公司就是以插件的方式引入,使用redo log以保证crash-safe能力。

常用命令

1
2
3
4
5
6
7
8
9
10
11
12
#查看日志开启状态 
show variables like 'log_%';
#查看所有binlog日志列表
show master logs;
#查看最新一个binlog日志的编号名称,及其最后一个操作事件结束点
show master status;
#查看binlog配置
show global variables like "binlog%";
#刷新log日志,立刻产生一个新编号的binlog日志文件,跟重启一个效果
flush logs;
#清空所有binlog日志
reset master;

开启bin log

修改配置文件(my.cnf )

1
2
3
4
5
6
7
8
#开启binlog日志
log_bin=ON
#binlog日志的基本文件名
log_bin_basename=/var/lib/mysql/mysql-bin
#binlog文件的索引文件,管理所有binlog文件
log_bin_index=/var/lib/mysql/mysql-bin.index
#配置serverid
server-id=1

详细binlog的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[mysqld]
#设置日志三种格式:STATEMENT、ROW、MIXED 。
binlog_format = mixed
#设置日志路径,注意路经需要mysql用户有权限写,这里可以写绝对路径,也可以直接写mysql-bin(后者默认就是在/var/lib/mysql目录下)
log-bin = /data/mysql/logs/mysql-bin.log
#设置binlog清理时间
expire_logs_days = 7
#binlog每个日志文件大小
max_binlog_size = 100m
#binlog缓存大小
binlog_cache_size = 4m
#最大binlog缓存大小
max_binlog_cache_size = 512m
#配置serverid
server-id=1

bin log 日志格式:

STATMENT模式:MySQL默认模式,每一条会修改数据的sql都会记录在binlog中。

优点:不需要记录每条数据的变化,减少binlog日志量,节省IO,提高了性能。

缺点:由于记录的只是执行语句,如果数据库是主从模式,主从数据不一致就会导致有问题。有一些特定的函数也是有问题。(如sleep()函数, last_insert_id(),以及user-defined functions(udf)会出现问题).

Row模式: 基于行的复制。不记录sql上下文信息,仅记录那条数据被修改了。修改了什么样子。

优点: binlog中可以不记录执行的sql语句的上下文相关的信息,仅需要记录那一条记录被修改成什么了。所以rowlevel的日志内容会非常清楚的记录下每一行数据修改的细节。而且不会出现某些特定情况下的存储过程,或function,以及trigger的调用和触发无法被正确复制的问题

缺点:会产生大量日志,尤其是在alert table的时候。

MIXED模式:以上两种模式的混合使用,一般的复制使用STATEMENT模式保存binlog对于STATEMENT模式无法复制的操作使用ROW模式保存binlog,MySQL会根据执行的SQL语句选择日志保存方式。

查看bin log

bin log 是二进制文件,平常文件查看命令是无法查看的,必须使用自带的mysqlbinlog命令查看。

在两阶段提交的不同瞬间,MySQL 如果发生异常重启,是怎么保证数据完整性的?

在两阶段提交的不同时刻,MySQL 异常重启会出现什么现象。

时刻A崩溃

如果在图中时刻 A 的地方,也就是写入 redo log 处于 prepare 阶段之后、写 binlog 之前,发生了崩溃(crash),由于此时 binlog 还没写,redo log 也还没提交,所以崩溃恢复的时候,这个事务会回滚。这时候,binlog 还没写,所以也不会传到备库。

时刻B崩溃

在时刻 B,也就是 binlog 写完,redo log 还没 commit 前发生 crash,那崩溃恢复的时候 MySQL 会怎么处理?

先来看一下崩溃恢复时的判断规则

  1. 如果 redo log 里面的事务是完整的,也就是已经有了 commit 标识,则直接提交;
  2. 如果 redo log 里面的事务只有完整的 prepare,则判断对应的事务 binlog 是否存在并完整:
  • 如果是,则提交事务;
  • 否则,回滚事务。

时刻 B 发生 crash 对应的就是 2(1) 的情况,崩溃恢复过程中事务会被提交

时刻B崩溃后MySQL 怎么知道 binlog 是完整的?

一个事务的 binlog 是有完整格式的:

  • statement 格式的 binlog,最后会有 COMMIT;
  • row 格式的 binlog,最后会有一个 XID event。

redo log binlog 是怎么关联起来的?

它们有一个共同的数据字段,叫 XID。崩溃恢复的时候,会按顺序扫描 redo log:

  • 如果碰到既有 prepare、又有 commit 的 redo log,就直接提交;
  • 如果碰到只有 parepare、而没有 commit 的 redo log,就拿着 XID 去 binlog 找对应的事务。

redo log 一般设置多大?

redo log 太小的话,会导致很快就被写满,然后不得不强行刷 redo log,这样 WAL 机制的能力就发挥不出来了。

所以,如果是现在常见的几个 TB 的磁盘的话,就不要太小气了,直接将 redo log 设置为 4 个文件、每个文件 1GB 吧。

正常运行中的实例,数据写入后的最终落盘,是从 redo log 更新过来的还是从 buffer pool 更新过来的呢?

redo log 并没有记录数据页的完整数据,所以它并没有能力自己去更新磁盘数据页,也就不存在“数据最终落盘,是由 redo log 更新过去”的情况。

  1. 如果是正常运行的实例的话,数据页被修改以后,跟磁盘的数据页不一致,称为脏页。最终数据落盘,就是把内存中的数据页写盘。这个过程,甚至与 redo log 毫无关系。
  2. 在崩溃恢复场景中,InnoDB 如果判断到一个数据页可能在崩溃恢复的时候丢失了更新,就会将它读到内存,然后让 redo log 更新内存内容。更新完成后,内存页变成脏页,就回到了第一种情况的状态。

参考:MySQL实战45讲

参考:bin log