在Innodb 事务详解(1)当中我们介绍了什么是事务,以及事务需要满住什么条件接下来我们来聊一聊inndb是如何实现事务的这些特性的。
在聊事务实现之前我们来先补充一些基础知识。
1、WAL(预写日志)
目前数据的持久化存储都是依靠硬盘来实现的,硬盘的内部结构是这个样子的
它有多个盘面,每个盘面上有一圈一圈的圆形的磁道,磁道被分为多个扇形的扇区。扇区上存储着我们的数据,从扇区读写数据是通过磁头移到到指定的扇区上来操作的。读写数据时首先磁头移动到指定的磁道上,然后等待盘面旋转使得磁头正好位于我们想要操作的扇区上,这个步骤叫做寻址,然后磁头开始对扇区进行读写操作。
磁盘的寻址需要盘面和磁头的机械运动,这个过程相对于磁头的读写数据来说是非常耗时的。这种结构决定了磁盘在随机读写的时候是相当慢的,因为需要多次寻址,而在顺序读写的时候是非常的快的,因为只需要一次寻址操作。
这里有个机械硬盘读写性能测试,顺序读写比随机读写快了90倍。
可是在大多数使用场景下我们都需要对数据进行随机读写的,就拿数据库来说吧,插入的时候为了读得快肯定要进行排序,排序就会发生后写入得数据排在先写入的数据前面的情况,这个时候肯定就有大量的随机写的操作。修改和读取那就更不用说了,肯定有大量的随机操作。
随机操作硬盘太慢了那咋办呢,那就把随机操作转换成连续操作,也就是现在大多数存储系统使用的 WAL技术,原理很简单,在内存当中保存数据的时候只在内存当中进行修改,硬盘上的数据先不动,为了防止内存当中的修改丢失,保存一条日志,日志积累到了一定大小,或者需要的时候顺序写入硬盘。内存当中的数据也不是一条一条存的而是分页来存储的,一页当中有多条数据,写入的时候就整页写,这样就把随机的硬盘操作变成了顺序操作,大大提升了效率。
整体思路有了还是有几个细节上要处理 的问题
① 为了性能考虑 ,日志里面一般不会记录真个内存页的所有的数据,只会记录增量的数据(哪些数据发生了变化)。这样问题就来了,当系统崩溃重启的时候,我们从哪个日志开始来恢复硬盘中的数据,
为了解决这个问题,一般采用的方式是每条日志都给上一个递增的编号,在实际数据页当中也保存一个编号,这个编号和最后一次写入这个数据页的日志编号一样,这样恢复的时候用数据页上的编号和日志上的编号进行对比,就能够从正确的位置。
② 同样也是为了性能考虑,一般内存中存储的数据页要比硬盘的最小写入单元扇区大很多,也就是写入数据页写入硬盘的过程不是原子的。
为了解决这个问题,首先日志的大小需要设置成和扇区的大小一样,保证每条日志的原子性(512字节),一次的变更可以使用多条日志来表示。
在就是内存当中的数据页刷入硬盘的时候 采用双写,每次刷多个数据页的时候 先把这些数据页进行顺序写入一个缓冲区,这个过程是顺序写操作,再把数据页写入实际存储的位置后上去,当页面刷入硬盘失败的时候就可以直接从缓冲区恢复。
原子性的实现
那innodb原子性实现是不是只记录一个日志就行了呢,理论上貌似是可以是实现的,每次操作的时候记录一条日志,需要回滚的时候读取到相应的操作执行相反的操作就行了,可是这样做的性能不高,操作日志即数据库中的redo log是整个库通用的一个,线上的数据库通常一般操作会非常多,这就导致redo log数据量会非常的大,回滚操作的时候去读取数据量这么大的redo log会非常的耗时,而且redo log 记录的是物理日志,记录的是某个内存页发生了什么变化,而回滚是根据操作逻辑来的,是redolog 来记录回滚信息显然是不合适的。 于是在inndb当中专门为回滚设计了一个日志叫做 undo log,这个日志记录的是逻辑日志,当操作修改和新增,删除的时候相当于记录了一个相反的操作,为了提升性能,undo log是按照事务id来存储的,相同事务id的日志存储在相同的槽当中,这样回滚的时候能够快速找到日志,当事务提交的时候也能够快速清理调日志内容。
undo log的特性决定了它会涉及到大量的随机读写操作,直接操作硬盘显然不合适,那么如何保证undo log的持久性呢,不能说故障重启之后undo log就丢了,那没提交的数据咋回滚。
于是innodb给undo log 数据页的操作也添加了redo log 写undo log 之前先顺序写入redo log ,故障恢复时 先通过redo log 恢复undo log 在通过undo log来确定是否需要回滚。
&emps undo log redo log 的存在就确保了inndb的原子性,在事务当中的存在执行的时候 先写undo log (先写入 undo log 数据页的redo log 再 修改undo log内存页),在写实际数据(先写实际数据页的redo log的,在操作内存数据)当事务提交的时候,把redo log全部刷入硬盘就行了。需要回滚的时候根据事务id读取到undo log 根据 undo log 来进行回滚操作 。
持久性的实现
通过 WAL技术(redo log)即实现了持久性,每次操作之前写记录 redo log 事务提交的时候把日志刷入硬盘 实现了持久性。