1 Mysql中的一些日志文件跟事务隔离性的介绍
1.1 redo log
redo log就是保存执行的SQL语句到一个指定的Log文件,当Mysql执行recovery时重新执行redo log记录的SQL操作即可。当客户端执行每条SQL(更新语句)时,redo log会被首先写入log buffer;当客户端执行COMMIT命令时,log buffer中的内容会被视情况刷新到磁盘。redo log在磁盘上作为一个独立的文件存在,即Innodb的log文件
1.2 undo log
与redo log相反,undo log是为回滚而用,具体内容就是copy事务前的数据库内容(行)到undo buffer,在适合的时间把undo buffer中的内容刷新到磁盘。undo buffer与redo buffer一样,也是环形缓冲,但当缓冲满的时候,undo buffer中的内容会也会被刷新到磁盘;与redo log不同的是,磁盘上不存在单独的undo log文件,所有的undo log均存放在主ibd数据文件中(表空间),即使客户端设置了每表一个数据文件也是如此
1.3 binlog
- binlog,即二进制日志,它记录了数据库上的所有改变.
- 改变数据库的SQL语句执行结束时,将在binlog的末尾写入一条记录,同时通知语句解析器,语句执行完毕.
- binlog格式
- 基于语句,无法保证所有语句都在从库执行成功,比如update ... limit 1;
- 基于行,将每一次改动记为binlog中的一行.在执行一个特别复杂的update或者delete操作时,基于行的格式会有优势.
1.4 数据库的事务隔离级别(MVCC机制需要了解的)
读未提交(READ UNCOMMITTED)
:一个事务还没提交时,它做的变更就能被别的事务看到。读提交(READ COMMITTED)
:一个事务提交之后,它做的变更才会被其他事务看到。可重复读(REPEATABLE READ)
:一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。串行化(SERIALIZABLE)
:对于同一行记录,“写”会加“写锁”,“读”会加“读锁”,当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。隔离级别解决了哪些问题分别有:
脏读(dirty read)
:如果一个事务读到了另一个未提交事务修改过的数据。
不可重复读(non-repeatable read)
:如果一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交后,该事务都能查询得到最新值。
幻读(phantom read)
:如果一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另一个事务插入的记录也读出来设置数据可靠的隔离级别 SET [GLOBAL(全局设置)|SESSION(会话设置)] TRANSACTION ISOLATION LEVEL level 隔离级别;//等级就是上面的几种
视图
,这是事务隔离实现的根本,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。在MySQL里,有两个“视图”的概念:
- 一个是view,它是一个用查询语句定义的虚拟表,在调用的时候执行查询语句并生成结果。创建视图的语法是create view … ,而它的查询方法与表一样。
- 另一个是InnoDB在实现MVCC时用到的一致性读视图,即consistent read view,用于支持RC(Read Committed,读提交)和RR(Repeatable Read,可重复读)隔离级别的实现。
在
可重复读
隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。版本链
在
可重复读
隔离级别下,事务在启动的时候就“拍了个快照”,注意,这个快照是基于整库的。你肯定会说,这怎么可能?如果一个库有100G,那么我启动一个事务,MySQL就要拷贝100G的数据出来,这个过程得多慢啊,这谁顶得住啊?可是你回头一想,平时的事务执行起来不是很快么?
实际上,数据库并不需要拷贝出这100G的数据,那快照怎么实现的?
InnoDB里面每个事务有一个唯一的事务ID,叫作
transaction id
,它是在事务开始的时候向InnoDB的事务系统申请的,是按申请顺序严格递增的。每行数据也都是有多个版本的,每次事务更新数据的时候,都会生成一个新的数据版本,并且把
transaction id
赋值给这个数据版本的事务ID,记为row trx_id
。同时,旧的数据版本要保留,并且在新的数据版本中,能够有信息可以直接拿到它。也就是说,数据表中的一行记录,其实可能有多个版本(row),每个版本有自己的
row trx_id
。这是一个隐藏列,还有另外一个
roll_pointer
:每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到undo日志
中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息
2 Mysql中的锁
Mysql中锁的分类按照不同类型的划分可以分成不同的锁,按照「锁的粒度」划分可以分成:「表锁、页锁、行锁」;按照「使用的方式」划分可以分为:「共享锁」和「排它锁」;按照思想的划分:「乐观锁」和「悲观锁」。
下面我们对着这几种划分的锁进行详细的解说和介绍,在了解设计者设计锁的概念的同时,也能深入的理解设计者的设计思想。
「表锁」是粒度最大的锁,开销小,加锁快,不会出现死锁,但是由于粒度太大,因此造成锁的冲突几率大,并发性能低。
Mysql的「MyISAM储存引擎就支持表锁」,MyISAM的表锁模式有两种:「表共享读锁」和「表独占写锁」。
当一个线程获取到MyISAM表的读锁的时候,会阻塞其他用户对该表的写操作,但是不会阻塞其它用户对该用户的读操作。
相反的,当一个线程获取到MyISAM表的写锁的时候,就会阻塞其它用户的读写操作对其它的线程具有排它性。
「页锁」的粒度是介于行锁和表锁之间的一种锁,因为页锁是在BDB中支持的一种锁机制,也很少没人提及和使用,所以这里制作概述,不做详解。
「行锁」是粒度最小的锁机制,行锁的加锁开销性能大,加锁慢,并且会出现死锁,但是行锁的锁冲突的几率低,并发性能高。
行锁是InnoDB默认的支持的锁机制,MyISAM不支持行锁,这个也是InnoDB和MyISAM的区别之一。
行锁在使用的方式上可以划分为:「共享读锁(S锁)「和」排它写锁(X锁)」。
当一个事务对MySQL中的一条数据行加上了S锁,当前事务不能修改该行数据只能执行读操作,其他事务只能对该行数据加S锁不能加X锁。
若是一个事务对一行数据加了X锁,该事务能够对该行数据执行读和写操作,其它事务不能对该行数据加任何的锁,既不能读也不能写。
「悲观锁和乐观锁是在很多框架都存在的一种思想,不要狭义地认为它们是某一种框架的锁机制」。
数据库管理系统中为了控制并发,保证在多个事务执行时的数据一致性以及事务的隔离性,使用悲观锁和乐观锁来解决并发场景下的问题。
Mysql的「悲观锁的实现是基于MySQL自身的锁机制实现,而乐观锁需要程序员自己去实现的锁机制」,最常见的乐观锁实现就锁机制是「使用版本号实现」。
2.1MyISAM引擎
MyISAM中默认支持的表级锁有两种:「共享读锁」和「独占写锁」。表级锁在MyISAM和InnoDB的存储引擎中都支持,但是InnoDB默认支持的是行锁。
MySQL中平时读写操作都是隐式的进行加锁和解锁操作,Mysql已经自动帮我们实现加锁和解锁操作了,若是想要测试锁机制,我们就要显示的自己控制锁机制。
MySQL中可以通过以下sql来显示的在事务中显式的进行加锁和解锁操作:
// 显式的添加表级读锁
LOCK TABLE 表名 READ
// 显示的添加表级写锁
LOCK TABLE 表名 WRITE
// 显式的解锁(当一个事务commit的时候也会自动解锁)
unlock tables;
2.1.1 MyISAM加表级写锁总结:
当一个线程获取到表级写锁后,只能由该线程对表进行读写操作,别的线程必须等待该线程释放锁以后才能操作
2.1.2 MyISAM加表级读锁总结:
当一个线程获取到表级读锁后,该线程只能读取数据不能修改数据,其它线程也只能加读锁,不能加写锁
2.1.3 查看MyISAM存储引擎中表锁的竞争情况
show status like 'table%';
主要是查看
table_locks_waited
和table_locks_immediate
的值的大小分析锁的竞争情况。
Table_locks_immediate
:表示能够立即获得表级锁的锁请求次数;Table_locks_waited
表示不能立即获取表级锁而需要等待的锁请求次数分析,「值越大竞争就越严重」。
2.2 并发插入
通过总结,详细的说明了表级共享锁和表级写锁的特点。但是在平时的执行sql的时候,这些「解锁 和释放锁都是 Mysql底层隐式的执行的」。
上面的演示只是为了证明显式的执行事务的过程共享锁和表级写锁的加锁和解锁的特点,实际并不会这么做的。
在我们平时执行select语句的时候就会隐式的加读锁,执行增、删、改的操作时就会隐式的执行加写锁。
MyISAM存储引擎中,虽然读写操作是串行化的,但是它也支持并发插入,这个需要设置内部变量 concurrent_insert
的值。
它的值有三个值0、1、2
。可以通过以下的sql查看concurrent_insert
的默认值为「AUTO(或者1)」。
concurrent_insert的值为NEVER (or 0)
表示不支持比并发插入;值为AUTO(或者1)
表示在MyISAM表中没有 被 删除的行,运行另一个线程从表尾插入数据;值为ALWAYS (or 2)
表示不管是否有删除的行,都允许在表 尾插入数据。
2.3锁的调度
MyISAM存储引擎中,「假如同时一个读请求,一个写请求过来的话,它会优先处理写请求」,因为MyISAM存储引擎中认为写请求比读请求重要。
这样就会导致,「假如大量的读写请求过来,就会导致读请求长时间的等待,或者"线程饿死",因此MyISAM不适合运用于大量读写操作的场景」,这样会导致长时间读取不到用户数据,用户体验感极差。
当然可以通过设置low-priority-updates
参数,设置请求链接的优先级,使得Mysql优先处理读请求。
2.4 InnoDB引擎
InnoDB和MyISAM不同的是,InnoDB支持「行锁」和「事务」,行级锁的概念前面以及说了,这里就不再赘述,事务的四大特性的概述以及实现的原理可以参考这一篇[]。
InnoDB中除了有「表锁」和「行级锁」的概念,还有Gap Lock(间隙锁)、Next-key Lock锁,「间隙锁主要用于范围查询的时候,锁住查询的范围,并且间隙锁也是解决幻读的方案」。
InnoDB中的行级锁是「对索引加的锁,在不通过索引查询数据的时候,InnoDB就会使用表锁」。
「但是通过索引查询的时候是否使用索引,还要看Mysql的执行计划」,Mysql的优化器会判断是一条sql执行的最佳策略。
若是Mysql觉得执行索引查询还不如全表扫描速度快,那么Mysql就会使用全表扫描来查询,这是即使sql语句中使用了索引,最后还是执行为全表扫描,加的是表锁。
2.4.1 InnoDB行锁和表锁
InnoDB的行锁也是分为行级「共享读锁(S锁)「和」排它写锁(X锁)」,原理特点和MyISAM的表级锁两种模式是一样的。
若想显式的给表加行级读锁和写锁,可以执行下面的sql语句:
// 给查询sql显示添加读锁
select ... lock in share mode;
// 给查询sql显示添加写锁
select ... for update;
下面我们直接进入锁机制的测试阶段,还是创建一个测试表,并插入两条数据:
// 先把原来的MyISAM表给删除了
DROP TABLE IF EXISTS employee;
CREATE TABLE IF NOT EXISTS employee (
id INT PRIMARY KEY auto_increment,
name VARCHAR(40),
money INT
)ENGINE INNODB;
// 插入测试数据
INSERT INTO employee(name, money) VALUES('黎杜', 1000);
INSERT INTO employee(name, money) VALUES('非科班的科班', 2000);
(2)创建的表中可以看出对表中的字段只有id添加了主键索引,接着就是在session1窗口执行begin
开启事务,并执行下面的sql语句:
// 使用非索引字段查询,并显式的添加写锁
select * from employee where name='黎杜' for update;
(3)然后在session2中执行update语句,上面查询的是id=1的数据行,下面update的是id=2的数据行,会发现程序也会进入等待状态:
update employee set name='ldc' where id =2;
可见若是「使用非索引查询,直接就是使用的表级锁」,锁住了整个表。
(4)若是session1使用的是id来查询,如下图所示:
(5)那么session2是可以成功update其它数据行的,但是这里我建议使用数据量大的表进行测试,因为前面我说过了「是否执行索引还得看Mysql的执行计划,对于一些小表的操作,可能就直接使用全表扫描」。
(6)还有一种情况就是:假如我们给name字段也加上了普通索引,那么通过普通索引来查询数据,并且查询到多行数据,那它是锁这多行数据还是锁整个表呢?
下面我们来测试一下,首先给「name字段添加普通索引」,如下图所示:
(6)并插入一条新的数据name值与id=2的值相同,并显式的加锁,如下若是:
(7)当update其它数据行name值不是ldc的也会进入等待状态,并且通过explain来查看是否name='ldc'有执行索引,可以看到sql语句是有执行索引条件的。
结论:从上面的测试锁机制的演示可以得出以下几个结论:
- 执行非索引条件查询执行的是表锁。
- 执行索引查询是否是加行锁,还得看Mysql的执行计划,可以通过explain关键字来查看。
- 用普通键索引的查询,遇到索引值相同的,也会对其他的操作数据行的产生影响。
2.4.2 InnoDB间隙锁
当我们使用范围条件查询而不是等值条件查询的时候,InnoDB就会给符合条件的范围索引加锁,在条件范围内 并不存的记录就叫做"间隙(GAP)"
大家大概都知道在事务的四大隔离级别中,不可重复读会产生幻读的现象,只能通过提高隔离级别到串行化来 解 决幻读现象。
但是Mysql中的不可重复是已经解决了幻读问题,它通过引入间隙锁的实现来解决幻读,通过给符合条件的间隙 加锁,防止再次查询的时候出现新数据产生幻读的问题。
例如我们执行下面的sql语句,就会对id大于100的记录加锁,在id>100的记录中肯定是有不存在的间隙:
Select * from employee where id> 100 for update;
(1)接着来测试间隙锁,新增一个字段num,并将num添加为普通索引、修改之前的数据使得num之 间的值存在间隙,操作如下sql所示:
alter table employee add num int not null default 0;
update employee set num = 1 where id = 1;
update employee set num = 1 where id = 2;
update employee set num = 3 where id = 3;
insert into employee values(4,'kris',4000,5);
同时打开窗口session2,并执行新增语句:
insert into employee values(5,'ceshi',5000,2); // 程序出现等待
insert into employee values(5,'ceshi',5000,4); // 程序出现等待
insert into employee values(5,'ceshi',5000,6); // 新增成功
insert into employee values(6,'ceshi',5000,0); // 新增成功
「从上面的测试结果显示在区间(1,3]U[3,5)之间加了锁,是不能够新增数据行,这就是新增num=2和num=4失败的原因,但是在这个区间以外的数据行是没有加锁的,可以新增数据行」。
根据索引的有序性,而普通索引是可以出现重复值,那么当我们第一个sesson查询的时候只出现一条数据num=3,为了解决第二次查询的时候出现幻读,也就是出现两条或者更多num=3这样查询条件的数据。
Mysql在满足where条件的情况下,给(1,3]U[3,5)
区间加上了锁不允许插入num=3的数据行,这样就解决了幻读。
这里抛出几种情况接着来测试间隙锁。主键索引(唯一索引)是否会加上间隙锁呢?范围查询是否会加上间隙锁?使用不存在的检索条件是否会加上间隙锁?
先来说说:「主键索引(唯一索引)是否会加上间隙锁呢?」
因为主键索引具有唯一性,不允许出现重复,那么当进行等值查询的时候id=3,只能有且只有一条数据,是不可能再出现id=3的第二条数据。
因此它只要锁定这条数据(锁定索引),在下次查询当前读的时候不会被删除、或者更新id=3的数据行,也就保证了数据的一致性,所以主键索引由于他的唯一性的原因,是不需要加间隙锁的。
再来说说第二个问题:「范围查询是否会加上间隙锁?」
直接在session1中执行下面的sql语句,并在session2中在这个num>=3的查询条件内和外新增数据:
select * from employee where num>=3 for update;
insert into employee values(6,'ceshi',5000,2); // 程序出现等待
insert into employee values(7,'ceshi',5000,4); // 程序出现等待
insert into employee values(8,'ceshi',5000,1); // 新增数据成功
我们来分析以下原理:单查询num>=3的时候,在现有的employee表中满足条件的数据行,如下所示:
id | num |
---|---|
3 | 3 |
4 | 5 |
5 | 6 |
那么在设计者的角度出发,我为了解决幻读的现象:在num>=3的条件下是必须加上间隙锁的。
而在小于num=3中,下一条数据行就是num=1了,为了防止在(1,3]的范围中加入了num=3的数据行,所以也给这个间隙加上了锁,这就是添加num=2数据行出现等待的原因。
最后来说一说:「使用不存在的检索条件是否会加上间隙锁?」
假如是查询num>=8的数据行呢?因为employee表并不存在中num=8的数据行,num最大num=6,所以为了解决幻读(6,8]与num>=8也会加上锁。
2.4.3 死锁
案例一:
死锁在InnoDB中才会出现死锁,MyISAM是不会出现死锁,因为MyISAM支持的是表锁,一次性获取了所有的锁,其它的线程只能排队等候。
而InnoDB默认支持行锁,获取锁是分步的,并不是一次性获取所有的锁,因此在锁竞争的时候就会出现死锁的情况。
虽然InnoDB会出现死锁,但是并不影响InnoDB成为最受欢迎的存储引擎,MyISAM可以理解为串行化操作,读写有序,因此支持的并发性能低下。
举一个例子,现在数据库表employee中六条数据,如下所示:
其中name=ldc的有两条数据,并且name字段为普通索引,分别是id=2和id=3的数据行,现在假设有两个事务分别执行下面的两条sql语句:
// session1执行
update employee set num = 2 where name ='ldc';
// session2执行
select * from employee where id = 2 or id =3;
其中session1执行的sql获取的数据行是两条数据,假设先获取到第一个id=2的数据行,然后cpu的时间分配给了另一个事务,另一个事务执行查询操作获取了第二行数据也就是id=3的数据行。
当事务2继续执行的时候获取到id=3的数据行,锁定了id=3的数据行,此时cpu又将时间分配给了第一个事务,第一个事务执行准备获取第二行数据的锁,发现已经被其他事务获取了,它就处于等待的状态。
当cpu把时间有分配给了第二个事务,第二个事务准备获取第一行数据的锁发现已经被第一个事务获取了锁,这样就行了死锁,两个事务彼此之间相互等待。
案例二:
第二种死锁情况就是当一个事务开始并且update一条id=1的数据行时,成功获取到写锁,此时另一个事务执行也update另一条id=2的数据行时,也成功获取到写锁(id为主键)。
此时cpu将时间分配给了事务一,事务一接着也是update id=2的数据行,因为事务二已经获取到id=2数据行的锁,所以事务已处于等待状态。
事务二有获取到了时间,像执行update id=1的数据行,但是此时id=1的锁被事务一获取到了,事务二也处于等待的状态,因此形成了死锁。
session1 | session2 |
---|---|
begin;update t set name='测试' where id=1; | begin |
update t set name='测试' where id=2; | |
update t set name='测试' where id=2; | |
等待..... | update t set name='测试' where id=1; |
等待..... | 等待...... |
2.4.4 死锁的解决方案
首先要解决死锁问题,在程序的设计上,当发现程序有高并发的访问某一个表时,尽量对该表的执行操作串行化,或者锁升级,一次性获取所有的锁资源。
然后也可以设置参数innodb_lock_wait_timeout
,超时时间,并且将参数innodb_deadlock_detect
打开,当发现死锁的时候,自动回滚其中的某一个事务。
锁总结
上面详细的介绍了MyISAM和InnoDB两种存储引擎的锁机制的实现,并进行了测试。
MyISAM的表锁分为两种模式:「共享读锁」和「排它写锁」。获取的读锁的线程对该数据行只能读,不能修改,其它线程也只能对该数据行加读锁。
获取到写锁的线程对该数据行既能读也能写,对其他线程对该数据行的读写具有排它性。
MyISAM中默认写优先于去操作,因此MyISAM一般不适合运用于大量读写操作的程序中。
InnoDB的行锁虽然会出现死锁的可能,但是InnoDB的支持的并发性能比MyISAM好,行锁的粒度最小,一定的方法和措施可以解决死锁的发生,极大的发挥InnoDB的性能。
InnoDB中引入了间隙锁的概念来决解出现幻读的问题,也引入事务的特性,通过事务的四种隔离级别,来降低锁冲突,提高并发性能。
3 MVCC机制
行的更新过程:
下面演示下事务对某行记录的更新过程:
1. 初始数据行
F1~F6是某行列的名字,1~6是其对应的数据。后面三个隐含字段分别对应该行的事务号和回滚指针,假 如 这条数据是刚INSERT的,可以认为ID为1,其他两个字段为空。
2.事务1更改该行的各字段的值
当事务1更改该行的值时,会进行如下操作:
- 用排他锁锁定该行
- 记录redo log
- 把该行修改前的值Copy到undo log,即上图中下面的行
- 修改当前行的值,填写事务编号,使回滚指针指向undo log中的修改前的行
3.事务2修改该行的值
与事务1相同,此时undo log,中有有两行记录,并且通过回滚指针连在一起。
因此,如果undo log一直不删除,则会通过当前记录的回滚指针回溯到该行创建时的初始内容,所幸的时 在 Innodb中存在purge线程,它会查询那些比现在最老的活动事务还早的undo log,并删除它们,从而保 证undo log文件不至于无限增长。
4. 事务提交
当事务正常提交时Innbod只需要更改事务状态为COMMIT即可,不需做其他额外的工作,而Rollback则稍 微 复杂点,需要根据当前回滚指针从undo log中找出事务修改前的版本,并恢复。如果事务影响的行非 常多,回滚则可能会变的效率不高,根据经验值没事务行数在1000~10000之间,Innodb效率还是非常高 的。很显然,Innodb是一个COMMIT效率比Rollback高的存储引擎。据说,Postgress的实现恰好与此相 反。
5. Insert Undo log
上述过程确切地说是描述了UPDATE的事务过程,其实undo log分insert和update undo log,因为insert时,原始的数据并不存在,所以回滚时把insert undo log丢弃即可,而update undo log则必须遵守上述过程。
总结
上述更新前建立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这个名字,提供了读的非阻塞而已。
5.总结
也不是说MVCC就无处可用,对一些一致性要求不高的场景和对单一数据的操作的场景还是可以发挥作用的,比如多个事务同时更改用户在线数,如果某个事务更新失败则重新计算后重试,直至成功。这样使用MVCC会极大地提高并发数,并消除线程锁。
MVCC的另一种总结
MVCC(Multi-Version Concurrency Control ,多版本并发控制)
指的就是在使用读已提交(READ COMMITTD)、可重复读(REPEATABLE READ)
这两种隔离级别的事务在执行普通的SELECT操作时访问记录的版本链的过程,这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。
这两个隔离级别的一个很大不同就是:生成ReadView的时机不同
,READ COMMITTD在每一次进行普通SELECT操作前都会生成一个ReadView,而REPEATABLE READ只在第一次进行普通SELECT操作前生成一个ReadView,数据的可重复读其实就是ReadView的重复使用。
参考链接:MVCC参考链接 MVCC参考链接 锁机制的参考链接