Reference: https://www.imooc.com/article/17290mysql
能够很负责人的跟你们说,MySQL 中的此隔离级别不仅仅是经过加锁实现的,实际上还有repeatable read 隔离级别,其实这两个隔离级别效果的实现还须要一个辅助,这个辅助就是MVCC-多版本并发控制,但其实它又不是严格意义上的多版本并发控制,是否是很懵,不要紧,咱们一一剖析。算法
目录sql
1.单纯加锁是怎么实现 read committed 的?
2.真实的演示状况是什么样子的?
3.MVCC 实现原理?
4.对于InnoDB MVCC 实现原理的反思数据库
1.单纯加锁是怎么实现 read committed 的?并发
今后隔离级别效果入手:事务只能读其余事务已提交的的记录。
数据库事务隔离级别的实现,InnoDB 支持行级锁,写时加的是行级排他锁(X lock),那么当其余事务访问另外一个事务正在update (除select操做外其余操做本质上都是写操做)的同一条记录时,事务的读操做会被阻塞。因此只能等到记录(实际上是索引上的锁)上的排他锁释放后才能进行访问,也就是事务提交的时候。这样确实能实现read commited隔离级别效果。
数据库这样作确实能够实现 事务只能读其余事务已提交的的记录 的效果,可是这是很低效的一种作法,为何呢?由于对于大部分应用来讲,读操做是多于写操做的,当写操做加锁时,那么读操做所有被阻塞,这样会致使应用的相应能力受数据库的牵制。分布式
2.真实的演示状况是什么样子的?函数
看以下操做:
1.开启两个客户端实例,设置事务隔离级别为read committed,并各自开启事务。性能
set transaction isolation level read committed; set autocommit = 0; begin;
2.客户端1作更新操做:测试
update test set name = '测试' where id =32;
结果以下图所示:3d
3.客户端2作查询操做:
select name from test where id = 32;
结果以下所示:
这时估计你有疑问了,正在 被客户端1 upate 的记录,客户端2还能无阻塞的读到,并且读到的是未更改以前的数据。
那就是 InnoDB 的辅助打得好,由于内部使用了 MVCC 机制,实现了一致性非阻塞读,大大提升了并发读写效率,写不影响读,且读到的事记录的镜像版本。
下面开始介绍 MVCC 原理。
3.MVCC 实现原理
网上对 MVCC 实现原理 的讲述五花八门,参差不齐。
包括《高性能MySQL》对 MVCC 的讲解只是停留在表象,并无结合源码去分析。固然绝大多数人仍是相信这本书的,历来没有进行深剖,思考。
以下是 《高性能MySQL》对 MVCC实现原理 的描述:
"InnoDB的 MVCC ,是经过在每行记录的后面保存两个隐藏的列来实现的。这两个列, 一个保存了行的建立时间,一个保存了行的过时时间, 固然存储的并非实际的时间值,而是系统版本号。"
就是这本书,蒙蔽了真理,贻害不浅。
咱们仍是看源码吧:
1.记录的隐藏列
其实有三列
在Mysql中MVCC是在Innodb存储引擎中获得支持的,Innodb为每行记录都实现了三个隐藏字段: 6字节的事务ID(DB_TRX_ID) 7字节的回滚指针(DB_ROLL_PTR) 隐藏的ID 6字节的事物ID用来标识该行所述的事务,7字节的回滚指针须要了解下Innodb的事务模型。
2.MVCC 实现的依赖项
MVCC 在mysql 中的实现依赖的是 undo log 与 read view。
1.undo log: undo log中记录的是数据表记录行的多个版本,也就是事务执行过程当中的回滚段,其实就是MVCC 中的一行原始数据的多个版本镜像数据。 2.read view: 主要用来判断当前版本数据的可见性。
3.undo log
undo log是为回滚而用,具体内容就是copy事务前的数据库内容(行)到undo buffer,在适合的时间把undo buffer中的内容刷新到磁盘。undo buffer与redo buffer同样,也是环形缓冲,但当缓冲满的时候,undo buffer中的内容会也会被刷新到磁盘;与redo log不一样的是,磁盘上不存在单独的undo log文件,全部的undo log均存放在主ibd数据文件中(表空间),即便客户端设置了每表一个数据文件也是如此。
咱们经过行的更新过程来看下undo log 是如何造成的?
3.1 行的更新过程
下面演示下事务对某行记录的更新过程:
4.read view 判断当前版本数据项是否可见
在innodb中,建立一个新事务的时候,innodb会将当前系统中的活跃事务列表(trx_sys->trx_list)建立一个副本(read view),副本中保存的是系统当前不该该被本事务看到的其余事务id列表。当用户在这个事务中要读取该行记录的时候,innodb会将该行当前的版本号与该read view进行比较。
具体的算法以下:
- 设该行的当前事务id为trx_id_0,read view中最先的事务id为trx_id_1, 最迟的事务id为trx_id_2。
- 若是trx_id_0< trx_id_1的话,那么代表该行记录所在的事务已经在本次新事务建立以前就提交了,因此该行记录的当前值是可见的。跳到步骤6.
- 若是trx_id_0>trx_id_2的话,那么代表该行记录所在的事务在本次新事务建立以后才开启,因此该行记录的当前值不可见.跳到步骤5。
- 若是trx_id_1<=trx_id_0<=trx_id_2, 那么代表该行记录所在事务在本次新事务建立的时候处于活动状态,从trx_id_1到trx_id_2进行遍历,若是trx_id_0等于他们之中的某个事务id的话,那么不可见。跳到步骤5.
- 从该行记录的DB_ROLL_PTR指针所指向的回滚段中取出最新的undo-log的版本号,将它赋值该trx_id_0,而后跳到步骤2.
- 将该可见行的值返回。
须要注意的是,新建事务(当前事务)与正在内存中commit 的事务不在活跃事务链表中。
对应代码以下:
函数:read_view_sees_trx_id。
read_view中保存了当前全局的事务的范围:
【low_limit_id, up_limit_id】
1. 当行记录的事务ID小于当前系统的最小活动id,就是可见的。
if (trx_id < view->up_limit_id) {
return(TRUE);
}
2. 当行记录的事务ID大于当前系统的最大活动id,就是不可见的。
if (trx_id >= view->low_limit_id) {
return(FALSE);
}
3. 当行记录的事务ID在活动范围之中时,判断是否在活动链表中,若是在就不可见,若是不在就是可见的。
for (i = 0; i < n_ids; i++) {
trx_id_t view_trx_id
= read_view_get_nth_trx_id(view, n_ids - i - 1);
if (trx_id <= view_trx_id) {
return(trx_id != view_trx_id);
}
}
5 事务隔离级别的影响
可是:对于两张不一样的事务隔离级别
tx_isolation='READ-COMMITTED': 语句级别的一致性:只要当前语句执行前已经提交的数据都是可见的。
tx_isolation='REPEATABLE-READ'; 语句级别的一致性:只要是当前事务执行前已经提交的数据都是可见的。
针对这两张事务的隔离级别,使用相同的可见性判断逻辑是如何作到不一样的可见性的呢?
6.不一样隔离级别下read view的生成原则
这里就要看看read_view的生成机制:
1. read-commited:
函数:ha_innobase::external_lock
if (trx->isolation_level <= TRX_ISO_READ_COMMITTED
&& trx->global_read_view) {
/ At low transaction isolation levels we let
each consistent read set its own snapshot /
read_view_close_for_mysql(trx);
即:在每次语句执行的过程当中,都关闭read_view, 从新在row_search_for_mysql函数中建立当前的一份read_view。
这样就能够根据当前的全局事务链表建立read_view的事务区间,实现read committed隔离级别。
2. repeatable read:
在repeatable read的隔离级别下,建立事务trx结构的时候,就生成了当前的global read view。
使用trx_assign_read_view函数建立,一直维持到事务结束,这样就实现了repeatable read隔离级别。
正是由于6中的read view 生成原则,致使在不一样隔离级别()下,read committed 老是读最新一份快照数据,而repeatable read 读事务开始时的行数据版本。
4.InnoDB MVCC 实现原理的深入反思
上述更新前创建undo log,根据各类策略读取时非阻塞就是MVCC,undo log中的行就是MVCC中的多版本,这个可能与咱们所理解的MVCC有较大的出入。
通常咱们认为MVCC有下面几个特色:
每行数据都存在一个版本,每次数据更新时都更新该版本
修改时Copy出当前版本随意修改,个事务之间无干扰
保存时比较版本号,若是成功(commit),则覆盖原记录;失败则放弃copy(rollback)
就是每行都有版本号,保存时根据版本号决定是否成功,听起来含有乐观锁的味道。。。,而
Innodb的实现方式是:
事务以排他锁的形式修改原始数据
把修改前的数据存放于undo log,经过回滚指针与主数据关联
修改为功(commit)啥都不作,失败则恢复undo log中的数据(rollback)
两者最本质的区别是,当修改数据时是否要排他锁定,若是锁定了还算不算是MVCC?
Innodb的实现真算不上MVCC,由于并无实现核心的多版本共存,undo
log中的内容只是串行化的结果,记录了多个事务的过程,不属于多版本共存。但理想的MVCC是难以实现的,当事务仅修改一行记录使用理想的MVCC模式
是没有问题的,能够经过比较版本号进行回滚;但当事务影响到多行数据时,理想的MVCC据无能为力了。好比,若是Transaciton1执行理想的MVCC,修改Row1成功,而修改Row2失败,此时须要回滚Row1,但由于Row1没有被
锁定,其数据可能又被Transaction2所修改,若是此时回滚Row1的内容,则会破坏Transaction2的修改结果,致使
Transaction2违反ACID。理想MVCC难以实现的根本缘由在于企图经过乐观锁代替二段提交。修改两行数据,但为了保证其一致性,与修改两个分布式系统中的数据并没有区别,
而二提交是目前这种场景保证一致性的惟一手段。二段提交的本质是锁定,乐观锁的本质是消除锁定,两者矛盾,故理想的MVCC难以真正在实际中被应
用,Innodb只是借了MVCC这个名字,提供了读的非阻塞而已。
做者:mark_fork连接:https://www.imooc.com/article/17290来源:慕课网