引言
说到事务我们最先想到的是事务的隔离特征:ACID,那我就从ACID来讲述事务:
特性 | 实现方式 |
A(原子性) | 通过undolog来实现 |
C(一致性) | 无具体实现方式,靠其他特性共同维持 |
I(隔离性) | MVCC机制 |
D(持久性) | 通过redolog来实现 |
1. 原子性(atomicity)
原子性比较好理解,事务作为最小操作单元,要么都成功要么都失败。
怎么实现了?
最常用的方式就是记录回滚,InnoDB中就是使用undolog进行记录,用于失败回滚,所以undolog又称回滚日志。
1.1 实现流程
我们以update为例,操作数据之前,先将数据备份到undolog,然后进行数据修改,如果出现错误或用户执行了rollback语句,则系统就可以利用undolog中的备份数据恢复到事务开始之前的状态,
需要强调一点很多人说undolog记录了与实际操作语句相反的操作,类似:目前是insert插入操作,则生成一个对应的delete操作,实际不然,它记录的是旧数据,多个版本的旧数据形成版本链,新数据会有回滚指针(roll_ptr)指向它,当需要回滚事务时,直接用undolog旧记录覆盖表中修改过的新记录即可;
如果是insert操作,由于插入之前这条数据都不存在,那么就不会产生undolog记录,此时回滚时如何删除这条记录呢?因为插入操作不会产生undolog旧记录,因此隐藏字段中的roll_ptr=null,因此直接用null覆盖插入的新记录即可,这样也就实现了删除数据的效果.
当进行insert操作的时候,产生的undolog,只在事务回滚的时候需要用到,并且在事务提交之后可以被立刻丢弃
当进行update和delete操作的时候,产生的undolog,不仅仅在事务回滚的时候需要,在快照读的时候也需要,所以不能随便删除,只有在快照读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除;
为啥undolog这么处理主要是因为MVCC中还有对它的使用,我们在后面章节仔细说。
2. 一致性(consistency)
一致性指事务将数据库从一种状态转变为下一种一致的状态,在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。
它没有具体的实现方式,依赖其他特性的实现来保证的。
3.隔离性(isolation)
事务的隔离性要求每个读写事务的对象对其他事务的操作对象能相互分离,即该事务提交前对其他事务都不可见不影响,当然这种不可见不影响也是分级别的,也就是我们常说的隔离级别
隔离级别:
- READ_UNCOMMITTED:读未提交
- READ_COMMITTED :读已提交(RC)
- REPETABLE_READ :重复读(RR)
- SERIALIZABLE :可串行化
层级具有包含关系,每级都会解决上级存在和已解决的问题,越往下性能代价越高;
它们各自解决的问题如下:
隔离级别 | 脏读 | 非重复读 | 幻读(Phantom read) |
READ_UNCOMMITTED | allowed | allowed | allowed |
READ_COMMITTED | prevented | allowed | allowed |
REPETABLE_READ | prevented | prevented | allowed |
SERIALIZABLE | prevented | prevented | prevented |
说明:
脏读:事务可以读取到其他事务未提交的数据
不可重复读:多个事务操作相同数据时,某一事务内对该数据的读取随着其他事务的操作发生改变,也就是说同一事务内对某条数据的多次读取不能保证都一样。
幻读:当事务2插入一行记录,在插入数据的前后,事务1查询了应该包含这个新纪录的数据,查询结果中均无事务2的新增数据,但此时在事务1中执行包含修改删除在内的更新或加锁操作时会用到该数据,没读到,但用到,这时幻读发生了。
InnoDB的默认隔离级别是RR,这也是为啥我们常说Mysql的默认隔离级别是RR。
4. 持久性(Duration)
持久性:事务提交后,对数据的修改是永久性的,要记录到磁盘的,即使系统故障也不会丢失,这一特性InnoDB主要使用其redolog日志文件实现。
redolog称为重做日志,主要记录数据操作,由两部分组成:一是内存中的重做日志缓冲(redolog buffer),其是易失的;二是重做日志文件(redolog file)持久到磁盘,我们后续讲解时一般都指磁盘的部分,redolog是循环顺序写入固定的文件空间,文件空间循环利用,当事务提交数据写入磁盘后所占空间就可被释放,其他事务即可覆盖。
4.1 实现流程
保证持久性的方法简单来说就是数据先写一份redolog日志,当服务故障重启后会根据redolog日志进行数据还原, 我们简单画一下流程图,以写数据为例
绿色部分是在内存操作,红色部分是在磁盘操作。
从图可以看到数据修改之后放在内存形成脏页,异步修改磁盘数据,同步刷盘写redolog,
是不是感觉多此一举,都是要写磁盘干嘛要多这么一步;
这里我们要在插入一个小知识点:磁盘的顺序读和随机读;关键就在这,对于磁盘寻址IO是主要瓶颈,随机读的IO次数不可控,而顺序读就好多了,直接在后追加,效率提升很多;
如果我们每次都直接操作数据,数据的查询更改加上索引的更新时间上不可控,如果中途发生故障,内存中的数据丢失导致数据都找不回来了,而先写redolog ,顺序写盘,能保证数据更高效的落盘,降低突发情况下的数据丢失风险。
在上图中redolog日志在整个操作过程经历了两个状态,中间夹这一个binlog,当redolog处于prepare状态下会写binlog,binlog写入完成redolog状态改成commit,我们称之为的二阶段提交,接下来我们来说一下binlog和二阶段提交具体是做什么的;
首先介绍一下binlog
binlog(二进制日志):记录了对MySQL数据库执行更改的所有操作,主要是数据库主从复制使用,它是server层的日志。
再介绍一下二阶段提交:
二阶段提交就是在图上描述的部分,redolog的状态会经历两次变化,中间有binlog的写入,为啥要这样?我们从binlog和redolog的用途大致就能猜到:为了保障数据能正常恢复,而且主从数据一致,
4.2 一次写入redolog和binlog行不行了?
正常情况肯定没问题,但是故障情况了?
我们做两个假设:
1. 先写入redolog再写入binlog:如果写入redolog后故障导致binlog缺失,这时从服务数据就会缺失,主从不一致。
2.先写入binlog再写入redolog:如果写入binlog后故障导致redolog缺失,此时从服务数据已经添加,但主服务恢复时数据缺失,还是主从不一致。
而使用二次提交就可以避免这个情况:当故障恢复时,我们会先查看redolog状态,当处于prepare状态时,会再查找binlog,如果binlog缺失就把redolog置为失效,反之改成commit状态。
4.3 都是记录操作,为啥我们需要写binlog和redolog两个日志文件?
这里我们需要注意binlog和redolog差别还是大的:
1.binlog 属于server层,用来做主从数据恢复同步的,和用啥存储引擎没有关系,而redolog是InnoDB存储引擎所有。
2. binlog 不能用来做存储引擎数据恢复,它是记录文件,没有提交节点,不知道从哪恢复,而redolog是固定大小的文件,记录的都是还没写盘的数据。