MySQL(十九)MySQL事务日志(一)RedoLog
事务的四种特性:原子性
、一致性
、持久性
和隔离性都是基于什么机制实现的?
-
事务的隔离性由
锁机制
实现 -
而事务的
原子性
、一致性
和持久性
则由事务的redo
和undo
日志来实现的-
redo log
是重做日志
,提供再写入操作,恢复提交事务修改的页的操作,保证了事务的持久性
一般情况下,事务中的操作都是先对内存中的数据页进行的操作,只有内存的数据刷回磁盘中才能叫做持久化,这时候redo log就是为了恢复因为宕机导致内存中修改但是没能刷回磁盘的情况
-
undo log
是回滚日志
,回滚行记录到某个特定的版本,用来保证事务的原子性
和一致性
-
undo不是redo的逆过程,两者都可以被视作恢复操作
(redo log
是恢复磁盘记录保证数据持久性,undo log
则是将操作恢复成之前的状态),但是:
redo log
是存储引擎层
生成的日志,记录的是物理级别
上的页修改操作,比如页号xxx,偏移量yyy写入了zzz数据。主要是为了保证数据的可靠性。undo log
也是存储引擎层
生成的日志,但是记录的是逻辑操作
的日志,比如对某一行数据进行了insert操作,则undo log就是与之相反的delete语句,即每个操作的逆操作。主要用于事务的回滚
和一致性非锁定读
(undo log 回滚到某种特定的版本--MVCC
,多版本并发控制
)
1 Redo Log概述
InnoDB存储引擎是以页
为单位来管理存储空间的。在真正访问页面之前,需要把磁盘上的页缓存到内存中的buffer Pool
中才可以访问。之后所有的变更(指事务commit后)必须先更新缓冲池中的数据,然后缓冲池中的脏页会以一定的频率刷入磁盘(Check Point机制
),这么做的目的是优化CPU和磁盘之间IO速度的鸿沟,保证整体的性能不会下降太多。
1.1 为什么需要redo日志
-
一方面,缓冲池可以帮助优化CPU和磁盘之间IO速度的鸿沟,
Check Point机制
可以保证所修改的页面都能刷回磁盘,然而由于Check Point机制
不是每次变更都会触发,而是master线程隔一段时间去处理。所以最坏的情况就是,事务刚提交完写完缓冲池就宕机了,Check Point机制
来不及刷盘操作。 -
另一方面,事务要求应该有
持久性
特性,即一个更新提交完成的事务,即使数据库发生崩溃,这个事务对数据库的更改也不能消失那么,能通过更新一次就刷一次盘的方式来保证一致性吗?显然是不能的:这种简单粗暴的方式存在两个问题:
①修改量和磁盘刷新量不成正比,由于存储引擎是以页为单位来管理存储空间的,因此哪怕每次只是修改了一个页的一条记录的一个字段,也会将整个数据页刷回磁盘
②随机IO速度慢:一个事务会包含很多SQL语句,因此会牵扯到修改很多页面,这些修改的页面可能并不相邻,这也就意味着将某个事务的
buffer pool
中的页面刷回磁盘的时候,需要进行很多的随机IO
-
因此redo日志的思路是:并不需要把所有修改的数据页全部刷回磁盘,只需要记录修改的地方即可。InnoDB引擎采用了
WAL(Write Ahead Logging)
技术,即先写日志再写磁盘,只有写入redo log
成功,才算事务提交成功,当发生数据库宕机且未刷新回磁盘的时候,就可以通过redo log
恢复。
1.2 redo log的优点
- 降低了刷盘频率
- 占用空间小(只需要存储表空间id、页号、偏移量以及需要更新的值)
1.3 redo log的特点
- redo log是顺序写入磁盘的:在执行事务的过程中,每执行一条语句,就可能产生若干条redo日志,这些日志是按照产生的顺序写入磁盘的,也就是顺序IO效率要比随机IO快很多(这里的IO不是指的刷盘IO!!!而是写入内存的缓冲池和写入磁盘redo log的对比)
- 事务执行过程中redo log不断记录:redo log是
存储引擎层
产生的,bin log是数据库层
产生的,因此redo log 会在事务的操作执行期间不断写入日志,bin log则是事务执行完成之后一次性写入(用于主从复制)
1.4 redo log的组成
redo log 可以简单分为一下两个部分:
-
重做日志的缓存(redo log buffer)
:保存在内存中,是易失的。在mysql启动的时候,就向操作系统申请了一大片称为redo log buffer的连续内存
空间,这段内存空间被划分为了连续的redo log block
。一个redo log block
占512字节
的大小。参数设置:innodb_log_buffer_size,默认为15M
mysql> select @@innodb_log_buffer_size -> ; +--------------------------+ | @@innodb_log_buffer_size | +--------------------------+ | 16777216 | +--------------------------+ 1 row in set (0.00 sec)
-
重做日志文件(redo log file):保存在硬盘中,是持久的。如下面的
ib_logfile0
和ib_logfile1
,这两个文件大小是相等的,是因为这个空间是先开辟出来的,防止无线扩大。
2 ? redo log写入的整体流程
- 将数据页从磁盘拷贝到内存中,修改
缓冲区
中的数据的内存拷贝 - 生成一条重做日志,写入
重做日志缓存(redo log buffer)
中,记录的是数据被修改之后的值 - 当事务commit之后,将
重做日志缓存(redo log buffer)
中的内容以追加的方式
刷新到redo log file
- 定期将内存修改的数据刷新到磁盘中
Write Ahead Log(预先日志持久化):在持久化数据页之前,先对日志进行持久化操作
2.1 redo log的刷盘策略
redo log
的刷盘策略关注的是从redo log buffer
写入磁盘的redo log file
的过程(而不是上面的4实际写入磁盘的过程),redo log
的写入并不是直接写入磁盘的,而是InnoDB存储引擎在写redo log的时候先写入redo log buffer
,之后会以一定的频率写入到真正的log filer
中。这里的一定频率就是要说的刷盘策略:
redo log buffer
在写入redo log file
的过程也不是真正直接刷到磁盘中去的,而是先刷入文件系统缓存
(Page Cache)(操作系统为了提高文件写入效率的优化),然后由操作系统来决定什么时候写入redo log file
。这么做存在的问题就是,如果redo log buffer
交给系统来同步,系统一旦宕机数据也就丢失了,虽然系统宕机的概率很小。
针对这种情况,InnoDB给出innodb_flush_log_at_trx_commit
的参数,该参数控制commit提交事务的时候,如何将redo log buffer
中的日志数据刷新到redo log file
。
0
:每次事务提交的时候,不进行刷盘操作(系统默认的master线程
会每隔1s进行一次重做日志的同步操作)1
:每次提交事务都进行同步,刷盘操作(默认值
)2
:每次提交事务都只把redo log buffer
的日志记录放入page cache
中,而不进行刷盘操作,由os来决定什么时候进行。
mysql> select @@innodb_flush_log_at_trx_commit;
+----------------------------------+
| @@innodb_flush_log_at_trx_commit |
+----------------------------------+
| 1 |
+----------------------------------+
1 row in set (0.00 sec)
上面的
同步
指的是后台线程调用操作系统fsync
,将内存中的文件缓存刷新到磁盘中进行同步
InnoDB存储引擎有一个后台master线程
,每隔一秒就会对redo log buffer
进行刷盘同步
操作。因此,一个没提交的事务,也有可能被刷盘,因为事务在执行过程中redo log记录会写入redo log buffer,后台线程会每隔1s对其执行刷盘、同步操作。
所以redo真正的刷盘策略是下面的这样:
流程图
默认情况,Inndb_flush_log_at_trx_commit
为1的情况
只要数据提交成功,redo log的记录就一定在磁盘中,不会有任何数据的丢失;如果没有提交宕机了,则重做日志缓存由于在内存也就消失了,因此不会有任何的问题。
这样可以保证ACID的D,数据绝对不会丢失,但是效率也是最差的。
Inndb_flush_log_at_trx_commit
为2的情况
Inndb_flush_log_at_trx_commit
为2是每次提交事务都写入到page cache里面,如果mysql提交完成事务后宕机,则不会造成数据的丢失,因为日志记录已经写入到了文件系统缓存里面,这部分内容除非os宕机否则不会消失。OS宕机的情况无法满足持久性,但是这种效率也是最高的。
Inndb_flush_log_at_trx_commit
为0的情况
Inndb_flush_log_at_trx_commit
为0是完全由后台线程每隔一秒来负责刷盘,如果mysql宕机则会丢失这1s的数据数值为0的刷盘策略理论上效率高于1但是小于2,这种策略存在风险,无法保证D
2.3 写入redo log buffer过程
mini-transaction
MySQL将对底层页面的一次原子访问
过程称之为一个mini-transaction
,简称为mtr
,比如:向某个索引对应的B+树中添加记录的过程就是一个mtr
,一个mtr可以包含一组redo log日志记录(回想一下一个redo log日志记录的是向某个表的多少偏移量添加修改什么数据),在进行崩溃修复的时候这一组redo log
日志记录就作为一个不可分割的整体。
redo日志写入log buffer
向log buffer
中写入redo日志
的过程是顺序的,也就是先往前面的block
中写,当该block中的空间用完之后就往后面的block
中写,因此block
设置了一个两边buf_free
指示当前block用到了哪里,也就是写入的时候应该从哪里开始。
每一个redo log block
的大小是512kb,包括log block header
、log block body
、log block trailer
。上面说到过,一个mtr可以产生若干条redo日志记录,由于mtr具有原子性,所以产生的几条redo日志记录也是一个不可分割的组,所以并不是每产生一条redo日志记录,就插入到redo buffer中。而是先暂存到一个地方,等到事务执行结束的时候,再将产生的同一组的redo日志记录全部复制到log buffer中。
假设有两个名为T1、T2的事务,每个事务包含两个mtr,命名为:
- 事务T1的两个mtr:
mtr_T1_1
和mtr_T1_2
- 事务T2的两个mtr:
mtr_T2_1
和mtr_T2_2
不同的事务可能是并发
执行的,因此不同事务的mtr
也会交替地写入log buffer
中的(mtr内部的redo log record
必须是相邻的):
2.4 redo log block的结构图
一个redo log block是由日志头
、日志尾
和日志体
组成的。日志头占用12字节,日志尾占用8字节,所以一个blobk真正能够存储的数据就是512-12-8=492字节
为什么一个block要设计成512字节?
因为机械磁盘的扇区就是512字节,如果写入两个扇区,就会导致盘片的转动,比如一个写入成功一个不成功,就不能保证原子性的写入。
2.5 redo log file
相关参数
-
innodb_log_group_home_dir
mysql> show variables like 'innodb_log_group_home_dir'; +---------------------------+-------+ | Variable_name | Value | +---------------------------+-------+ | innodb_log_group_home_dir | ./ | +---------------------------+-------+ 1 row in set (0.01 sec)
-
innodb_files_in_group
mysql> show variables like 'innodb_files_in_group'; Empty set (0.00 sec)
-
innodb_log_file_size
:单个redo log
文件设置大小,最大为512G,是单个文件大小*文件个数mysql> show variables like 'innodb_log_file_size'; +----------------------+----------+ | Variable_name | Value | +----------------------+----------+ | innodb_log_file_size | 50331648 | +----------------------+----------+ 1 row in set (0.00 sec)
一般需要根据业务修改其大小,以便能够容纳更大的事务,编辑my.cnf文件并重启数据库
2.6 日志文件组
-
磁盘中的
redo log
不只有一个,而是以一个日志文件组
的形式出现的,这些文件是以ib_logfile[数字]
的形式命名,每个redo日志的大小是相同的 -
将redo日志记录写入到日志文件组的时候,首先从
ib_logfile0
开始写,然后写满后依次接着ib_logfile1
...直到写满最后一个文件,就重新从ib_logfile0
写
2.7 checkpoint机制
为了防止日志文件组循环写入导致文件覆盖,引入了check poin
机制:
- 记录
write pos
和checkpoint
两个变量:write pos
为当前记录的位置,一边写一边后移checkpoint
是刷盘完要擦除的位置,也是需要往后推移
如上图:
checkpoint
往后到write pos
的1、2文件表示还没有进行刷盘的redo log记录- 0、3文件表示已经刷盘可以写入的文件
- 如果
write pos
追上了checkpoint
,则表明日志文件组已满