MySQL 实战45讲-02 redo log(重做日志)和bin log(归档日志)
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_logfile0
和ib_logfile1
,查看data dir可以通过命令select @@datadir;
1 | mysql> select @@datadir; |
文件数量和每个文件的大小可以通过变量innodb_log_files_in_group
和innodb_log_file_size
来设置,这两个变量都是只读变量,只能通过在配置文件中修改并重启的方式生效。redo log文件的总大小(innodb_log_file_size * innodb_log_files_in_group)一般建议配置为可以处理一个小时写操作的量,数值越大则通过checkpoint刷新的次数越少,就越能降低磁盘IO。
配置文件(my.cnf)
1 | [mysqld] |
查看变量
1 | mysql> show variables like 'innodb_log_file%'; |
查看磁盘文件:
1 | cd /home/mysql/data3001/dbs3001/ |
工作原理
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 | #查看日志开启状态 |
开启bin log
修改配置文件(my.cnf )
1 | #开启binlog日志 |
详细binlog的配置
1 | [mysqld] |
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 会怎么处理?
先来看一下崩溃恢复时的判断规则
- 如果 redo log 里面的事务是完整的,也就是已经有了 commit 标识,则直接提交;
- 如果 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 更新过去”的情况。
- 如果是正常运行的实例的话,数据页被修改以后,跟磁盘的数据页不一致,称为脏页。最终数据落盘,就是把内存中的数据页写盘。这个过程,甚至与 redo log 毫无关系。
- 在崩溃恢复场景中,InnoDB 如果判断到一个数据页可能在崩溃恢复的时候丢失了更新,就会将它读到内存,然后让 redo log 更新内存内容。更新完成后,内存页变成脏页,就回到了第一种情况的状态。
参考:MySQL实战45讲
参考:bin log