mysql隔离级别mvcc_一、深刻理解mysql四种隔离级别及底层实现原理(MVCC和锁)

1、ACID特性

持久性,咱们就不讲了,易懂。java

一、原子性

在同一个事务内部的一组操做必须所有执行成功(或者所有失败)。redis

为了保证事务操做的原子性,必须实现基于日志的REDO/UNDO机制:将全部对数据的更新操做都写入日志,若是一个事务中的一部分操做已经成功,但之后的操做,因为断电/系统崩溃/其它的软硬件错误而没法继续,则经过回溯日志,将已经执行成功的操做撤销,从而达到“所有操做失败”的目的。 最多见的场景是,数据库系统崩溃后重启,此时数据库处于不一致的状态,必须先执行一个crash recovery的过程:读取日志进行REDO(重演将全部已经执行成功但还没有写入到磁盘的操做,保证持久性),再对全部到崩溃时还没有成功提交的事务进行UNDO(撤销全部执行了一部分但还没有提交的操做,保证原子性)。crash recovery结束后,数据库恢复到一致性状态,能够继续被使用。sql

某个应用在执行转账的数据库操做时,必须在同一个事务内部调用对账户A和账户B的操做,才能保证数据的一致性。数据库

可是,原子性并不能彻底保证一致性。在多个事务并行进行的状况下,即便保证了每个事务的原子性,仍然可能致使数据不一致的结果。 例如,事务1须要将100元转入账号A:先读取账号A的值,而后在这个值上加上100。可是,在这两个操做之间,另外一个事务2修改了账号A的值,为它增长了100元。那么最后的结果应该是A增长了200元。但事实上,事务1最终完成后,账号A只增长了100元,由于事务2的修改结果被事务1覆盖掉了。安全

简而言之,就是:原子性仅可以保证单个事务的一致性。就像redis同样,也只能保证单操做的线程安全,并不能保证多操做下的线程安全。并发

二、一致性

按照我我的的理解,在事务处理的ACID属性中,一致性是最基本的属性,其它的三个属性都为了保证一致性而存在的。性能

咱们举个反例来理解下一致性概念。例如:从账户A转一笔钱到账户B上,若是账户A上的钱减小了,而账户B上的钱却没有增长,那么咱们认为此时数据处于不一致的状态。线程

为了保证并发状况下的一致性,引入了隔离性,即保证每个事务可以看到的数据老是一致的,就好象其它并发事务并不存在同样。日志

三、隔离性

数据库四种隔离级别,以及常见的几种读异常,你们应该都是耳熟能详的,但数据库底层是怎么实现隔离性的呢?都采用了哪些技术呢? 主要有两个技术:MVCC(多版本并发控制)和锁。code

(1)MVCC(多版本并发控制)

多版本并发控制,顾名思义,在并发访问的时候,数据存在版本的概念,能够有效地提高数据库并发能力,常见的数据库如MySQL、MS SQL Server、IBM DB二、Hbase、MongoDB等等都在使用。

简单讲,若是没有MVCC,当想要读取的数据被其余事务用排它锁锁住时,只能互斥等待;而这时MVCC能够经过提供历史版本从而实现读取被锁的数据的历史版本,从而避免了互斥等待。

InnoDB采用的MVCC实现方式是:在须要时,经过undo日志构造出历史版本。

(2)锁

1) 锁的分类

Shared Locks(共享锁/S锁)

若事务T对数据对象A加上S锁,则事务T只能读A;其余事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这就保证了其余事务能够读A,但在T释放A上的S锁以前不能对A作任何修改。

Exclusive Locks(排它锁/X锁)

若事务T对数据对象A加上X锁,则只容许T读取和修改A,其它任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。它防止任何其它事务获取资源上的锁,直到在事务的末尾将资源上的原始锁释放为止。在更新操做(INSERT、UPDATE 或 DELETE)过程当中始终应用排它锁。

注意:排他锁会阻止其它事务再对其锁定的数据加读或写的锁,可是不加锁的就没办法控制了。

Record Locks(行锁)

行锁,顾名思义,是加在索引行(对!是索引行!不是数据行!)上的锁。好比select * from user where id=1 and id=10 for update,就会在id=1和id=10的索引行上加Record Lock。

Gap Locks(间隙锁)

间隙锁,它会锁住两个索引之间的区域。好比select * from user where id>1 and id<10 for update,就会在id为(1,10)的索引区间上加Gap Lock。

Next-Key Locks(间隙锁)

也叫间隙锁,它是Record Lock + Gap Lock造成的一个闭区间锁。好比select * from user where id>=1 and id<=10 for update,就会在id为[1,10]的索引闭区间上加Next-Key Lock。

这样组合起来就有,行级共享锁,表级共享锁,行级排它锁,表级排它锁。

2) 何时会加锁?

在数据库增删改查四种操做中,insert、delete和update都是会加排它锁(Exclusive Locks)的,而select只有显式声明才会加锁:

select: 即最经常使用的查询,是不加任何锁的

select ... lock in share mode: 会加共享锁(Shared Locks)

select ... for update: 会加排它锁

3) 四种隔离级别

不一样的隔离级别是在数据可靠性和并发性之间的均衡取舍,隔离级别越高,对应的并发性能越差,数据越安全可靠。

READ UNCOMMITTED

顾名思义,事务之间能够读取彼此未提交的数据。机智如你会记得,在前文有说到全部写操做都会加排它锁,那还怎么读未提交呢?

机智如你,前面咱们介绍排它锁的时候,有这种说明: 排他锁会阻止其它事务再对其锁定的数据加读或写的锁,可是对不加锁的读就不起做用了。

READ UNCOMMITTED隔离级别下, 读不会加任何锁。而写会加排他锁,并到事务结束以后释放。

实例1:

查-写:查并无阻止写,代表查确定并无加锁,要不写确定就阻塞了。写很明显,会加排它锁的。

实例2: 写-写:阻塞,代表,写会加排它锁。

READ COMMITTED

顾名思义,事务之间能够读取彼此已提交的数据。

InnoDB在该隔离级别(READ COMMITTED)写数据时,使用排它锁, 读取数据不加锁而是使用了MVCC机制。

所以,在读已提交的级别下,都会经过MVCC获取当前数据的最新快照,不加任何锁,也无视任何锁(由于历史数据是构造出来的,身上不可能有锁)。

可是,该级别下仍是遗留了不可重复读和幻读问题: MVCC版本的生成时机: 是每次select时。这就意味着,若是咱们在事务A中执行屡次的select,在每次select之间有其余事务更新了咱们读取的数据并提交了,那就出现了不可重复读,即:重复读时,会出现数据不一致问题,后面咱们会讲解超支现象,就是这种引发的。

REPEATABLE READ

READ COMMITTED级别不一样的是MVCC版本的生成时机,即:一次事务中只在第一次select时生成版本,后续的查询都是在这个版本上进行,从而实现了可重复读。

可是由于MVCC的快照只对读操做有效,对写操做无效,举例说明会更清晰一点: 事务A依次执行以下3条sql,事务B在语句1和2之间,插入10条age=20的记录,事务A就幻读了。

1. select count(1) from user where age=20;

-- return 0: 当前没有age=20的

2. update user set name=test where age=20;

-- Affects 10 rows: 由于事务B刚写入10条age=20的记录,而写操做是不受MVCC影响,能看到最新数据的,因此更新成功,而一旦操做成功,这些被操做的数据就会对当前事务可见

3. select count(1) from user where age=20;

-- return 10: 出现幻读

REPEATABLE READ级别,能够防止大部分的幻读,但像前边举例读-写-读的状况,使用不加锁的select依然会幻读。

SERIALISABLE

大杀器,该级别下,会自动将全部普通select转化为select ... lock in share mode执行,即针对同一数据的全部读写都变成互斥的了,可靠性大大提升,并发性大大下降。

读-写,写-写均互斥。

4)总结:几类读异常

读-写-读,引发的异常

脏读:读取了脏数据(不存在的数据)。 事务一读 事务二写(未提交) 事务二读(脏数据) 事务二回滚

不可重复读:既能够读取修改的数据,也能够读取新增的数据(幻读)。 事务一读 事务二写(更新已提交) 事务二读(数据不一致,不可重复读)

幻读:仅能够读取新增的数据,可是没法读取修改的数据; 事务一读 事务二写(新增已提交) 事务二读(数据不一致,幻读)

附命令

查看表的加锁状况: select * from information_schema.INNODB_LOCKS; 事务状态 select * from information_schema.INNODB_TRX; 更多资料分享,问题咨询,能够入群讨论:375412858 请入群索要代码:

你可能感兴趣的:(mysql隔离级别mvcc)