文章目录
- 事务四个特性
-
- 原子性(atomicity)
-
- InnoDB是如何实现原子性的
-
- redo log保证提交时的数据落盘
- undo log保证数据回滚
-
- insert undo log:
- update undo log
- 一致性(consistency)
-
- 实现
- 隔离性(isolation)
-
- 实现
- 事务的隔离级别
-
- 未提交读(read-uncommitted)
- 已提交读(read-committed)
- 可重复读(repeatable-read)
- 串行化(serializable)
- InnoDB是靠ReadView来保证数据的可见性的
- ReadView
-
- ReadView如何解决数据的可见性
- ReadView生成时机
- 持久性(durability)
-
- 实现
- 总结
事务四个特性
原子性(atomicity)
一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
InnoDB是如何实现原子性的
通过redolog的持久化以及undolog回滚来保证原子性
redo log保证提交时的数据落盘
redo log 通过Force log at commit 机制保证事务提交后redo log日志都已经持久化。innodb_flush_log_at_trx_commit需要默认设置为1才可以保证原子性。详细可以参照上一篇博文。
undo log保证数据回滚
undo log 分为两种,一种是insert undo log,一种是update undo log
insert undo log:
是在 insert 操作中产生的 undo log。 因为 insert 操作的记录只对事务本身可见,对于其它事务此记录是不可见的,所以
insert undo log 可以在事务提交后直接删除而不需要进行 purge 操作。
update undo log
是 update 或 delete 操作中产生的 undo log。 因为会对已经存在的记录产生影响,为了提供 MVCC机制,因此
update undo log 不能在事务提交时 就进行删除,而是将事务提交时放到入 history list 上,等待 purge
线程进行最后的删除操作。
示例:
当事务2使用UPDATE语句修改该行数据时,会首先使用排他锁锁定改行,将该行当前的值复制到
undo log中,然后再真正地修改当前行的值,最后填写事务ID,使用回滚指针指向undo log中修
改前的行。
每次对记录进行改动,都会记录一条 undo日志 ,每条 undo日志 也都有一个 roll_pointer 属性
( INSERT 操作对应的 undo日志 没有该属性,因为该记录并没有更早的版本),可以将这些 undo日志
都连起来,对该记录每次更新后,都会将旧值放到一条 undo日志 中,就算是该记录的一个旧版本,随着更新次数
的增多,所有的版本都会被 roll_pointer 属性连接成一个链表,我们把这个链表称之为 版本链 ,版本
链的头节点就是当前记录最新的值。另外,每个版本中还包含生成该版本时对应的事务id。
为了保证事务并发操作时,在写各自的undo log时不产生冲突,InnoDB采用回滚段的方式来维护undo
log的并发写入和持久化。回滚段实际上是一种 Undo 文件组织方式。
对于使用 InnoDB 存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列( row_id 并不是
必要的,我们创建的表中有主键或者非NULL唯一键时都不会包含 row_id 列):
- trx_id :每次对某条聚簇索引记录进行改动时,都会把对应的事务id赋值给 trx_id 隐藏列。
-
roll_pointer :每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到 undo日志 中,然
后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。
一致性(consistency)
事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
实现
过redo log、undo log和Force Log at Commit机制机制来完成
隔离性(isolation)
一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
实现
InnoDB是由多版本控制机制(MVCC)和锁实现。
事务的隔离级别
未提交读(read-uncommitted)
脏读,就是发生在这一隔离级别下
脏读:一个事物读取到其他事物未提交的数据
已提交读(read-committed)
不可重复读,就是发生在这一隔离级别下。
不可重复读:在一个事务中一条记录,因其他事物修改导致两次读取的结果不一样称为不可重复读
可重复读(repeatable-read)
可重复读示意:
幻读就发生在这一隔离级别下
幻读:一个事物因读取到另一个事物insert,delete的数据,导致同一个条件对一张表的两次检索结果不一致。
注意:大多数数据库,幻读是在serializable级别下解决的。而MySql是在repeatable-read解决的,原理就是之前博文中讲到的间隙锁。
串行化(serializable)
该级别禁止并发性操作,默认对所有的操作都加上锁。
InnoDB是靠ReadView来保证数据的可见性的
ReadView
- 对于使用 READ UNCOMMITTED 隔离级别的事务来说,直接读取记录的最新版本就好了。
- 对于使用 SERIALIZABLE 隔离级别的事务来说,使用加锁的方式来访问记录。
- ReadView主要是针对RC与RR级别的。
ReadView如何解决数据的可见性
其核心问题就是需要判断一下版本链中的哪个版本是当前事务可见的。所以设计 InnoDB 的设计
者提出了一个ReadView的概念,这个 ReadView 中主要包含当前系统中还有哪些活跃1的读写事
务,把它们的事务id放到一个列表中,我们把这个列表命名为m_ids。
这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本(版本链中的版本)是否可
见:
-
如果被访问版本的 trx_id 属性值小于 m_ids 列表中最小的事务id,表明生成该版本的事务在生成
ReadView 前已经提交,所以该版本可以被当前事务访问。
-
如果被访问版本的 trx_id 属性值大于 m_ids 列表中最大的事务id,表明生成该版本的事务在生成
ReadView 后才生成,所以该版本不可以被当前事务访问。
-
如果被访问版本的 trx_id 属性值在 m_ids 列表中最大的事务id和最小事务id之间,那就需要判断
一下 trx_id 属性值是不是在 m_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还
是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提
交,该版本可以被访问
那么问题来了,ReadView是什么时候生成的呢
ReadView生成时机
readview只会在select操作时产生
- 在已提交读(read-committed)级别中,每次select都会生成一个readview,这样也很好的说明了,为什么RC级别下无法重复读
- 在 可重复读(repeatable-read)级别中,只有第一次select操作才会生成readview,所以在一个事物中,针对一条记录的可见性是不变的,所以是可以重复读的。
持久性(durability)
持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
实现
同样是过redo log、undo log和Force Log at Commit机制机制来完成
总结
事务的隔离性由多版本控制机制和锁实现,而原子性,持久性和一致性主要是通过redo log、undo
log和Force Log at Commit机制机制来完成的。
redo log用于在崩溃时恢复数据,undo log用于对事务的影响进行撤销,也可以用于多版本控
制。而Force Log at Commit机制保证事务提交后redo log日志都已经持久化。
注意事项:
开启一个事务后,用户可以使用COMMIT来提交,也可以用ROLLBACK来回滚。其中COMMIT或 者ROLLBACK执行成功之后,数据一定是会被全部保存或者全部回滚到最初状态的,这也体现了事务的原子性。但是也会有很多的异常情况,比如说事务执行中途连接断开,或者是执行COMMIT或者ROLLBACK时发生错误,Server Crash(服务器崩溃)等,此时数据库会自动进行回滚或者重启之后进行恢复。
我们再来总结一下数据库事务的整个流程,如下图所示
事务的相关流程说明:
-
事务进行过程中,每次DML sql语句执行,都会记录undo log和redo log,然后更新数据形成脏
页。
-
然后redo log按照时间或者空间等条件进行落盘,undo log和脏页按照checkpoint进行落盘,落
盘后相应的redo log就可以删除了。
-
此时,事务还未COMMIT,如果发生崩溃,则首先检查checkpoint记录,使用相应的redo log进
行数据和undo log的恢复,然后查看undo log的状态发现事务尚未提交,然后就使用undo log进
行事务回滚。
-
事务执行COMMIT操作时,会将本事务相关的所有redo log都进行落盘,只有所有redo log落盘成
功,才算COMMIT成功。然后内存中的数据脏页继续按照checkpoint进行落盘。如果此时发生了
崩溃,则只使用redo log恢复数据。
- 指的是已经开始还没有提交的事物,且事物ID生成的时机并不是在事物begin的时候而是在一个事物中第一次对数据增删改操作。 ↩︎