MySQL服务器在事务运行过程中会产生2种事务日志
- 重做日志(redo log)
- 撤销日志(undo log)
重做日志(redo log)是InnoDB存储引擎的物理日志,是InnoDB用于崩溃恢复(crash recovery)以及组提交(group commit)策略的重要文件。MySQL事务的持久性(Durability)就是通过redo log来实现的。
物理日志:记录修改了某个页面的某个偏移量处修改的数据
逻辑日志:记录了完整的sql语句
redo log的作用
InnoDB存储引擎是以页为单位来存储数据的。在对数据库进行增删改查操作的时候,都是需要先把磁盘的页加载到内存中的Buffer Pool中才可以访问。
查询数据的时候,也是先从Buffer Pool中查找,如果没有命中再去磁盘加载,以减少磁盘IO,提升性能。
更新数据的时候,如果Buffer Pool存在要更新的数据,就直接在Buffer Pool中更新。
如果在事务提交后发生故障,且没有其他措施,则会导致内存中的数据都丢失。那么该如何解决?一个简单的做法就是事务提交之后,就立刻把所有页面的修改刷新到磁盘。这样做会有2个缺点:
- 有时可能只修改了某个页面的一个字节,就要把一个页面(默认16KB)刷新到磁盘,过于浪费性能。
- 一个事务可能包含多个语句,即使是一条语句也可能修改多个页面,如果事务修改的页面存储空间不相邻,由此带来的随机I/O刷新会比较慢。
因此InnoDB存储引擎引入了redo log,作用是:
- 用于记录某个页面的某个偏移量处数据的修改。当进行增删改操作时,MySQL会在更新Buffer Pool中的缓存页数据时,记录一条对应操作的redo log。
- 当MySQL宕机或断电时,如果有缓存页的数据还没来得及写入磁盘,那么在MySQL重启的时候,可以根据redo log日志进行数据重做,将数据恢复到宕机或断电前的状态,保证了更新数据不丢失。
redo log的原理
这种先写日志,后写磁盘的技术就是MySQL经常说的 WAL(Write-Ahead Logging)预写日志,是数据库系统中常见的一种手段,用于保证数据操作的原子性和持久性。
redo log的组成
redo log包括2部分:
- redo log buffer:内存中的日志缓冲
- redo log file:磁盘上的重做日志文件
redo log的写入机制
事务执行过程中,会先把redo log写到redo log buffer,在事务提交的时候再把redo log buffer的内容持久化到磁盘。
当innodb_flush_log_at_trx_commit = 1时,每次事务提交的时候都会把redo log持久化到磁盘,除此之外还有几种场景未提交的redo log也会写入到磁盘:
- redo log buffer的日志量占总容量的50%时,后台线程会主动写盘
- 后台有一个线程,大概每秒一次的频率将redo log buffer的内容写入到磁盘
- checkpoint的时候
- 正常关闭服务器的时候
InnoDB存储引擎默认有1个重做日志文件组(redo log group),1个文件组包含2个重做日志文件(redo log file),分别是 ib_logfile_0 和 ib_log_file_1 。
redo log文件的容量是有限的(默认48M),所以redo log文件是循环写入的。
如下图所示,如果1个日志文件组有4个redo log file。则InnoDB引擎会先写redo log file 0,当file 0写满会切换到redo log file 1,当file 1写满会切换到redo log file 2,当file 2写满会切换到redo log file 3,当file 3写满会再次切换到redo log file 0。
checkpoint机制
redo log 还有一个checkpoint机制,就是在redo log文件中找一个位置,将这个位置前的页都刷新到磁盘中,这个位置就称为check point(检查点)。 Checkpoint 解决了以下几个问题:
- 缓冲池不够用时,将脏页刷新到磁盘:所谓缓冲池不够用的意思就是缓冲池的空间无法存放新读取到的页,这个时候 InnoDB 引擎会怎么办呢?LRU 算法。InnoDB 存储引擎对传统的 LRU 算法做了一些优化,用其来管理缓冲池这块空间。
- redo log 不可用时,将脏页刷新到磁盘:所谓 redo log 不可用就是所有的 redo log file 都写满了。但事实上,其实 redo log 中的数据并不是时时刻刻都是有用的,那些已经不再需要的部分就称为 ”可以被重用的部分“,即当数据库发生宕机时,数据库恢复操作不需要这部分的 redo log,因此这部分就可以被覆盖重用(或者说被擦除)。
- 缩短数据库的恢复时间:当数据库发生宕机时,数据库不需要重做所有的日志,因为 Checkpoint 之前的页都已经刷新回磁盘。故数据库只需对 Checkpoint 后的 redo log 进行恢复就行了。这显然大大缩短了恢复的时间。
redo log的相关参数
innodb_log_buffer_size=16777216 # 用于设置日志缓冲区的大小。默认值是16MB,但如果事务之中含有blog/text等大字段,这个缓冲区会被很快填满会引起额外的IO负载。可通过查看innodb_log_waits状态
show status like 'innodb_log_waits',如果不为0的话,则需要增加innodb_log_buffer_size
innodb_log_file_size=50331648 # 用于MySQL日志组中每个日志文件的大小。此参数是一个全局的静态参数,不能动态修改。MySQL5.6.8之后默认值是48M
innodb_log_files_in_group=2 # 用于指定redo日志组的文件个数。默认为2个redo log日志文件
innodb_flush_log_at_trx_commit={0|1|2} # 指定何时将事务日志刷到磁盘,默认值是为1。( 0:表示每秒将"log buffer"同步到"os buffer"且从"os buffer"刷到磁盘日志文件中; 1:表示每事务提交都将"log buffer"同步到"os buffer"且从"os buffer"刷到磁盘日志文件中; 2:表示每事务提交都将"log buffer"同步到"os buffer"但每秒才从"os buffer"刷到磁盘日志文件中)
推荐设置 innodb_flush_log_at_trx_commit=1 sync_binlog=1 这是MySQL的双1设置,前者保证redolog的不丢失、后者保证了binlog的不丢失
脏页的刷盘时机
当内存页的数据跟磁盘页的数据不一致时,内存的这个页就是脏页。 脏页的刷盘时机
- InnoDB的redo log写满了,此时系统会停止所有更新操作,把checkpoint往前推进,redo log留出空间可以继续写。
- 当需要新的内存页,即内存的buffer pool不够用的时候,就需要淘汰内存里的一些数据页。
- MySQL认为系统“空闲”的时候。
- MySQL正常关闭的时候,会把内存的脏页都flush到磁盘。
update语句的执行流程
update T set num=num+1 where id=2;
我们再来看执行器和 InnoDB 引擎在执行这个简单的 update 语句时的内部流程。
- 执行器先找引擎取 id=2 这一行。id 是主键,引擎直接用树搜索找到这一行。如果 id=2 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。
- 执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口写入这行新数据。
- 引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。
- 执行器生成这个操作的 binlog,并把 binlog 写入磁盘。
- 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。
下图是这个 update 语句的执行流程图,图中橙色框表示是在 InnoDB 内部执行的,蓝色框表示是在执行器中执行的
两阶段提交是跨系统维持数据逻辑一致性时常用的一个方案。
2PC(Two Phase Commitment Protocol),即二阶段提交,两个阶段分别为提交请求Prepare(投票)和提交Commit(执行)。
binlog能做崩溃恢复吗?
不能,binlog没有checkpoint机制。
InnoDB设计之初,数据写入的时候,写到内存、redo log、binlog就算结束了。内存的数据有没有落盘主要看redo log有没有日志数据,只要redo log日志数据没有被checkpoint擦完,就代表还有数据在内存里,所以只要发生了崩溃恢复,就会去把redo log的数据先恢复到内存的数据页中。如果只有binlog,我们知道binlog记录的是完整的sql逻辑,无法知道这些日志的数据是在磁盘还是在内存中。
参考文章:
time.geekbang.org/column/arti…
blog.csdn.net/qq_39390545…