数据库事务的四大特性---隔离性(MVCC)
今日份鸡汤:当你再找借口的时候,其实是你再酝酿失败了,当你在抱怨的时候,其实是你在宣布投降了~
定义:
在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
举个例子:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
在MySQL的众多存储引擎中,只有InnoDB支持事务,所以这里说的事务隔离级别指的是InnoDB下的事务隔离级别:
(1)读未提交(Read uncommitted):一个事务可以读取到另一个事务未提交的修改。这会带来脏读、幻读、不可重复读问题。
直白说就是读取到不正确的数据,因为另一个事务可能还没提交最终数据,但这个读事务就读取了中途的数据,这个数据可能是不正确的。(基本没用)
(2)读已提交(Read committed):一个事务只能读取另一个事务已经提交的修改。其避免了脏读,但仍然存在不可重复读和幻读问题。
直白说就是在一次事务之间,进行了两次读取,但是结果不一样,可能第一次id为1的人叫“李三”,第二次读id为1的人就叫了“李四”。因为读取操作不会阻止其他事务。
(3)可重复读(Repeatable read):同一个事务中多次读取相同的数据返回的结果是一样的。其避免了脏读和不可重复读问题,同时也解决了幻读。
幻读:当某个事务在读取某个范围的记录的时候,另外一个事务又在该范围插入了新的记录,当前事务再次读取这个范围的记录,发现比上次查询多了一条数据。
(4)串行化(Serializable):事务串行执行。避免了以上所有问题。
直白说就是读加共享锁,写加排他锁。这样读取事务可以并发,但是读写,写写事务之间都是互斥的,基本上就是一个个执行事务,所以叫串行化。
一般用读已提交和可重复读这两种,这两种的快照读,都是基于mvcc实现的。
先说几个概念,为了下面更好的理解:
当前读:就是说insert、update、delete语句执行之前,会先select,再执行insert、update、delete。简单说,就是先读一次,再执行更新语句。而且这个读,是读数据库最新的数据!
快照读:快照读是基于多版本并发控制的(MVCC),快照读读取到的数据不一定是数据库中最新的数据,有可能是历史版本的数据。
幻读问题到底存在不?:在RR隔离级别下,如果一个事务从头到尾就只有快照读,那么MVCC解决了幻读的问题。如果一个事务从头到尾只有当前读,那么MVCC通过间隙锁和临建锁也解决了幻读问题。但是如果一个事务既有快照读也有当前读,那么MVCC就解决不了幻读问题。
实现原理:
MVCC:多版本并发控制,主要是为了提高数据库的读写性能,让数据库在读写的时候不用去加锁,mvcc主要是处理读请求的,这个读指的是快照读,而不是当前读。快照读就是普通的select读。当前读就是一个悲观锁,需要去加锁,比如说执行update、insert、delete的时候,执行这些语句之前需要把数据先全部读出来。再比如说select for update 这种排它锁,隔离性就是通过加锁+mvcc实现的。
MVCC中的几个概念:
(1)undo log
(2)版本链:如下图,版本链是由undo log和回滚指针连接起来的。
image.png
(3)ReadView:是一个快照,版本链中有这么多版本,我并不知道去取哪个版本,readview的作用就是让你知道在版本链中去选择哪一条记录。
image.png
如何判断版本链中哪个版本可用?
image.png
MVCC如何实现RR、RC?
MVCC实现RC是因为每select生成一份readview。
MVCC实现RR是因为每事务生成一份readview。
RC不能解决可重复读和幻读的问题就在于每select生成一份readview,
RR将脏读、不可重复读、幻读都解决了,原因就是每事务只生成一份readview。
这是快照读解决幻读使用RR,当前读解决幻读使用的是间隙锁,间隙锁是锁住一段范围,比如说id>2,他会把id>2后面这段的范围全部都锁上,上锁之后就不存在幻读的问题,因为别人也不能往id>2后面再插入数据了,因为已经上了间隙锁,MySQL默认隔离级别是RR可重复读,在RR这个隔离级别之下是默认开启了间隙锁,所以在MySQL的innodb存储引擎里面是解决了幻读的问题。