一文搞懂MySQL中的事务和锁机制

事务

  • 基本概念

    • 一组逻辑操作单元,使数据从一种状态变换到另一种状态。
    • 事务处理的原则:保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交( commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚( rollback )到最初状态。
  • ACID

    • 原子性
      • 原子性是指事务是一个不可分割的工作单位,要么全部提交,要么全部失败回滚。即要么转账成功,要么转账失败,是不存在中间的状态。如果无法保证原子性会怎么样?就会出现数据不一致的情形,A账户减去100元,而B账户增加100元操作失败,系统将无故丢失100元。
    • 一致性
      • 根据定义,一致性是指事务执行前后,数据从一个合法性状态变换到另外一个合法性状态。这种状态是语义上的而不是语法上的,跟具体的业务有关。
      • 那什么是合法的数据状态呢?满足预定的约束的状态就叫做合法的状态。通俗一点,这状态是由你自己来定义的(比如满足现实世界中的约束)。满足这个状态,数据就是一致的,不满足这个状态,数据就是不一致的!如果事务中的某个操作失败了,系统就会自动撤销当前正在执行的事务,返回到事务操作之前的状态。
      • 一致性是应用程序的属性,而其他三者是数据库的属性。应用可能依赖原子性和隔离性来保证一致性,但有时候一致性的保证并不仅仅取决于数据库。一致性是 对数据的一组特定约束必须始终成立
    • 隔离性
      • 事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
    • 持久性
      • 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。|
      • 持久性是通过事务日志来保证的。日志包括了重做日志和回滚日志。当我们通过事务对数据进行修改的时候,首先会将数据库的变化信息记录到重做日志中,然后再对数据库中对应的行进行修改。这样做的好处是,即使数据库系统崩溃,数据库重启后也能找到没有更新到数据库系统中的重做日志,重新执行,从而使事务具有持久性。
    • 原子性和持久性通过redo log和undo log保证
    • 一致性和隔离性通过MVCC保证
  • 事务的状态

    • 活动(active)
      • 事务对应的数据库操作正在执行过程中时,我们就说该事务处在活动的状态。·
    • 部分提交(partially committed)
      • 当事务中的最后一个操作执行完成,但由于操作都在内存中执行,所造成的影响并没有刷新到磁盘时,我们就说该事务处在部分提交的状态。
    • 失败(failed)
      • 当事务处在活动的或者部分提交的状态时,可能遇到了某些错误(数据库自身的错误、操作系统错误或者直接断电等)而无法继续执行,或者人为的停止当前事务的执行,我们就说该事务处在失败的状态。
    • 中止(aborted)
      • 如果事务执行了一部分而变为失败的状态,那么就需要把已经修改的事务中的操作还原到事务执行前的状态。换句话说,就是要撤销失败事务对当前数据库造成的影响。我们把这个撤销的过程称之为回滚。当回滚操作执行完毕时,也就是数据库恢复到了执行事务之前的状态,我们就说该事务处在了中止的状态。
    • 提交(committed)
      • 当一个处在部分提交的状态的事务将修改过的数据都同步到磁盘上之后,我们就可以说该事务处在了提交的状态。
  • 显式事务

    • START TRANSACTION 或者 BEGIN,作用是显式开启一个事务。
      • START TRANSACTION语句相较于BEGIN特别之处在于,后边能跟随几个修饰符:
    • READ ONLY∶标识当前事务是一个只读事务,也就是属于该事务的数据库操作只能读取数据,而不能修改数据。
      • 只读事务中只是不允许修改那些其他事务也能访问到的表中的数据,对于临时表来说(我们使用CREATE TMEPORARY TABLE创建的表),由于它们只能在当前会话中可见,所以只读事务其实也是可以对临时表进行增、删、改操作的。
    • READ WRITE:标识当前事务是一个读写事务,也就是属于该事务的数据库操作既可以读取数据,也可以修改数据。
    • WITH CONSISTENTSNAPSHOT:启动一致性读。
  • 隐式事务

    • 默认情况下,如果我们不显式的使用START TRANSACTION或者BEGIN语句开启一个事务,那么每一条语句都算是一个独立的事务,这种特性称之为事务的自动提交。
  • 数据并发问题

    • 脏写
      • 对于两个事务Session A、Session B,如果事务Session A修改了另一个未提交事务SsessionB修改过的数据,那就意味着发生了脏写。
    • 脏读
      • 对于两个事务Session A、Session B,Session A读取了已经被Session B更新但还没有被提交的字段。之后若Session B回滚,SessionA读取的内容就是临时且无效的。
    • 不可重复度
      • 对于两个事务Session A、Session B,Session A 读取了一个字段,然后SessionB更新了该字段。之后Session A再次读取同一个字段,值就不同了。那就意味着发生了不可重复读。
    • 幻读
      • 对于两个事务Session A、Session B,Session A从一个表中读取了一个字段,然后SessionB在该表中插入了一些新的行。之后,如果Session A再次读取同一个表,就会多出几行。那就意味着发生了幻读。
  • 事务隔离级别

    • 读未提交(READ UNCOMMITTED)
      • 在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。不能避免脏读、不可重复读、幻读。
    • 读已提交(READ COMMITTED)
      • 它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这是大多数数据库系统的默认隔离级别(但不是MysQL默认的)。可以避免脏读,但不可重复读、幻读问题仍然存在。
    • 可重复读(REPEATABLE READ)
      • 事务A在读到一条数据之后,此时事务B对该数据进行了修改并提交,那么事务A再读该数据,读到的还是原来的内容。可以避免脏读、不可重复读,但幻读问题仍然存在。这是MysQL的默认隔离级别。
    • 序列化(SERIALIZABLE)
      • 确保事务可以从一个表中读取相同的行。在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作。所有的并发问题都可以避免,但性能十分低下。能避免脏读、不可重复读和幻读。
  • 事务日志

    • redo log(WAL)

      • 重做日志,提供再写入操作,恢复提交事务修改的页操作用来保证事务的持久性。
      • 存储引擎层(innodb)生成的日志,记录的是"物理级别"上的页修改操作,比如页号xx、偏移量yy写入了'zzz'数据。主要为了保证数据的可靠性。
      • 优点
        • 降低了刷盘频率
          • 没必要每次事务提交都刷新到磁盘,先记录到redo log中
        • 占用空间非常小
        • 顺序写磁盘
      • 组成
        • 重做日志的缓冲(redo log buffer)
          • 保存在内存中,是易失的。在服务器启动时就向操作系统申请了一大片称之为redo log buffer的连续内存空间,,翻译成中文就是redo日志缓冲区。这片内存空间被划分成若干个连续的redo log block。一个redo log block占用512字节大小。
          • 默认大小16M,最大值4096M,最小值1M。
        • 重做日志文件( redo log file)
          • 保存在磁盘中,是持久的。
      • 流程
        • 先将原始数据从磁盘中读入内存中来,修改数据的内存拷贝
        • 生成一条重做日志并写入redo log buffer记录的是数据被修改后的值
        • 当事务commit时,将redo log buffer中的内容刷新到redo log file,对 redo log file采用追加写的方式
        • 定期将内存中修改的数据刷新到磁盘中
      • 刷盘策略
        • InnoDB给出innodb_flush_log_at_trx_commit参数,该参数控制commit提交事务时,如何将redo log buffer中的日志刷新到redo log file中。它支持三种策略:
          • 设置为0︰表示每次事务提交时不进行刷盘操作。(系统默认master thread每隔1s进行一次重做日志的同步)·
          • 设置为1:表示每次事务提交时都将进行同步,刷盘操作(默认值)
          • 设置为2︰表示每次事务提交时都只把redo log buffer内容写入page cache,不进行同步。由os自己决定什么时候同步到磁盘文件。
        • 另外,InnoDB存储引擎有一个后台线程,每隔1秒,就会把 redo log buffer 中的内容写到文件系统缓存( page cache ) ,然后调用刷盘操作。
    • undo log

      • 回滚日志,回滚行记录到某个特定版本,用来保证事务的原子性、一致性。
      • 是存储引擎层(innodb)生成的日志,记录的是逻辑操作日志,比如对某一行数据进行了INSERT语句操作,那么undo log就记录一条与之相反的DELETE操作。主要用于事务的回滚(undo log记录的是每个修改操作的逆操作)和一致性非锁定读(undo log回滚行记录到某种特定的版本---MVCC,即多版本并发控制)。
      • 作用
        • 数据回滚
          • undo log并非物理的将数据库恢复到之前的样子,只是将数据库逻辑的恢复,通过执行SQL语句的方式。
        • MVCC
      • 结构
        • InnoDB对undo log的管理采用段的方式,也就是回滚段(rollback segment)。每个回滚段记录了1024个undo log segment,而在每个undo log segment段中进行undo页的申请。
        • 支持最大128个回滚段,故同时支持的事务提高到了128 * 1024
        • undo页
          • 当我们开启一个事务需要写undo log的时候,就得先去undo log segment中去找到一个空闲的位置,当有空位的时候,就去申请undo页,在这个申请到的undo页中进行undo log的写入。
          • undo页可以重用,当事务提交时,并不会立刻删除undo页。因为重用,所以这个undo页可能混杂着其他事务的undo log。undo log在commit后,会被放到一个链表中,然后判断undo页的使用空间是否小于3/4,如果小于3/4的话,则表示当前的undo页可以被重用,那么它就不会被回收,其他事务的undo log可以记录在当前undo页的后面。由于undo log是离散的,所以清理对应的磁盘空间时,效率不高。
        • 回滚段与事务
          • 每个事务只会使用一个回滚段,一个回滚段在同一时刻可能会服务于多个事务。
          • 当一个事务开始的时候,会指定一个回滚段,在事务进行的过程中,当数据被修改时,原始的数据会被复制到回滚段。
          • 在回滚段中,事务会不断填充盘区,直到事务结束或所有的空间被用完。如果当前的盘区不够用,事务会在段中请求扩展下一个盘区,如果所有已分配的盘区都被用完,事务会覆盖最初的盘区或者在回滚段允许的情况下扩展新的盘区来使用。
          • 回滚段存在于undo表空间中,在数据库中可以存在多个undo表空间,但同一时刻只能使用一个undo表空间。
          • 当事务提交时,InnoDB存储引擎会做以下两件事情:
            • 将undo log放入列表中,以供之后的purge操作
            • 判断undo log所在的页是否可以重用,若可以分配给下个事务使用
        • 分类
          • insert undo log
            • insert undo log是指在insert操作中产生的undo log,因为insert操作的记录,只对事务本身可见,对其他事务不可见(这是事务隔离性的要求),故该undo log可以在事务提交后直接删除。不需要进行purge操作。
          • update undo log
            • update undo log记录的是对delete和update操作产生的undo log,该undo log可能需要提供MVCC机制,因此不能在事务提交时就进行删除。提交时放入undo log链表,等待purge线程进行最后的删除。
          • purge线程
            • 主要作用是清理undo页和清除page里面带有Delete_Bit标识的数据行。在InnoDB中,事务中的Delete操作实际上并不是真正的删除掉数据行,而是一种Delete Mark操作,在记录上标识Delete_Bit,而不删除记录。是一种"假删除",只是做了个标记,真正的删除工作需要后台purge线程去完成。

  • 数据操作类型划分

    • 读锁
      • 也称为共享锁、英文用S表示。针对同一份数据,多个事务的读操作可以同时进行而不会互相影响,相互不阻塞的。
      • 在普通的SELECT语句后边加LOCK IN SHARE MODE,如果当前事务执行了该语句,那么它会为读取到的记录加s锁,这样允许别的事务继续获取这些记录的s锁(比方说别的事务也使用SELECT ... LOCK IN SHARE MODE语句来读取这些记录),但是不能获取这些记录的X锁(比如使用SELECT ... FOR UPDATE语句来读取这些记录,或者直接修改这些记录)。如果别的事务想要获取这些记录的X锁,那么它们会阻塞,直到当前事务提交之后将这些记录上的S锁释放掉。
    • 写锁
      • 也称为排他锁、英文用X表示。当前写操作没有完成前,它会阻断其他写锁和读锁。这样就能确保在给定的时间里,只有一个事务能执行写入,并防止其他用户读取正在写入的同一资源。
      • 在普通的SELECT语句后边加FOR UPDATE,如果当前事务执行了该语句,那么它会为读取到的记录加X锁。这样既不允许别的事务获取这些记录的S锁(比方说别的事务使用SELECT ... LOCK IN SHARE MODE语句来读取这些记录)也不允许获取这些记录的(比如使用SELECT ... FOR UPDATE语句来读取这些记录,或者直接修改这些记录)。[如果别的事务想要获取这些记录的S锁或者X锁,那么它们会阻塞,直到当前事务提交之后将这些记录上的X锁释放掉。
      • 如果查询条件用了索引/主键,那么select ..... for update就会进行行锁。
      • 如果是普通字段(没有索引/主键),那么select ..... for update就会进行锁表。
  • 数据操作粒度划分

    • 表锁
      • 该锁会锁定整张表,它是 MySQL中最基本的锁策略,并不依赖于存储引擎管你是MySQL的什么存储引擎,对于表锁的策略都是一样的),并且表锁是开销最小的策略(因为粒度比较大)。由于表级锁一次会将整个表锁定,所以可以很好的避免死锁问题。当然,锁的粒度大所带来最大的负面影响就是出现锁资源争用的概率也会最高,导致并发率大打折扣。
      • 意向锁
        • 现在有两个事务,分别是T1和T2,其中T2试图在该表级别上应用共享或排它锁,如果没有意向锁存在,那么T2就需要去检查各个页或行是否存在锁,如果存在意向锁,那么此时就会受到由T1控制的表级别意向锁的阻塞。T2在锁定该表前不必检查各个页或行锁,而只需检查表上的意向锁。简单来说就是给更大一级别的空间示意里面是否已经上过锁。
        • 在数据表的场景中,如果我们给某一行数据加上了排它锁,数据库会自动给更大一级的空间,比如数据页或数据表加上意向锁,告诉其他人这个数据页或数据表已经有人上过排它锁了,这样当其他人想要获取数据表排它锁的时候,只需要了解是否有人已经获取了这个数据表的意向排他锁即可。
        • 如果事务想要获得数据表中某些记录的共享锁,就需要在数据表上添加意向共享锁。·
        • 如果事务想要获得数据表中某些记录的排他锁,就需要在数据表上添加意向排他锁。
      • 自增锁
      • MDL锁
        • 属于表锁范畴。MDL的作用是,保证读写的正确性。比如,如果一个查询正在遍历一个表中的数据,而执行期间另一个线程对这个表结构做变更,增加了一列,那么查询线程拿到的结果跟表结构对不上,肯定是不行的。
        • 因此,当对一个表做增删改查操作的时候,加MDL读锁;当要对表做结构变更操作的时候,加MDL写锁。
        • 读锁之间不互斥,因此你可以有多个线程同时对一张表增删改查。读写锁之间、写锁之间是互斥的,用来保证变更表结构操作的安全性,解决了DML和DDL操作之间的一致性问题。不需要显式使用,在访问一个表的时候会被自动加上。
    • 行锁
      • 也称为记录锁,顾名思义,就是锁住某一行(某条记录row)。需要的注意的是,MySQL服务器层并没有实现行锁机制,行级锁只在存储引擎层实现。
      • 间隙锁
        • 图中值为8的记录加了gap锁,意味着不允许别的事务在id值为8的记录前边的间隙插入新记录,其实就是id列的值(3,8)这个区间的新记录是不允许立即插入的。比如,有另外一个事务再想插入一条id值为4的新记录,它定位到该条新记录的下一条记录的id值为8,而这条记录上又有一个gap锁,所以就会阻塞插入操作,直到拥有这个gap锁的事务提交了之后,id列的值在区间(3,8)中的新记录才可以被插入。
        • 给一条记录加了gap锁只是不允许其他事务往这条记录之前插入新纪录,如果是找最大的一条记录则会在最大记录之后所有都不能插入
      • 临键锁
        • next-key锁的本质就是一个记录锁和一个gap锁的合体,它既能保护该条记录,又能阻止别的事务将新记录插入被保护记录前边的间障。
      • 插入意向锁
        • 我们说一个事务在插入一条记录时需要判断一下插入位置是不是被别的事务加了gap锁(next-key锁也包含gap锁),如果有的话,插入操作需要等待,直到拥有gap锁的那个事务提交。但是InnoDB规定事务在等待的时候也需要在内存中生成一个锁结构,表明有事务想在某个间隙中插入新记录,但是现在在等待。InnoDB就把这种类型的锁命名为Insert Intention Locks,官方的类型名称为: LOCK_INSERT_INTENTION,我们称为插入意向锁。插入意向锁是一种Gap锁,不是意向锁,在insert操作时产生。
      • 页锁
        • 页锁就是在页的粒度上进行锁定,锁定的数据资源比行锁要多,因为一个页中可以有多个行记录。当我们使用页锁的时候,会出现数据浪费的现象,但这样的浪费最多也就是一个页上的数据行。页锁的开销介于表锁和行锁之间,会出现死锁。锁定粒度介于表锁和行锁之间,并发度一般。
    • 悲观锁
      • 悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁〈共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁,当其他线程想要访问数据时,都需要阻塞挂起。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
    • 乐观锁
      • 乐观锁认为对同一数据的并发操作不会总发生,属于小概率事件,不用每次都对数据上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,也就是不采用数据库自身的锁机制,而是通过程序来实现。在程序上,我们可以采用版本号机制或者CAS机制实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量。在Java中java.util.concurrent.atomic包下的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
  • 按加锁方式划分

    • 隐式锁
      • 当事务需要加锁时,如果这个锁不可能发生冲突,InnoDB会跳过加锁环节,这种机制称为隐式锁。隐式锁是 InnoDB 实现的一种延迟加锁机制,其特点是只有在可能发生冲突时才加锁,从而减少了锁的数量,提高了系统整体性能。另外,隐式锁是针对被修改的 B+ Tree 记录,因此都是记录类型的锁,不可能是间隙锁或 Next-Key 类型。
    • 显式锁
      • 通过特定语法进行加锁,FOR UPDATE、LOCK IN SHARE MODE
  • 其它锁

    • 全局锁
      • 对整个数据库实例加锁,用于全库数据备份
    • 死锁
      • 两个事务都持有对方需要的锁,并且在等待对方释放,并且双方都不会释放自己的锁。
      • 处理方式
        • 等待直到超时
          • 即当两个事务互相等待时,当一个事务等待时间超过设置的阈值时,就将其回滚,另外事务继续进行。这种方法简单有效,在innodb中,参数innodb_lock_wait_timeout用来设置超时时间。
          • 缺点:对于在线服务来说,这个等待时间往往是无法接受的。
          • 那将此值修改短一些,比如1s,0.1s是否合适?不合适,容易误伤到普通的锁等待。
        • 死锁检测进行死锁处理
          • 每当加锁请求无法立即满足需要并进入等待时,wait-for graph算法都会被触发。这是一种较为主动的死锁检测机制”,要求数据库保存锁的信息链表和事务等待链表两部分信息。
          • 一旦检测到回路、有死锁,这时候InnoDB存储引擎会选择回滚undo量最小的事务,让其他事务继续执行( innodb_deadlock_detect=on表示开启这个逻辑)。
          • 缺点:每个新的被阻塞的线程,都要判断是不是由于自己的加入导致了死锁,这个操作时间复杂度是o(n)。如果100个并发线程同时更新同一行,意味着要检测100*100 = 1万次,1万个线程就会有1千万次检测。
        • 如何避免
          • 合理设计索引,使业务SQL尽可能通过索引定位更少的行,减少锁竞争。
          • 调整业务逻辑SQL执行顺序,避免update/delete长时间持有锁的SQL在事务前面。
          • 避免大事务,尽量将大事务拆成多个小事务来处理,小事务缩短锁定资源的时间,发生锁冲突的几率也更小。
          • 在并发比较高的系统中,不要显式加锁,特别是是在事务里显式加锁。如select ... for update语句,如果是在事务里运行了start transaction或设置了autocommit等于o,那么就会锁定所查找到的记录。
          • 降低隔离级别。如果业务允许,将隔离级别调低也是较好的选择,比如将隔离级别从RR调整为RC,可以避免掉很多因为gap锁造成的死锁。
  • 锁升级

    • 每个层级的锁数量是有限制的,因为锁会占用内存空间,锁空间的大小是有限的。当某个层级的锁数量超过了这个层级的阈值时,就会进行锁升级。锁升级就是用更大粒度的锁替代多个更小粒度的锁,比如InnoDB中行锁升级为表锁,这样做的好处是占用的锁空间降低了,但同时数据的并发度也下降了。

多版本并发控制(MVCC)

  • MVCC (Multiversion Concurrency Control),多版本并发控制。顾名思义,MVCC是通过数据行的多个版本管理来实现数据库的并发控制。这项技术使得在innoDB的事务隔离级别下执行一致性读操作有了保证。换言之,就是为了查询一些正在被另一个事务更新的行,并且可以看到它们被更新之前的值,这样在做查询的时候就不用等待另一个事务释放锁。
  • MVCC 没有正式的标准,在不同的DBMS中 MVCC的实现方式可能是不同的,也不是普遍使用的(大家可以参考相关的DBMS文档)。这里讲解InnoDB中 MVcc的实现机制(MysQL其它的存储引擎并不支持它)。
  • MVCC可以不采用锁机制,而是通过乐观锁的方式来解决不可重复读和幻读问题!它可以在大多数情况下替代行级锁,降低系统的开销。
  • 快照读
    • 快照读又叫一致性读,读取的是快照数据。不加锁的简单的SELECT都属于快照读,即不加锁的非阻塞读。
    • 快照读是基于MVCC避免了加锁操作,降低了开销。
  • 当前读
    • 当前读读取的是记录的最新版本(最新数据,而不是历史版本的数据),读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。加锁的SELECT,或者对数据进行增删改都会进行当前读。
  • 实现原理
    • undo log版本链
      • 每次对记录进行改动,都会记录一条undo日志,每条undo日志也都有一个roll_pointer属性(INSERT操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些undo日志都连起来,串成一个链表
    • 隐藏字段
      • 聚簇索引中都包含两个必要的隐藏列
        • trx_id :每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的事务id赋值给trx_id隐藏列。
        • roll_pointer:每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到undo日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。
        • ReadView
          • ReadView就是事务A在使用MVCC机制进行快照读操作时产生的读视图。当事务启动时,会生成数据库系统当前的一个快照,InnoDB为每个事务构造了一个数组,用来记录并维护系统当前活跃事务的ID(“活跃"指的就是,启动了但还没提交)。
          • 结构
            • creator_trx_id:创建这个 Read view的事务ID。
            • trx_ids:表示在生成ReadView时当前系统中活跃的读写事务的事务id列表。
            • up_limit_Id:活跃的事务中最小的事务ID。
            • low_limit_id:表示生成ReadView时系统中应该分配给下一个事务的id值。low_limit_id是系统最大的事务id值,这里要注意是系统中的事务id,需要区别于正在活跃的事务ID。
          • 流程
            • 如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
            • 如果被访问版本的trx_id属性值小于ReadView中的up_limit_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
            • 如果被访问版本的trx_id属性值大于或等于ReadView中的low_limit_id值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。
            • 如果被访问版本的trx_id属性值在ReadView的up_limit_id和low_limit_id之间,那就需要判断一下trx_id属性值是不是在trx_ids列表中。
              • 如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问。
              • 如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。
            • 在隔离级别为读已提交(Read Commit)时,一个事务中的每一次SELECT查询都会重新获取一次Read View.
            • 当隔离级别为可重复读的时候,就避免了不可重复读,这是因为一个事务只在第一次SELECT的时候会获取一次Read View,而后面所有的SELECT都会复用这个Read View
  • 23
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值