事务
我们知道,事务具有四大特性——ACID
A atomicity 指的是原子性
C consistency 指的是一致性
I isolation 指的是隔离性
D durability 指的是持久性
四大特性的实现原理
原子性
原子性在这指的是整个事务操作,要么同时成功要么同时失败。让它变成一个整体。
同时成功这好办,难办的是同时失败。
想要实现同时失败,就需要恢复之前的数据,如何恢复旧版本数据呢? undolog回滚日志
undolog回滚日志是实现事务原子性的关键。它保存了数据修改之前的历史数据。当我们的事务中有insert操作时,他会反向的记录一个delete,反之亦然。
隔离性
隔离性指的是当由多个活动事务的时候,每一个事务应该是相互独立的整体。也就是说它们互不干扰。
隔离性实现是基于MVCC多版本数据并发控制实现的。
持久性
持久性指的是,当我的事务提交,事务里面涉及修改的数据都会得到持久性。而不会因为某些特殊原因导致我的修改数据没有更新到磁盘里面去。
持久性的实现是由redolog重做日志完成的。
当我们对数据做增删改的时候,会经历这样的过程:
修改缓冲池里面的数据
写入缓冲池的redolog
提交事务
同步更新磁盘中的redolog
等待后台线程刷新脏数据,完成持久化。
一致性
一致性指的是发生增删改操作时,数据库从一个一致性的状态进入另一个一致性状态。
其实上面三个特性就是为了实现一致性。
举例说:
我的钱包里有1000块钱,买了一包烟花掉100块钱。
那么此时的一致性应该是这样:
我的钱包从1000变成了900.
烟店老板的钱包从N 变成了N+100
redolog
redolog是innodb存储引擎提供的一个日志机制,它可以保证事务的持久性特性。
redolog解决什么问题
首先要知道,mysql的存储结构是包含内存结构和硬盘结构。
内存结构中主要的结构就是缓冲池,当我们增删改一条数据的时候,会从缓冲池里面寻找这条数据,如果没有则从硬盘读取。在对数据做修改的时候,如果不是唯一索引条件的修改,那么会先把缓存池里的数据先修改,为了保证性能,修改完之后并不会马上同步到硬盘里。此时存在缓冲池里面这条不一致的数据就是脏数据。
脏数据会等待一段时间由后台线程刷新到磁盘中。
假如没有redolog,在刷新到磁盘的过程中出现问题了,那么就会出现一致性问题。
redolog就是为了解决这个问题。
redolog啥时候用
当我们对数据做修改时,此时就会记录这条修改数据的redolog,此时事务尚未提交,记录的是redolog的内存部分。
当事务提交的时候,便会将内存中的redolog同步到磁盘中去。
搞得这么麻烦,为啥不直接在事务提交的时候将脏页刷新过去?
这是因为数据的修改,写入磁盘顺序是随机的。而写入日志文件,则是顺序写入磁盘,在性能上要远强于直接写入数据。在写入数据之前先写入日志,这个叫做WAL(Write Ahead Logging)
当脏页成功刷盘之后,redolog就可以被销毁了。
redolog会不会有一致性问题
不会
从上面也知道,redolog 是在事务提交的时候,同步写入硬盘。如果此时提交事务这条线程宕机了,那么原事务也会撤销掉。也就是说由于持久化redolog这个动作是同步的,所以不会有一致性问题。
redolog如何保证持久性
如果真的发生宕机,redolog如何保证事务提交之后仍然可以将脏页持久到磁盘。
这个部分,则是由redolog的两阶段提交做保证。
redolog在写入磁盘的时候,并不是写进去就完事的。
它是有两个状态,跟Binlog做配合。
刚刚写入磁盘,此时是repare准备阶段,当Binlog接收到时,此时才是真正的commit状态。
只有commit状态的redolog才会被看作事务真正完成了,如果是prepare状态则代表这条日志还没有被处理完。
undolog
undolog跟redolog不同,它是逻辑日志。当有修改操作发生时,它记录的时与它相反的动作。undolog保证了事务的原子性特性。
undolog解决什么问题
作为回滚日志,它主要解决的就是如何实现事务的同时失败。
undolog用于记录数据被修改前的信息。
当insert的时候,他会记录相反的操作delete;反之亦然。当Update的时候则会记录一条相反的数据。
undolog啥时候用
undolog是在修改数据的时候生成的。
undolog在事务提交之后不会马上删除,因为它还可以用于MVCC。
undolog版本链
insert产生的undolog 只在回滚需要,在事务提交后可被立即删除。
update,delete的时候产生的undolog不仅在回滚是需要,在快照读的时候也需要,不会立即删除。因为它记录了数据之前的版本是什么样子。
当有活动事务的时候,undolog版本链就不能被删除。
不同的事务或者相同事务对同一条记录进行修改,会导致这条记录的undolog生成一条记录版本链表,链表的头部都是最新的旧纪录,链表尾部是最早的旧纪录.
MVCC
基本概念
全称 Multi-Version Concurrency Control,多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突。数据库隔离级别读已提交、可重复读 都是基于MVCC实现的,事务的隔离性也是基于MVCC实现。相对于加锁简单粗暴的方式,它用更好的方式去处理读写冲突,能有效提高数据库并发性能。MVCC的具体实现,还需要依赖于数据库记录中的三个隐式字段、undo log日志、readView。
当前读
当前读读的是最新的数据,在读取的时候给这一行数据加上共享锁,增删改加的是排他锁,它们也是当前读。
例如select ... for share mode, delete ,insert ,update
快照读
简单的select 就是快照读,快照读读取的是记录数据的可见版本。有可能是历史版本,也可能是最新版本。
读取的时候不加锁,非阻塞读。
在RR(可重复读)的隔离级别下,事务中的第一次读是快照读,后续读到的数据都是第一次快照读的版本。所以它可以解决不可重复读。
而RC(读已提交)的隔离级别下,事务中每一次读取数据都是快照读,所以不可以解决不可重复读,可以解决读已提交。
三个隐藏字段
MVCC的实现原理依赖于三个隐藏字段,undolog,readView
三个隐藏字段分别为,事务ID,回滚指针,行ID(有可能不需要)
隐藏字段 | 含义 |
DB_TRXID | 最近修改事务D,记录插入这条记录或最后一次修改该记录的事务D。 |
DB ROLL PTR | 回滚指针,指向这条记录的上一个版本,用于配合undo log,指向上一个版本。 |
DB ROW ID | 隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段。 |
ReadView
readView(读视图)是快照读SQL执行的时候MVCC提取数据的依据,记录并且维护系统当前活跃的事务id
readView包含四个核心字段
字段 | 含义 |
m_ ids | 当前活跃的事务D集合 |
min_ trx id | 最小活跃事务ID |
max_ trx id | 预分配事务D 当前最大事务ID+1(因为事务D是自增的) |
creator trx_ id | ReadView创建者的事务D |
MVCC的实现原理
前面提到,MVCC的实现依赖于三个隐藏字段+undolog+ReadView
那么具体是怎么利用他们完成多版本并发控制的呢?
RreadView版本链数据的访问规则 + 不同隔离级别下快照读的时机
readView规定了版本链数据的访问规则,这套规则就是实现MVCC的关键。
条件 | 是否可以访问 | 说明 |
trx_id == creator_trx_id | 可以访问该版本 | 成立,说明数据是当前这个事 务更改的。 |
trx_id< min_trx_id | 可以访问该版本 | 成立,说明数据已经提交了。 |
trx_id> max_trx_id | 不可以访问该版本 | 成立,说明该事务是在 ReadView生成后才开启。 |
min_trx_id<= trx_id <= max_trx_id | 如果trx_id不在m_ids中, 是可以访问该版本的 | 成立,说明数据已经提交。 |
简单来说,这套规则会让那些可以被访问的数据是别的事务已经提交的数据。
同时,不同的隔离级别,生成readView的时机也是不同的
READ COMMITTED :在事务中每一次执行快照读时生成ReadView。
REPEATABLE READ:仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。