Mysql数据库事物及锁

对数据库事务的一些概念个理解一直都是模模糊糊的,正好前两天被问到了就整理记录一下。

1.什么是事务?

  • 在关系数据库中,一个事务可以是一条SQL语句,一组SQL语句或整个程序。

2.事物的四大特性ACID

  • 原子性:事务作为数据库的逻辑工作单元,其中包含的操作要么全部成功执行,要么全部失败回滚。
  • 一致性:事务执行前后数据库只能由一种一致性状态变为另一种一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。 
  • 隔离性:一个事务的执行不影响其他事务,并发执行的各事务之间互不干扰。(这是不太能实现的,要做到互不干扰需要牺牲数据库访问性能,因此出现了数据库事物的隔离级别)
  • 持久性:事务一旦提交对数据库中数据的改变就是永久的。

3.事物并发执行产生的问题,数据库的隔离级别。

事务并发执行可能产生的问题:

  • 读脏数据:事物A修改数据库中的数据但未提交,事物B查询数据库的结果包含了事物A修改的数据。事物A回滚导致事物B读到了脏数据。
  • 第一类更新丢失:A事务撤销时,把已经提交的B事务的更新数据覆盖了。例子:                                                                                        A事务在撤销时,“不小心”将B事务已经转入账户的金额给抹去了。
  • 不可重复读:事务B在事务A两次查询的过程中对事务A的查询结果进行修改,导致事物A两次查询的结果不一致。
  • 第二类更新丢失:覆盖更新/两次更新问题。A事务覆盖B事务已经提交的数据,造成B事务所做操作丢失。例子:                                                                                     第二类更新丢失实际上和不可重复读是同一种问题。
  • 幻读:事物B在事务A两次查询的过程中,插入符合事务A查询结果的数据,导致事物A第二次查询结果多出来几条,好似发生了幻读。

数据库的隔离级别:

  • Read Uncommitted :读未提交,即使一个事务的更新操作没有提交,别的事务也能够读到这次更新的改变,几种异常情况都可能导致。极易出错,没有安全性可言,基本不会使用。
  • Read Committed : 读已提交,一个事务只能看到其他事务已经提交的更新看不到未提交的更新。消除了脏读和第一类更新丢失。Oracle和SQLServer默认的隔离级别。
  • Repeatable Read : 可重复读,保证在同一个事务中多次检索相同的条件,总是得到同样的结果,但是不保证查询到的数据条数是一样的。消除了不可重复读和第二类丢失更新,但是有可能发生幻读。mysql默认的隔离级别。
  • 可串行化(Serializable): 多个事务串行执行,这是最高的隔离级别,可能导致大量的超时和锁竞争问题,性能最低。

4.什么是锁机制?数据库为什么需要锁机制?

数据库是一种多个用户使用的共享资源,当多个事务并发修改同一数据时需要一定的机制保证数据库状态的一致性。

加锁是实现数据库并发控制的重要手段。

5.锁的基本类型

     (1)锁分为乐观锁和悲观锁:

  • 乐观锁:乐观锁的实质是消除锁,每次去拿数据的时候都认为别人不会修改因此不会上锁,在最后更新时会判断别人有没有修改。可以使用基于应用的版本机制来实现,比如MVCC多版本并发控制
  • 悲观锁:每次拿数据时都认为别人会修改,因此在拿数据之前会先对数据上锁,当别人想要拿着个数据时就会block(阻塞)直到获取了数据上的锁。

    (2)悲观锁按性质划分

  • 共享锁(Share Lock,简称S锁):也成读锁。事物A对对象T 加S锁,其他事物也能对这个对象加S锁,多个事务可以同时读,但不能有写操作,直到S锁被释放
  • 排它锁(ExclusiveLock,简称X锁):也成为写锁。事物A对对象T加X锁,其他事务不能对T加任和锁,只有事务A可以读写对象T直到A释放X锁。
  • 更新锁(简称U锁):用来预定对对象T添加X锁,它允许其他事务读但不允许再有其他事务对T添加X锁或者U锁。用来防止发生死锁。因为使用共享锁时,修改数据的操作分为两步,首先获得一个共享锁,读取数据,然后将共享锁升级为排它锁,然后再执行修改操作。这样如果同时有两个或多个事务同时对一个对象申请了共享锁,在修改数据的时候,这些事务都要将共享锁升级为排它锁。这些事务都不会释放共享锁而是一直等待对方释放,这样就造成了死锁。如果一个数据在修改前直接申请更新锁,在数据修改的时候再升级为排它锁,就可以避免死锁。

    (3)锁按照作用范围划分

  • 行级锁:分为共享锁和排它锁,锁的作用范围是行级别。InnoDB行锁是通过给索引上的索引项加锁来实现行级锁的,只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁。
  • 表级锁:分为共享锁和排它锁,锁的作用范围是全表。

6.MySql数据库中的锁以及Innodb中的锁定模式及实现机制

(1) Mysql中有行级锁和表级锁:

  • 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低;
  • 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高;   
  • 适用:从锁的角度来说,表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用;而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统。

(2) Mysql的行级锁由InnoDB存储引擎实现。考虑到行级锁定是由各个存储引擎自行实现,而且具体实现也各有差别,而InnoDB是目前事务型存储引擎中使用最为广泛的存储引擎,所以这里我们就主要分析一下InnoDB的锁定特性。

InnoDB的行级锁定分为共享锁和排他锁,在锁定机制的实现过程中为了让行级锁定和表级锁定共存,InnoDB使用了意向锁(表级锁定)的概念,也就有了意向共享锁和意向排他锁这两种。

当一个事务给自己需要的某个资源加锁的时候,如果遇到一个共享锁正锁定着自己需要的资源,自己可以再加一个共享锁,不过不能加排他锁。但是,如果遇到自己需要锁定的资源已经被一个排他锁占有之后,则只能等待该锁定释放资源之后自己才能获取锁定资源并添加自己的锁定。

意向锁的作用就是当一个事务在需要获取资源锁定的时候,如果遇到自己需要的资源已经被排他锁占用,那么该事务可以给需要锁定行的表上面添加一个合适的意向锁。如果自己需要一个共享锁,就在表上面添加一个意向共享锁。如果自己需要的是某行(或者某些行)上面添加一个排他锁的话,可以先在表上面添加一个意向排他锁。意向共享锁可以同时并存多个,但是意向排他锁同时只能有一个存在。

因此可以说InnoDB的锁定模式实际上可以分为四种:共享锁(S),排他锁(X),意向共享锁(IS)和意向排他锁(IX),我们可以通过以下表格来总结上面这四种所的共存逻辑关系:

如果一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。意向锁是InnoDB自动加的,不需用户干预。

对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁;事务可以通过以下语句显示给记录集加共享锁或排他锁。

-- 获得共享锁,主要用在需要数据依存关系时来确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或者DELETE操作。
-- 但是如果当前事务也需要对该记录进行更新操作,则很有可能造成死锁,对于锁定行记录后需要进行更新操作的应用
-- 应该使用SELECT... FOR UPDATE方式获得排他锁。
共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE

排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE

7.Innodb中行级锁定的实现

  (1) InnoDB行锁是通过给索引上的索引项加锁来实现行锁的,只有通过索引条件检索数据,InnoDB才使用行级锁,否则InnoDB将使用表锁。在实际应用中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。下面通过一些实际例子来加以说明。

  • 在不通过索引条件查询的时候,InnoDB确实使用的是表锁,而不是行锁。
  • 由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。
  • 当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁。
  • 即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决定的,如果MySQL认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。因此,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。

(2) 间隙锁:算是行锁的一种

当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;
对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。

例子:假如emp表中只有101条记录,其empid的值分别是 1,2,...,100,101,执行SQL

select * from emp where empid > 100 for update;

这是一个范围条件的检索,InnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的“间隙”加锁。

  (3) InnoDB使用间隙锁的目的:

  • 防止幻读,以满足相关隔离级别的要求。对于上面的例子,要是不使用间隙锁,如果其他事务插入了empid大于100的任何记录,那么本事务如果再次执行上述语句,就会发生幻读;
  • 为了满足其恢复和复制的需要。
  • 很显然,在使用范围条件检索并锁定记录时,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能造成很大的危害。

(4) 除了间隙锁给InnoDB带来性能的负面影响之外,通过索引实现锁定的方式还存在其他几个较大的性能隐患:

  • 当Query无法利用索引的时候,InnoDB会放弃使用行级别锁定而改用表级别的锁定,造成并发性能的降低;
  • 当Query使用的索引并不包含所有过滤条件的时候,数据检索使用到的索引键所只想的数据可能有部分并不属于该Query的结果集的行列,但是也会被锁定,因为间隙锁锁定的是一个范围,而不是具体的索引键;
  • 当Query在使用索引定位数据的时候,如果使用的索引键一样但访问的数据行不同的时候(索引只是过滤条件的一部分),一样会被锁定。

因此,在实际应用开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件。还要特别说明的是,InnoDB除了通过范围条件加锁时使用间隙锁外,如果使用相等条件请求给一个不存在的记录加锁,InnoDB也会使用间隙锁。

  (5) 行级锁定可能会发生死锁

  (6) Innodb优化建议

  • 可能让所有的数据检索都通过索引来完成,从而避免InnoDB因为无法通过索引键加锁而升级为表级锁定;
  • 合理设计索引,让InnoDB在索引键上面加锁的时候尽可能准确,尽可能的缩小锁定范围,避免造成不必要的锁定而影响其他Query的执行;
  • 尽可能减少基于范围的数据检索过滤条件,避免因为间隙锁带来的负面影响而锁定了不该锁定的记录;
  • 尽量控制事务的大小,减少锁定的资源量和锁定时间长度;
  • 在业务环境允许的情况下,尽量使用较低级别的事务隔离,以减少MySQL因为实现事务隔离级别所带来的附加成本。

8.Mysql表级锁定的实现(MyISAM引擎为例)

(1) 表级锁定模式也分为两种:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。

(2) 如何加表锁
 在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此,用户一般不需要直接用LOCK TABLE命令给MyISAM表显式加锁。

(3) MyISAM表锁优化建议
        对于MyISAM存储引擎,虽然使用表级锁定在锁定实现的过程中比实现行级锁定或者页级锁所带来的附加成本都要小,锁定本身所消耗的资源也是最少。但是由于锁定的颗粒度比较大,所以造成锁定资源的争用情况也会比其他的锁定级别都要多,从而在较大程度上会降低并发处理能力。所以,在优化MyISAM存储引擎锁定问题的时候,最关键的就是如何让其提高并发度。由于锁定级别是不可能改变的了,所以我们首先需要尽可能让锁定的时间变短,然后就是让可能并发进行的操作尽可能的并发。

  • 查询表级锁争用情况,使用Mysql内置的状态变量记录系统内部锁资源争用情况:
    mysql> show status like 'table%';
    Table_locks_immediate:产生表级锁定的次数;
    Table_locks_waited:出现表级锁定争用而发生等待的次数;
    两个状态值都是从系统启动后开始记录,出现一次对应的事件则数量加1。如果这里的Table_locks_waited状态值比较高,
    那么说明系统中表级锁定争用现象比较严重,就需要进一步分析为什么会有较多的锁定资源争用了。
  • 缩短锁定时间,减少大的复杂Query,将复杂Query分拆成几个小的Query分布进行;建立足够高效的索引,让数据检索更迅速;尽量让表只存放必要的信息,控制字段类型;
  • 分离能并行的操作

  • 合理利用读写优先级,两个进程同时请求同一张表的锁,一个请求读锁另一个请求写锁,写进程会优先获得锁。默认情况下,写的优先级高于读。通过执行命令SET LOW_PRIORITY_UPDATES=1,使该连接读比写的优先级高。如果我们的系统以读为主,可以设置此参数,如果以写为主,则不用设置;
    通过指定INSERT、UPDATE、DELETE语句的LOW_PRIORITY属性,降低该语句的优先级。
    虽然上面方法都是要么更新优先,要么查询优先的方法,但还是可以用其来解决查询相对重要的应用(如用户登录系统)中,读锁等待严重的问题。
    另外,MySQL也提供了一种折中的办法来调节读写冲突,即给系统参数max_write_lock_count设置一个合适的值,当一个表的读锁达到这个值后,MySQL就暂时将写请求的优先级降低,给读进程一定获得锁的机会。

  • 这里还要强调一点:一些需要长时间运行的查询操作,会使写进程“饿死”。因此,应用中应尽量避免出现长时间运行的查询操作,不要总想用一条SELECT语句来解决问题,因为这种看似巧妙的SQL语句,往往比较复杂,执行时间较长,在可能的情况下可以通过使用中间表等措施对SQL语句做一定的“分解”,使每一步查询都能在较短时间完成,从而减少锁冲突。如果复杂查询不可避免,应尽量安排在数据库空闲时段执行,比如一些定期统计可以安排在夜间执行。

9. Innodb什么时候使用表级锁定,什么时候使用行级锁定

(1)事务需要更新大部分或全部数据,表又比较大,如果使用默认的行锁,不仅这个事务执行效率低,而且可能造成其他事务长时间锁等待和锁冲突,这种情况下可以考虑使用表锁来提高该事务的执行速度。
(2)事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚。这种情况也可以考虑一次性锁定事务涉及的表,从而避免死锁、减少数据库因事务回滚带来的开销。
当然,应用中这两种事务不能太多,否则,就应该考虑使用MyISAM表了。
在InnoDB下,使用表锁要注意以下两点。
(1)使用LOCK TABLES虽然可以给InnoDB加表级锁,但必须说明的是,表锁不是由InnoDB存储引擎层管理的,而是由其上一层MySQL Server负责的,仅当autocommit=0、InnoDB_table_locks=1(默认设置)时,InnoDB层才能知道MySQL加的表锁,MySQL Server也才能感知InnoDB加的行锁,这种情况下,InnoDB才能自动识别涉及表级锁的死锁,否则,InnoDB将无法自动检测并处理这种死锁。
(2)在用 LOCK TABLES对InnoDB表加锁时要注意,要将AUTOCOMMIT设为0,否则MySQL不会给表加锁;事务结束前,不要用UNLOCK TABLES释放表锁,因为UNLOCK TABLES会隐含地提交事务;COMMIT或ROLLBACK并不能释放用LOCK TABLES加的表级锁,必须用UNLOCK TABLES释放表锁。正确的方式见如下语句:

SET AUTOCOMMIT=0;
LOCK TABLES t1 WRITE, t2 READ, ...;
[do something with tables t1 and t2 here];
COMMIT;
UNLOCK TABLES;

10.MVCC实现乐观锁,来源https://www.imooc.com/article/17290

(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: 主要用来判断当前版本数据的可见性。一个列表保存了当前已经启动但是还没提交的事物id。

(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) 行的更新过程
下面演示下事务对某行记录的更新过程:

(1)初始数据行

F1~F6是某行列的名字,1~6是其对应的数据。后面三个隐含字段分别对应该行的事务号和回滚指针,假如这条数据是刚INSERT的,可以认为ID为1,其他两个字段为空。

(2)事务1更改该行的各字段的值                                                                               

          

当事务1更改该行的值时,会进行如下操作:

  • 用排他锁锁定该行
  • 记录redo log
  • 把该行修改前的值Copy到undo log,即上图中下面的行
  • 修改当前行的值,填写事务编号,使回滚指针指向undo log中的修改前的行

(3) 事务2修改改行的值

与事务1相同,此时undo log,中有有两行记录,并且通过回滚指针连在一起。

(4)Read View判断当前数据版本是否可见

在innodb中,创建一个新事务的时候,innodb会将当前系统中的活跃事务列表(trx_sys->trx_list)创建一个副本(read view),副本中保存的是系统当前不应该被本事务看到的其他事务id列表。当用户在这个事务中要读取该行记录的时候innodb会将该行当前的版本号与该read view进行比较。

  • 对于当前事物启动瞬间来说,一个数据版本的row trx_id有以下几种可能

    1. 落在绿色部分,表示这个版本是已提交的事物或者是自己生成的,可见

    2. 落在红色部分,这个版本是由将来事物生成的,不可见。

    3. 如果在黄色部分,若row trx_id在数组中,表示这个版本是由还没提交的事物生成的,不可见;若row trx_id不再数组中,表示这个版本是已提交的事物生成的,可见。

  • Read View 判断当前版本是否可见的整体流程:

1.设该行的当前事务id为trx_id_0,read view中最早的事务id为trx_id_1, 最迟的事务id为trx_id_2。
2.如果trx_id_0< trx_id_1的话,那么表明该行记录所在的事务已经在本次新事务创建之前就提交了,所以该行记录的当前值是可见的。
跳到步骤6.
3.如果trx_id_0>trx_id_2的话,那么表明该行记录所在的事务在本次新事务创建之后才开启,所以该行记录的当前值不可见.
跳到步骤5。
4.如果trx_id_1<=trx_id_0<=trx_id_2, 那么表明该行记录所在事务在本次新事务创建的时候处于活动状态,从trx_id_1到trx_id_2进行遍历,
如果trx_id_0等于他们之中的某个事务id的话,那么不可见。跳到步骤5.
5.从该行记录的DB_ROLL_PTR指针所指向的回滚段中取出最新的undo-log的版本号,将它赋值该trx_id_0,然后跳到步骤2.
6.将该可见行的值返回。
需要注意的是,新建事务(当前事务)与正在内存中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 生成原则

  • 可重复读(Repeatable Read):一个事物启动后,能够看到所有已提交事物结果。但是,这个事物执行期间,其它事物的更新对他不可见。因此一个事物只需在启动的时候说明,“以我启动的时刻为准,如果一个数据版本是在我启动之前生成的,就认;如果是我启动以后才生成的,我就不认,我必须要找到它的上一个版本”。当然,如果“上一个版本”也不可见,那就得继续往前找。还有,如果是这个事务自己更新的数据,它自己还是要认的。
  •  可重复读的核心就是一致性读(consistent read);而事务更新数据的时候,只能用当前读。如果当前的记录的行锁被其他事务占用的话,就需要进入锁等待。

  • 而读提交的逻辑和可重复读的逻辑类似,它们最主要的区别是:在可重复读隔离级别下,只需要在事务开始的时候创建一致性视图,之后事务里的其他查询都共用这个一致性视图;在读提交隔离级别下,每一个语句执行前都会重新算出一个新的视图。

  • 这里我们就要看一下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 读事务开始时的行数据版本。
  • MVCC的几个特点

每行数据都存在一个版本,每次数据更新时都更新该版本修改时Copy出当前版本随意修改,个事务之间无干扰保存时比较版本号,如果成功(commit),则覆盖原记录;失败则放弃copy(rollback)就是每行都有版本号,保存时根据版本号决定是否成功。

11.不同隔离级别的加锁实现

  • 对于读提交Read Committed,使用MVCC查询在语句启动前就已经提交完成的数据;写数据加行锁。

  • 对于可重复读Repeatable Read,使用MVCC消除不可重复读;写数据加表级锁。

  • 序列化 Serialized,读写均加表级锁

参考链接:

https://blog.csdn.net/weixin_39651041/article/details/79985715

https://www.jianshu.com/p/d75fcdeb07a3

https://www.cnblogs.com/jimmyhe/p/11013551.html

https://www.cnblogs.com/skying555/p/11136996.html

https://www.jianshu.com/p/eb41df600775

https://www.cnblogs.com/sessionbest/articles/8689071.html

https://blog.csdn.net/weixin_39651041/article/details/79985715

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值