事务日志
当提交一个事务时,InnoDB首先会把事务写入日志缓冲(Log buffer),日志缓冲把事务刷新到事务日志;然后存储引擎再把事务写入缓冲池(Buffer Pool)。由此可见,InnoDB通过事务日志把随机IO变成顺序IO,这大大的提高了InnoDB写入时的性能。因为把缓冲池的脏页数据刷新到磁盘可能会涉及大量随机IO,这些随机IO会非常慢,通过事务日志,避开随机IO,用顺序IO替代它。
Innodb日志是环行方式写的:当写到日志的尾部,会重新跳转到开头继续写,但不会覆盖还没应用到数据文件的日志记录,因为这样会清理掉已提交事务的唯一持久化记录。日志文件太小,InnoDB将必须做更多的检查点,导致更多的日志写,在日志没有空间继续写入前,必须等待变更被应用到数据文件,写语句可能会被拖累。但日志文件太大,在崩溃恢复时InnoDB可能不得不做大量的工作,增加恢复时间。应该在这之间找到平衡,设置合适的日志大小。
Redo Log
InnoDB在更新数据的时候会采用WAL技术,也就是Write Ahead Logging,这个日志就是Redo Log用来保证数据库宕机后可以通过该文件进行恢复。这个文件一般只会顺序写,只有在数据库启动的时候才会读取Redo Log文件看是否需要进行恢复。该文件记录了对某个数据页的物理操作,例如某个SQL把某一行的某个列的值改为10,对应的Redo Log文件格式可能为:把第5个数据页中偏移量为99的位置写入一个值10。Redo Log不是无限大的,他的大小是可以配置的,并且是循环使用的,例如配置大小为4G,一共4个文件,每个文件1G。
Redo Log中,存储引擎首先会从第一个文件开始顺序写,写到第四个文件后在从第一个文件开始写,类似一个环,用一个后台线程把Redo Log里的数据同步到聚簇索引上的数据页上。首先写入Redo Log的时候不能将没有同步到数据页上的记录覆盖,如果碰到这种情况会停下来先进行数据页同步然后在继续写入Redo Log。另外执行更新操作的时候,会先更新缓冲池里的数据页,然后写入Redo Log,这个时候真正存储数据的地方还没有更新,也就是说这时候缓冲池中的数据页和磁盘不一致,这种数据页称为脏页,当脏页由于内存不足或者其他原因需要丢弃的时候,一定要先将该脏页对应的Redo Log刷新到磁盘里的真实数据页,不然下次查询的时候由于Redo Log没有同步到磁盘,而查询直接通过索引定位到数据页就会查询出脏数据。
更新的时候先从磁盘或者缓冲池中读取对应的数据页,然后对数据页里的数据进行更改并生成Redo Log到对应的缓冲池(redolog buffer)进行缓存,当事务提交的时候将缓存写入到Redo Log的物理磁盘文件上。这里由于操作系统的文件写入InnoDB并没有使用O_DIRECT直接写入到文件,为了保证性能而是先写入操作系统的缓存,之后在进行flush,所以事务提交的时候InnoDB需要在调用一次fsync的系统调用来确保数据落盘。
为了提高性能InnoDB可以通过参数innodb_flush_log_at_trx_commit来控制事务提交时是否强制刷盘。默认为1,事务每次提交都需要调用fsync进行刷盘,0表示事务提交的时候不会调用Redo Log的文件写入,通过后台线程每秒同步一次;2表示事务提交的时候会写入文件但是只保证写入操作系统缓存,不进行fsync操作。Redo Log文件只会顺序写,所以磁盘操作性能不会太慢,所以建议生产环境都设置为1,以防止数据库宕机导致数据丢失。
Undo Log
Undo Log存放在共享表空间中,也就是即使打开了innodb_file_per_table参数,所有的表的Undo Log都存储在同一个文件里。Undo Log是逻辑日志,主要用于进行事务回滚与MVCC操作;譬如执行相反的操作回滚到之前的状态,譬如记录的和原sql相反的sql,例如insert对应delete,delete对应insert,update对应另外一个update。而MVCC是指可重复读,当一个事务需要查询某条记录,而该记录已经被其他事务修改,但该事务还没提交,而当前事务可以通过Undo Log计算到之前的值。
Undo Log也是需要在执行update语句的时候在事务提交前需要写入到文件的。另外Undo Log的写入也会有对应的Redo Log,因为Undo Log也需要持久化,通过WAL可以提高效率。这里可以总结下,在事务提交的时候要保证Redo Log写入到文件里,而这个Redo Log包含 主键索引上的数据页的修改,以及共享表空间的回滚段中Undo Log的插入。另外Undo Log的清理通过一个后台线程定时处理,清理的时候需要判断该Undo Log是否所有的事务都不会用到。
Links