(Multiversion Concurrency Control)html
最近正在啃《高性能MySQL》这本书, 当看到事务相关知识时,决定对该知识点稍微深刻一下, 《高性能MySQL》中在介绍事务相关知识点时, 显然不是特别深刻, 不少比较底层的知识点并无太多的深刻, 固然此处并非要对本书作什么评判,言归正传, 这里主要先说一下本人在啃相关知识点时的曲折之路:mysql
脏读
, 不可重复读
, 幻读
, 更新丢失
等问题以前也有相关笔记;MVCC多版本并发控制
, 而后一发不可收拾了...下面先引用一些前辈们比较优秀的文章:git
阿里数据库内核'2017/12'月报中对MVCC的解释是:
多版本控制: 指的是一种提升并发的技术。最先的数据库系统,只有读读之间能够并发,读写,写读,写写都要阻塞。引入多版本以后,只有写写之间相互阻塞,其余三种操做均可以并行,这样大幅度提升了InnoDB的并发度。在内部实现中,与Postgres在数据行上实现多版本不一样,InnoDB是在undolog中实现的,经过undolog能够找回数据的历史版本。找回的数据历史版本能够提供给用户读(按照隔离级别的定义,有些读请求只能看到比较老的数据版本),也能够在回滚的时候覆盖数据页上的数据。在InnoDB内部中,会记录一个全局的活跃读写事务数组,其主要用来判断事务的可见性。
<高性能MySQL>中对MVCC的部分介绍github
- MySQL的大多数事务型存储引擎实现的其实都不是简单的行级锁。基于提高并发性能的考虑, 它们通常都同时实现了多版本并发控制(MVCC)。不只是MySQL, 包括Oracle,PostgreSQL等其余数据库系统也都实现了MVCC, 但各自的实现机制不尽相同, 由于MVCC没有一个统一的实现标准。
- 能够认为MVCC是行级锁的一个变种, 可是它在不少状况下避免了加锁操做, 所以开销更低。虽然实现机制有所不一样, 但大都实现了非阻塞的读操做,写操做也只锁定必要的行。
- MVCC的实现方式有多种, 典型的有乐观(optimistic)并发控制 和 悲观(pessimistic)并发控制。
- MVCC只在
READ COMMITTED
和REPEATABLE READ
两个隔离级别下工做。其余两个隔离级别够和MVCC不兼容, 由于READ UNCOMMITTED
老是读取最新的数据行, 而不是符合当前事务版本的数据行。而SERIALIZABLE
则会对全部读取的行都加锁。从书中能够了解到:算法
- MVCC是被Mysql中
事务型存储引擎InnoDB
所支持的;- 应对高并发事务, MVCC比
单纯的加锁
更高效;- MVCC只在
READ COMMITTED
和REPEATABLE READ
两个隔离级别下工做;- MVCC可使用
乐观(optimistic)锁
和悲观(pessimistic)锁
来实现;- 各数据库中MVCC实现并不统一
- 可是书中提到 "InnoDB的MVCC是经过在每行记录后面保存两个隐藏的列来实现的"(网上也有不少此类观点), 但其实并不许确, 能够参考MySQL官方文档, 能够看到, InnoDB存储引擎在数据库每行数据的后面添加了三个字段, 不是两个!!
1.read view
, 快照snapshot
sql
淘宝数据库内核月报/2017/10/01/
此文虽然是以PostgreSQL进行的说明, 但并不影响理解, 在"事务快照的实现"该部分有细节须要注意:
事务快照是用来存储数据库的事务运行状况。一个事务快照的建立过程能够归纳为:
查看当前全部的未提交并活跃的事务,存储在数组中
选取未提交并活跃的事务中最小的XID,记录在快照的xmin中
选取全部已提交事务中最大的XID,加1后记录在xmax中
注意: 上文中在PostgreSQL中snapshot的概念, 对应MySQL中, 其实就是你在网上看到的read view
,快照
这些概念;
好比何登成就有关于Read view
的介绍;
而 此文 却还是使用快照
来介绍;数据库
2.read view 主要是用来作可见性判断的, 比较广泛的解释即是"本事务不可见的当前其余活跃事务", 但正是该解释, 可能会形成一节理解上的误区, 因此此处提供两个参考, 供给你们避开理解误区:数组
read view中的`高水位low_limit_id`能够参考 https://github.com/zhangyachen/zhangyachen.github.io/issues/68, https://www.zhihu.com/question/66320138 其实上面第1点中加粗部分也是相关高水位的介绍( 注意进行了+1 )
3.另外, 对于read view快照的生成时机, 也很是关键, 正是由于生成时机的不一样, 形成了RC,RR两种隔离级别的不一样可见性;并发
With REPEATABLE READ isolation level, the snapshot is based on the time when the first read operation is performed. 使用REPEATABLE READ隔离级别,快照是基于执行第一个读操做的时间。 With READ COMMITTED isolation level, the snapshot is reset to the time of each consistent read operation. 使用READ COMMITTED隔离级别,快照被重置为每一个一致的读取操做的时间。
4.undo-log高并发
另外, 在回滚段中的undo logs分为: insert undo log
和 update undo log
5.InnoDB存储引擎在数据库每行数据的后面添加了三个字段
事务ID
(DB_TRX_ID
)字段: 用来标识最近一次对本行记录作修改(insert|update)的事务的标识符, 即最后一次修改(insert|update)本行记录的事务id。回滚指针
(DB_ROLL_PTR
)字段: 指写入回滚段(rollback segment)的 undo log
record (撤销日志记录记录)。undo log
record 包含 '重建该行记录被更新以前内容' 所必须的信息。DB_ROW_ID
字段: 包含一个随着新行插入而单调递增的行ID, 当由innodb自动产生汇集索引时,汇集索引会包括这个行ID的值,不然这个行ID不会出如今任何索引中。6.可见性比较算法(这里每一个比较算法后面的描述是创建在rr级别下,rc级别也是使用该比较算法,此处未作描述)
设要读取的行的最后提交事务id(即当前数据行的稳定事务id)为 trx_id_current
当前新开事务id为 new_id
当前新开事务建立的快照read view
中最先的事务id为up_limit_id
, 最迟的事务id为low_limit_id
(注意这个low_limit_id=未开启的事务id=当前最大事务id+1)
比较:
trx_id_current < up_limit_id
, 这种状况比较好理解, 表示, 新事务在读取该行记录时, 该行记录的稳定事务ID是小于, 系统当前全部活跃的事务, 因此当前行稳定数据对新事务可见, 跳到步骤5.trx_id_current >= trx_id_last
, 这种状况也比较好理解, 表示, 该行记录的稳定事务id是在本次新事务建立以后才开启的, 可是却在本次新事务执行第二个select前就commit了,因此该行记录的当前值不可见, 跳到步骤4。trx_id_current <= trx_id_current <= trx_id_last
, 表示: 该行记录所在事务在本次新事务建立的时候处于活动状态,从up_limit_id到low_limit_id进行遍历,若是trx_id_current等于他们之中的某个事务id的话,那么不可见, 调到步骤4,不然表示可见。trx_id_current
,而后跳到步骤1从新开始判断。1.MySQL的InnoDB存储引擎默认事务隔离级别是RR(可重复读), 是经过 "行排他锁+MVCC" 一块儿实现的, 不只能够保证可重复读, 还能够部分防止幻读, 而非彻底防止;
2.为何是部分防止幻读, 而不是彻底防止?
当前读(current read)
和快照读(snapshot read)
:3.快照读(snapshot read)
简单的select操做(固然不包括 select ... lock in share mode, select ... for update)
4.当前读(current read) 官网文档 Locking Reads
在RR级别下,快照读是经过MVVC(多版本控制)和undo log来实现的,当前读是经过加record lock(记录锁)和gap lock(间隙锁)来实现的。
innodb在快照读的状况下并无真正的避免幻读, 可是在当前读的状况下避免了不可重复读和幻读!!!
通常咱们认为MVCC有下面几个特色:
而InnoDB实现MVCC的方式是:
排他锁定
,若是锁定了还算不算是MVCC?undo log
中的内容只是串行化的结果, 记录了多个事务的过程, 不属于多版本共存。但理想的MVCC是难以实现的, 当事务仅修改一行记录使用理想的MVCC模式是没有问题的, 能够经过比较版本号进行回滚, 但当事务影响到多行数据时, 理想的MVCC就无能为力了。第一类更新丢失
的状况。