在讲redo log之前,我们先来看一下MySQL的基础架构
MySQL整体架构来看,MySqL Server主要有三层:
-
连接层:连接客户端
-
Server层:执行SQL操作
-
存储引擎层:负责存储相关
Server层的日志,称为binlog(归档日志),所有存储引擎都具备,而redo log是InnoDB引擎特有的日志。关于binlog日志的具体内容,可以看《MySQL日志(二)—— 二进制日志binlog》这篇文章。
事务有4大特性ACID,即原子性、一致性、隔离性、持久性,在MySQL数据库中,InnoDB存储引擎事务的原子性、一致性、持久性是通过数据库的redo log和undo log来完成。redo log称为重做日志,用来保证事务的原子性和持久性,undo log用来保证事务的一致性。redo log和undo log 又被称为事务日志。
最初MySQL自带的引擎是MyISAM,但是MyISAM没有crash-safe的能力,binlog日志只能用于归档。而InnoDB是以插件形式引入MySQL的,由于binlog没有crash-safe能力,所以InnoDB使用另redo log来实现crash-safe。
和binlog日志相比,redo log日志有以下特点。
-
redo log是InnoDB引擎特有的;binlog是MySQL的Server层实现的,所有引擎都可以使用。
-
redo log是物理日志,记录的是“在某个数据页上做了什么修改”;binlog是逻辑日志,记录的是这个语句的原始逻辑也就是数据更新之后的状态,比如“某条数据做了什么操作”。
-
redo log是循环写入文件的,超过固定的大小后,会覆盖最先写入的日志;binlog是追加写入的。当binlog文件写到一定大小后会重新生产成一个日志文件,并不会覆盖以前的日志,也就是日志会自动切割。
checkpoint是当前要覆盖的位置。如果writepos跟checkpoint重叠,说明redo log已经写满,这时候需要同步redo log到磁盘中,否则就要覆盖掉之前的内容。
在InnoDB存储引擎的数据目录/var/lib/mysql/里面,会有两个名为ib_logfile0和ib_logfile1的文件,每个大小48M。这个文件就是InnoDB存储引的redo log(叫做重做日志)。在MySQL官方手册中将其称为InnoDB存储引擎的日志文件,不过更准确的定义应该是重做日志文件(redo log file)。重做日志文件对于InnoDB存储引擎至关重要,它们记录了InnoDB存储引擎的事务日志。
这种日志和磁盘配合的整个过程,其实就是 MySQL 里的 WAL 技术(Write-Ahead Logging),关键点就是先写日志,再写磁盘。
我们查看重做日志文件配置的大小
mysql> SHOW VARIABLES LIKE 'innodb_log_file_size';
+----------------------+----------+
| Variable_name | Value |
+----------------------+----------+
| innodb_log_file_size | 50331648 |
+----------------------+----------+
1 row in set (0.01 sec)
在 InnoDB存储引擎的数据目录生产的重做日志文件
[root@jeespring ~]# ll -h /var/lib/mysql | grep ib_log*
-rw-r----- 1 mysql mysql 48M Oct 3 10:39 ib_logfile0
-rw-r----- 1 mysql mysql 48M Oct 3 10:39 ib_logfile1
重做日志用来实现事务的持久性,其由两部分组成:
一是内存中的重做日志缓冲(redo log buffer),其是易失的;
二是重做日志文件(redo log file),是持久的。
我们看一下InnoDB的内存结构:
在Buffer Pool里面有一块内存区域(Log Buffer)专门用来保存即将要写入日志文件的数据,默认16M,它一样可以节省
磁盘IO。这里的Log Buffer就是redo log buffer。
mysql> SHOW VARIABLES LIKE 'innodb_log_buffer_size';
+------------------------+----------+
| Variable_name | Value |
+------------------------+----------+
| innodb_log_buffer_size | 16777216 |
+------------------------+----------+
1 row in set (0.00 sec)
InnoDB通过Force Log at Commit机制实现事务的持久性,即当事务提交(COMMIT)时,必须先将该事务的所有日志写入到重做日志文件进行持久化,待事务的COMMIT操作完成才算完成。这里的日志是指重做日志,在InnoDB存储引擎中,由两部分组成,即redo log和undo log。redo log用来保证事务的持久性,undo log用来帮助事务回滚及MVCC的功能。redo log基本上都是顺序写的,在数据库运行时不需要对redo log的文件进行读取操作。而undo log是需要进行随机读写的。
为了确保每次日志都写入重做日志文件,在每次将重做日志缓冲写入重做日志文件后,InnoDB存储引擎都需要调用一次fsync操作。由于重做日志文件打开并没有使用O_DIRECT选项,因此重做日志缓冲先写入文件系统缓存。为了确保重做日志写入磁盘,必须进行一次fsync操作。由于fsync的效率取决于磁盘的性能,因此磁盘的性能决定了事务提交的性能,也就是数据库的性能。
InnoDB存储引擎允许用户手工设置非持久性的情况发生,以此提高数据库的性能。即当事务提交时,日志不写入重做日志文件,而是等待一个时间周期后再执行fsync操作。由于并非强制在事务提交时进行一次fsync操作,显然这可以显著提高数据库的性能。但是当数据库发生宕机时,由于部分日志未刷新到磁盘,因此会丢失最后一段时间的事务。
重做日志刷新到磁盘的策略由参数innodb_flush_log_at_trx_commit控制。该参数的默认值为1,表示事务提交时必须调用一次fsync操作。还可以设置该参数的值为0和2。0表示事务提交时不进行写入重做日志操作,这个操作仅在master thread中完成,而在master thread中每1秒会进行一次重做日志文件的fsync操作。2表示事务提交时将重做日志写入重做日志文件,但仅写入文件系统的缓存中,不进行fsync操作。在这个设置下,当MySQL数据库发生宕机而操作系统不发生宕机时,并不会导致事务的丢失。而当操作系统宕机时,重启数据库后会丢失未从文件系统缓存刷新到重做日志文件那部分事务。
mysql> SHOW VARIABLES LIKE'innodb_flush_log_at_trx_commit';
+--------------------------------+-------+
| Variable_name | Value |
+--------------------------------+-------+
| innodb_flush_log_at_trx_commit | 1 |
+--------------------------------+-------+
1 row in set (0.01 sec)
值 | 含义 |
---|---|
0(延迟写) | log buffer将每秒一次地写入logfile中,并且log file的flush操作同时进行。该模式下,在事务提交的时候,不会主动触发写入磁盘的操作。 |
1(默认,实时写,实时刷) | 每次事务提交时MySQL都会把log buffer的数据写入log file,并且刷到磁盘中去。 |
2(实时写,延迟刷) | 每次事务提交时MySQL都会把log buffer的数据写入log file。但是flush操作并不会同时进行。该模式下,MySQL会每秒执行一次flush操作 |
我们对redo log有了初步的认识,我们再来看执行器和InnoDB引擎在执行下面这条update语句时的内部流程:
UPDATE user SET age = age + 1 WHERE id = 1;
- 执行器先找存储引擎获取id=1这一行数据。如果命中缓存,就直接返回;否则从磁盘获取数据然后读入内存,最后再返回。
-
执行器拿到引擎给的行数据,把age这个值加上1,比如原来是N,现在就是N+1,得到新的一行数据,再调用引擎接口写入这行新数据。
-
引擎将这行新数据更新到内存中,同时将这个更新操作记录到redo log里面,此时redo log处于prepare状态。然后告知执行器执行完成了,随时可以提交事务。
-
执行器生成这个操作的binlog,并把binlog写入磁盘。
-
执行器调用引擎的提交事务接口,引擎把刚刚写入的redo log改成提交(commit)状态,更新完成。
崩溃恢复