redo log
事务在执行的过程中,生成的redo log是要先写到redo log buffer中的。redo log buffer里面的内容不需要每次生成后都直接持久化到磁盘。
如果事务执行期间MySQL发生异常重启,那这部分日志就丢了,但是由于没有commit,所以丢了也不会有损失。
不过,事务没commit时,redo log buffer却有可能持久化到磁盘
redo log可能存在的三个状态
- 存在redo log buffer中,物理是在MySQL进程中
- 写到磁盘(write),但是没有持久化(fsync),物理上是在文件系统的page cache中
- 持久化到磁盘,物理上是在hard disk中
写redo log buffer是很快的,write到page chche也差不多,但是fsync的速度就慢很多了
InnoDB提供了innodb_flush_log_at_trx_commit参数用于控制redo log的写入策略
- 设置为0,每次commit都只把redo log留在redo log buffer
- 设置为1,每次commit都将redo log持久化到hard disk
- 设置为2,每次commit都只把redo log写到page cache
InnoDB有一个后台线程,每间隔1s就会把redo log buffer中的日志,调用write写到page cache,然后调用fsync持久化到hard disk
注意,事务执行中的redo log也是直接写到redo log buffer中的,这些redo log也会被一起持久化到hard disk。也就是说一个没有commit的事务的redo log也是可能已经fsync到hard disk的
除此以外,还有两种场景会让没有commit的事务的redo log fsync到hard disk中
- redo log buffer占用空间即将达到innodb_log_buffer_size一半的时候,后台线程会主动写盘。只是write,没有fsync
- 并行的事务commit时,顺带将这个事务的redo log buffer持久化到hard disk
binlog
事务执行过程中,先把日志写到binlog cache,事务commit时,再把binlog cache写到binlog文件中
一个binlog是不能被拆开的,因此无论事务多大也要一次性写入。这也就涉及到了binlog cache的保存问题
系统给binlog cache分配了一片内存,每个线程一个,参数binlog_cache_size控制单个线程内binlog cache所占内存的大小。如果超过了这个参数,就要暂存到磁盘。
事务commit时,执行器把binlog cache里的完成事务写到binlog中,并情况binlog cache
每个线程有自己的binlog cache,但是共用同一份binlog文件
- write操作是写入page cache,没有把数据持久化到hard disk,速度较快
- fsync操作,将数据持久化到hard disk。占磁盘的IOPS
write和fsync的时机,是由参数sync_binlog控制的
- 设置为0时,每次只write,不sync
- 设置为1时,每次都fsync
- 设置为N时,表示每次都write,累积N个后fsync
因此,在出现IO瓶颈的场景中,将sync_binlog设置为一个比较大的值,一般为100—1000,对应的风险是有可能会丢失最近的N个日志
binlog与redo log的区别
- redo log是InnoDB引擎持有的,binlog是MySQL的Server层实现的,所有引擎都可以使用
- redo log是物理日志,记录的是"在某个数据页上做了什么修改";binlog是逻辑日志,记录的是这个语句的原始逻辑,比如"给ID为2这一行的c字段+1"
- redo log是循环写的,空间固定会用完;binlog是可以追加写入的
有了对这两个日志的概念性理解,执行这个update语句的内部流程
- 执行器先找引擎取ID=2这一行。ID是主键,引擎直接用树搜索到这一行。如果ID=2这一行所有的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。
- 执行器拿到引擎给的行数据,把这个值加上1,得到一行新的数据,再调用引擎接口写入这行新数据。
- 引擎将这行新数据更新到内存中,同时将这个更新操作记录到redo log里面,此时redo log处于prepare状态。然后告知执行器执行完成了,随时可以提交事务。
- 执行器生成这个操作的binlog,并把binlog写入磁盘。
- 执行器调用引擎的提交事务接口,引擎把刚刚写入的redo log改成提交(commit)状态,更新完成。
流程图如下,浅色表示在InnoDB内部执行,深色表示在执行器中执行。
最后三步将redo log的写入拆成了两个步骤:prepare和commit,这就是两阶段提交。