一、ChangeBuffer技术
1.1、一般我们执行sql的修改语句时:
- 先把记录加载到内存中(系统调用)
- 然后修改内存中的数据,
- 事务提交后再写回磁盘。(系统调用)
如果数据库数据更新的频率非常低,那么这样更新方式数据库也可以接受,但是在更新非常频繁的情况下,大量的离散IO(系统调用)会成为数据库的瓶颈,影响数据库的性能。
1.2、在更新频繁的场景下,如何降低磁盘的IO并保证事务呢?
这就涉及到ChangeBuffer技术了,在满足ChangeBuffer缓存操作的条件下,InnoDB并不会立即把数据的变更操作写入磁盘,而是将这些对数据页的操作缓存到ChangeBuffer中,数据库找合适的机会再将操作Merge到数据库中。
通过ChangeBuffer技术,我们可以把对数据库的多次离散访问合并为一次数据库访问,并且用户的更新线程中不需要实际访问磁盘,大大提升了数据库性能。
1.3、ChangeBuffer有一个很大的问题:如果InnoDB实例在运行期间掉电,ChangeBuffer中的缓存会丢失,从而造成数据库数据的不一致,影响数据库事务的原子性和一致性。于是就有了WAL技术。
二、WAL技术(Write-ahead logging,预写式日志)
在使用WAL的系统中,所有的修改都先被写入到日志中,然后再被应用到系统状态中,日志通常包含redo
和undo
两部分信息。
redoLog
称为重做日志,每当有操作时,在数据变更之前将操作写入RedoLog,这样当发生掉电之类的情况时系统可以在重启后继续操作;UndoLog
称为撤销日志,当一些变更执行到一半无法完成时,可以根据撤销日志恢复到变更之间的状态;
2.1、redo Log保证持久性(ACID 中的D)
redo Log
如其名侧重于重做!它记录的是数据库中每个页的修改,而不是某一行或某几行修改成怎样,可以用来恢复提交后的物理数据页,且只能恢复到最后一次提交的位置。
redo log用到了WAL(Write-Ahead Logging)技术,这个技术的核心就在于修改记录前,一定要先写日志,并保证日志先落盘,才能算事务提交完成。
有了redo log
再修改数据时,InnoDB引擎会把更新记录先写在redo log中,在修改Buffer Pool中的数据,当提交事务时,调用fsync把redo log刷入磁盘。至于缓存中更新的数据文件何时刷入磁盘,则由后台线程异步处理。
(注意:此时redo log的事务状态是prepare,还未真正提交成功,要等bin log日志写入磁盘完成才会变更为commit,事务才算真正提交完成)
这样一来保证了对内存中数据修改的持久性,因为一旦事务提交了,即使刷脏页之前MySQL意外宕机也没关系,只要在重启时解析redo log中的更改记录进行重放,重新刷盘即可。
思考一个问题:只要每次把修改后的数据页直接刷盘不就好了,为什么还要用redo log刷盘?不都是刷盘吗?有什么区别?
实际上,数据页大小是16KB,刷盘比较耗时,可能就修改了数据页的几byte数据,没有必要把整页的数据刷盘。而且数据页刷盘都是随机写,因为一个数据页对应的位置可能是在硬盘文件的随机位置,所以性能很差。
如果是写redo log,一行记录就占了几十byte,只要包含了表空间号、数据页号、磁盘文件偏移量、修改值,再加上是顺序写,所以刷盘效率很高。
所以用 redo log 形式记录修改内容,性能会远远超过刷数据页的方式,这也让数据库的并发能力更强。
2.2、undo Log保证原子性(ACID 中的A)以及MVCC
想要保证事务的原子性,就需要在发生异常时,对已经执行的操作进行回滚,在MySQL中恢复机制是通过undo log
(回滚日志)实现的,所有事务进行的修改都会先被记录到这个回滚日志,然后再执行其他相关的操作。如果执行过程中遇到异常的话,我们直接利用回滚日志中的信息将数据回滚到修改之前的样子。并且,回滚日志会先于数据持久化到磁盘上。这样就保证了即使遇到数据库突然宕机等情况,当用户再次启动数据库的时候,数据库还能够通过查询回滚日志来回滚将之前未完成的事务。
另外,MVCC的实现依赖:隐藏字段、Read View、undo log。在底层实现中,InnoDB通过数据行的DB_TRX_ID和Read View来判断数据的可见性,如不可见,则通过数据行DB_ROLL_PTR找到undo log中的历史版本。每个事务读到的数据版本可能是不一样的,在同一个事物里,用户只能看到该事务创建Read View之前已经提交的修改和该事务本身做的修改。