Read Commited 隔离级别下锁情况(MVCC实现原理)

如何通过单纯加锁实现RC隔离级别的隔离效果?

对InnoDB引擎下的mysql数据库支持行级锁,通过对事务访问时增加排他锁(X锁)可以防止其他事务的访问,只有在该事务锁提交也就是commit后才可以访问,避免脏读产生。但是在多读的场景下,一个事务假如在进行update操作,后面有许多请求都想要单纯进行读操作(普通的SELECT语句),可是因为有锁的存在只能进行等待。该方法在多读的并发环境下效率大大降低。


真实情况下由于InnoDB在RC和RR隔离级别下使用了MVCC机制,实现了一致性非阻塞读,提高了并发读写效率。其实读到的都是事务发生之前最新的快照版本。MVCC只是实现了快照读,对于当前读还是使用的排他锁(文章最后会总结不同索引条件下排他锁的情况)。


MVCC实现原理(仅对于RC和RR两种隔离级别有MVCC)

1.版本链

对于使用InnoDB存储引擎的表来说,它的聚簇索引记录中都会包含两个必要的隐藏列(row_id并不是必要的,如果我们创建的表中有主键或者非null唯一键都不会再创建row_id)

  • 6字节的事务ID(DB_TRX_ID)每次对某条记录进行改动时,会把对应的事务ID值赋给DB_TRX_ID隐藏列(也就是每个行记录上这个字段会记录最近把这个字段修改的事务id)
  • 7字节的回滚指针(DB_ROLL_PTR)每次对某条记录进行改进时,这个隐藏列会存一个指针,可以通过这个指针找到该记录修改前的信息
  • 隐藏ID

2.MVCC实现的依赖:

  • undo log :undo log记录的时数据表记录行的多个版本,也就是事务执行过程的回滚段,其实就是MVCC中一行原始数据的多个版本镜像数据。
  • read view:主要用来判断当前版本数据的可见性
  • 创建readvew的时机:在该隔离级别下(RC),每次发起SELECT都会创建readview;在RR隔离级别下,事务中的第一个SELECT请求才会开始创建readview。正是由于创建readview时机不同,决定了RC隔离级别可以避免脏读,但不能避免不可重复读。

    read view中包含四个重要的内容

    1. m_ids:在生成readview时当前系统系统中活跃的读写事务的事务id列表【当前事务(新建事务)与正在内存中commit的事务不在活跃事务列表中】
    2. min_trx_id:表示在生成readview时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值
    3. max_trx_id:表示生成readview时系统中应该分配给下一个事务的id值
    4. creator_trx_id:表示生成readview的事务的事务id

    需要注意的是:max_trx_id并不是m_ids中的最大值,事务id是递增分配的。比如现在id为1,2,3这三个事务,之后id为3的事务提交了。事务在生成readview时,m_ids就包括1,2,其中min_trx_id的值就是1,max_trx_id的值就是4

3.当前事务读取某个行记录时的具体过程(注意:当前事务与正在内存中commit的事务不在readview活跃列表中):

在innodb中,在RC隔离级别下创建新事务并每次执行SELECT语句后,innodb会将当前系统中活跃事务的列表创建一个副本(read view),副本中保存的是系统当前不应该被本事务看到的其他事务id列表。当前一个用户创建了一个事务,当他想要读取这个行记录的时候,innodb会将这个行记录当前的版本号与readview进行比较,比较过程具体如下:

当有了ReadView后,每次有某条事务想要访问某行记录时,只需要按照下边的步骤判断当前该行记录的版本是否可见
1.如果该行的DB_TRX_ID属性值 < ReadView中min_trx_id的话,表明生成这个版本记录的事务已经在本次事务创建之前就已经提交了,所以该行记录的当前值是可见的。跳到步骤5.
2.如果该行的DB_TRX_ID属性值 >= ReadView中max_trx_id(前面已经提到,这个并不是活跃事务列表中的最大值)的话,表明生成这个版本记录的事务在本次事务readview创建之后才开启,所以当前值不可见,跳到步骤4.
3.如果ReadView中min_trx_id <= DB_TRX_ID属性值 <= ReadView中max_trx_id,那么就需要进行一次判断,如果DB_TRX_ID在事务活跃列表中,不可见,返回4,如果不在事务活跃列表中,说明生成这个版本的事务已经提交了,可以读,返回5.
4.从该行记录的DB_ROLL_PTR指针所指向的回滚段中取出最新的undo-log的版本号,将它赋值该DB_TRX_ID,然后跳到步骤1.
5.将该可见行的值返回。
6.如果被访问的事务id等于readview中的creator_trx_id,意味着当前事务在访问它自己

4.行的更新过程

  1. 初始数据行。F1~F6是字段的名,1~6对应该字段的值。后面三个隐藏字段分别对应该行的事务号和回滚指针,假如这条数据是刚insert的,可以认为ID为1,其余两个字段为空。

  1. Session1更改该行的各个字段的值,会进行如下图所作操作:

    用排他锁(X锁)锁定改行

    记录redo log

    把改行修改前的值Copy到undo log

    修改当前行的值,当前事务编号为01(示例中填写为01),使回滚指针指向undo log中修改前的行

  2. Session2再来修改该行的值,与Session1中的步骤基本相同,但此时undo log中有两行记录,并且通过回滚指针连在一起


5.InnoDB的MVCC多版本并发控制优缺点总结

使用MVCC使得在读取数据的时候,Innodb几乎可以不用获得任何锁,每个查询都通过版本检查,只获得自己需要的数据版本,从而大大提高了系统的并发度。但是这种策略的缺点是每行记录都需要额外的存储空间,更多的检查工作和一个额外的维护工作。

但是Innodb并不是单纯的使用MVCC,只是在读操作时使用MVCC代替读锁,在进行其他操作依旧需要用到排他锁。

当事务仅仅修改一行记录的时候使用MVCC是可以的,但是当涉及多行记录的修改,MVCC就力不从心了。比如事务a执行理想的MVCC,想要修改两行数据,第一行修改成功,但是第二行修改失败,此时需要回滚第一行,但是以为第一行没有加行锁,事务b可能此时就对第二行进行了修改,但若后来事务a进行了回滚,那事务b进行的操作就会遭到破坏

所以Innodb只是借了MVCC这个名字,提供了非阻塞读。在写数据时依旧使用的排他锁(不使用间隙锁,所以依旧可以执行插入操作,所以不能避免幻读),其他事务依旧可以读取上锁的数据是因为读的只是镜像版本而已

 6.RC隔离级别下行级锁的情况

在RC隔离级别下,行级锁并不是直接锁记录,而是会锁索引。索引又分为主键索引和唯一索引和非唯一索引。当sql语句操作主键索引时,mysql会直接在该主键索引上加排他锁;当sql语句操作唯一索引时,mysql会对该唯一索引记录加入排他锁,然后使用该条记录的主键通过主键索引(聚簇索引)在主键上加入排他锁;当sql语句操作非唯一索引,首先会将所有满足where条件的非唯一索引加排他锁,依然会通过聚簇索引对主键加锁;在无索引的情况下,因为无法直接定位到记录,所以直接走聚簇索引,走全表扫描,会将聚簇索引上所有的记录都加入排他锁(注意这里的并不是表锁)。mysql对之后的流程进行了优化,会将所有不满足条件的记录放锁,这样保证了最后只会有满足条件的记录上锁,降低了所冲突发生的概率,但这个流程是不会省略的。

7.RC隔离级别下的半一致性读

这是一种夹在普通读和锁定读之间的一种读取方式。它只在READ COMMITTED隔离级别下(或者在开启了innodb_locks_unsafe_for_binlog系统变量的情况下)使用update语句才会使用。具体的含义就是当UPDATE语句读取已经被其他事务加了锁的记录时,InnoDB会将该记录的最新提交的版本读出来,然后判断该版本是否与update语句中的where条件相匹配,如果不匹配则不对该记录加锁,从而跳到下一条记录;如果匹配则再次读取该记录并对其进行加锁。这样子处理只是为了让update语句尽量少被别的语句阻塞。

原文地址:https://www.cnblogs.com/leeeeemz/p/12586815.html

时间: 2024-10-24 12:28:26

Read Commited 隔离级别下锁情况(MVCC实现原理)的相关文章

RR隔离级别下锁情况(探究gap锁和行锁)

!!!我的数据库演示版本为5.5,以后会追加最新数据库的演示版本 间隙锁(GAP Lock)时InnoDB在可重复读下的隔离级别下为了解决幻读问题引入的锁机制.幻读存在的问题是因为在新增或者更新时如果进行查询,会出现不一致的现象,这时单纯的使用行锁无法满足我们的需求,我们需要对一定范围的数据加锁,防止幻读. 可重复读隔离级别就是数据库通过行锁和间隙锁(不要搞混gap lock和next-key lock)共同组成来实现的,在网上抄了一下加锁的规则,然后自己进行一下验证: 1.加锁的基本单位是(n

mysql 开发进阶篇系列 12 锁问题(隔离级别下锁的差异)

1. innodb在不同隔离级别下的一致性读及锁的差异 不同的隔离级别下,innodb处理sql 时采用的一致性读策略和需要的锁是不同的,同时,数据恢复和复制机制的特点,也对一些sql的一致性读策略和锁策略有很大影响.对于许多sql, 隔离级别越高,innodb给记录集的锁就越严格(龙其是使用范围条件的时候),产生的锁冲突的可能性也就越高,对并发性事务处理性能的影响也就越大.因此,在应用中,应该尽量使用较低的隔离级别,减少锁争用.通常使用Read Commited隔离级别就足够了, 对于一些确实

关于不同隔离级别下锁机制的研究

众所周之,隔离级别分为:       Read Uncommited:可以读取提交的记录      ReadCommitted:仅读到提交的数据,会产生幻读现象     Repeatable Read :对读取到的数据加锁,并对读取的范围加锁,不存在幻读现象     Serializable:读加读锁写加写锁,串行执行   情况一:主键(where  主键=???) Read Committed隔离级别 show create table key_id; +--------+----------

浅析SQL Server在可序列化隔离级别下,防止幻读的范围锁的锁定问题

原文:浅析SQL Server在可序列化隔离级别下,防止幻读的范围锁的锁定问题 本文出处:http://www.cnblogs.com/wy123/p/7501261.html (保留出处并非什么原创作品权利,本人拙作还远远达不到,仅仅是为了链接到原文,因为后续对可能存在的一些错误进行修正或补充,无他) 数据库在处理并发事物的过程中,在不同的隔离级别下有不同的锁表现,在非可序列化隔离级别下,存在着脏读,不可重复读,丢失更新,幻读等情况.本文不讨论脏读和不可重复读以及丢失更新的情形,仅讨论幻读,幻

MySQL事务及实现、隔离级别及锁与优化

事务  事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消.事务是逻辑上的一组操作,要么都执行,要么都不执行. ACID简介 原子性(Atomicity).一致性(Correspondence).隔离性(Isolation).持久性(Durability). (1)原子性:整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节.事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一

Innodb中的事务隔离级别和锁的关系

前言: 我们都知道事务的几种性质,数据库为了维护这些性质,尤其是一致性和隔离性,一般使用加锁这种方式.同时数据库又是个高并发的应用,同一时间会有大量的并发访问,如果加锁过度,会极大的降低并发处理能力.所以对于加锁的处理,可以说就是数据库对于事务处理的精髓所在.这里通过分析MySQL中InnoDB引擎的加锁机制,来抛砖引玉,让读者更好的理解,在事务处理中数据库到底做了什么. #一次封锁or两段锁? 因为有大量的并发访问,为了预防死锁,一般应用中推荐使用一次封锁法,就是在方法的开始阶段,已经预先知道

事务的隔离级别与锁的申请和释放

脏读(Dirty Read)     脏读意味着一个事务读取了另一个事务未提交的数据,而这个数据是有可能回滚 不可重复读(Unrepeatable Read) 不可重复读意味着,在数据库访问中,一个事务范围内两个相同的查询却返回了不同数据.这是由于查询时系统中其他事务修改的提交而引起的. 例如:事务B中对某个查询执行两次,当第一次执行完时,事务A对其数据进行了修改.事务B中再次查询时,数据发生了改变 幻读(phantom read) 幻读,是指当事务不是独立执行时发生的一种现象,例如第一个事务对

Innodb中的事务隔离级别和锁的关系(转)

原文:http://tech.meituan.com/innodb-lock.html 前言: 我们都知道事务的几种性质,数据库为了维护这些性质,尤其是一致性和隔离性,一般使用加锁这种方式.同时数据库又是个高并发的应用,同一时间会有大量的并发访问,如果加锁过度,会极大的降低并发处理能力.所以对于加锁的处理,可以说就是数据库对于事务处理的精髓所在.这里通过分析MySQL中InnoDB引擎的加锁机制,来抛砖引玉,让读者更好的理解,在事务处理中数据库到底做了什么. 一次封锁or两段锁? 因为有大量的并

MySQL InnoDB中的事务隔离级别和锁的关系

前言: 我们都知道事务的几种性质,数据库为了维护这些性质,尤其是一致性和隔离性,一般使用加锁这种方式.同时数据库又是个高并发的应用,同一时间会有大量的并发访问,如果加锁过度,会极大的降低并发处理能力.所以对于加锁的处理,可以说就是数据库对于事务处理的精髓所在.这里通过分析MySQL中InnoDB引擎的加锁机制,来抛砖引玉,让读者更好的理解,在事务处理中数据库到底做了什么. 一.一次封锁or两段锁因为有大量的并发访问,为了预防死锁,一般应用中推荐使用一次封锁法,就是在方法的开始阶段,已经预先知道会