mysql数据库可重复读、幻读

事务基本特性ACID分别是:

原子性(Atomicity

指的是一个事务中的操作要么全部成功,要么全部失败。

一致性(Consistency)

指的是数据库总是从一个一致性的状态转换到另外一个一致性的状态。比如A转账给B100块钱,假设中间sql执行过程中系统崩溃A也不会损失100块,因为事务没有提交,修改也就不会保存到数据库。

隔离性(Isolation)

指的是一个事务的修改在最终提交前,对其他事务是不可见的。

持久性(Durability)

指的是一旦事务提交,所做的修改就会永久保存到数据库中。

 

ACID靠什么保证的呢

A原子性由undo log日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql;

C一致性一般由代码层面来保证;(如tcc两阶段提交,事务回滚)

I隔离性由MVCC来保证;(多版本并发控制,保存当前时间快照)

D持久性由内存+redo log来保证,mysql修改数据同时在内存和redo log记录这次操作,事务提交的时候通过redo log刷盘,宕机的时候可以从redo log恢复;

 

而隔离性有4个隔离级别,分别是

1、read uncommit 读未提交,可能会读到其他事务未提交的数据,也叫做脏读。

用户本来应该读取到id=1的用户age应该是10,结果读取到了其他事务还没有提交的事务,结果读取结果age=20,这就是脏读。

2、read commit 读已提交,两次读取结果不一致,叫做不可重复读。

不可重复读解决了脏读的问题,他只会读取已经提交的事务。

用户开启事务读取id=1用户,查询到age=10,再次读取发现结果=20,在同一个事务里同一个查询读取到不同的结果叫做不可重复读。

3、repeatable read 可重复复读,这是mysql的默认级别,就是每次读取结果都一样,但是有可能产生幻读。

4、serializable 串行,一般是不会使用的,他会给每一行读取的数据加锁,会导致大量超时和锁竞争的问题。

 

多版本并发控制MVCC

- MVCC是行级锁的一个变种,但是它在很多情况下避免了加锁操作,因此开销更低。虽然实现机制有所不同,但大都实现了非阻塞的读操作,写操作也只锁定必要的行。
- MVCC的实现,是通过保存数据在某个时间点的快照来实现的。也就是说,不管需要执行多长时间,每个事务看到的数据都是一致的。根据事务开始的时间不同,每个事务对同张表,同一时刻看到的数据可能是不一样的。
- InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存行的过期时间(或删除时间)。当然存储的并不是实际的时间值,而是系统版本号。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。
- MVCC只在可重复读和读提交的隔离级别生效。其它两个级别都不兼容

 

 

事务到底是隔离的还是不隔离的
innodb支持RC(读提交)和RR(可重复读)隔离级别实现是用的一致性视图(consistent read view)
事务在启动时会拍一个快照,这个快照是基于整个库的。基于整个库的意思就是说一个事务内,整个库的修改对于该事务都是不可见的(对于快照读的情况)。如果在事务内select t表,另外的事务执行了DDL t表,根据发生时间,要吗锁住要嘛报错
事务是如何实现的MVCC呢?

每个事务都有一个事务ID,叫做transaction id(严格递增)
事务在启动时,找到已提交的最大事务ID记为up_limit_id。
事务在更新一条语句时,比如id=1改为了id=2.会把id=1和该行之前的row trx_id写到undo log里。并且在数据页上把id的值改为2,并且把修改这条语句的transaction id记在该行行头。
再定一个规矩,一个事务要查看一条数据时,必须先用该事务的up_limit_id与该行的transaction id做比对
如果up_limit_id>=transaction id,那么可以看.如果up_limit_id<transaction id,则只能去undo log里去取。去undo log查找数据的时候,也需要做比对,必须up_limit_id>transaction id,才返回数据
什么是当前读,

由于当前读都是先读后写,只能读当前的值,所以认为当前读.会更新事务内的up_limit_id为该事务的transaction id
为什么RR能实现可重复读而RC不能,分两种情况

快照读的情况下,rr(可重复读)不能更新事务内的up_limit_id,而rc(读提交)每次会把up_limit_id更新为快照读之前最新已提交事务的transaction id,则rc(读提交)不能可重复读
当前读的情况下,rr(可重复读)是利用record lock+gap lock来实现的,而rc(读提交)没有gap,所以rc不能可重复读

 

 

幻读是什么,幻读有什么问题
幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。(如假设张三开启了事务,select了两次,第二次查出来的数据比第一次的多了,如同出现了幻觉一样)
可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。因此,幻读在“当前读”下才会出现

 

innodb的默认事务隔离级别是rr(可重复读)。它的实现技术是mvcc。基于版本的控制协议。该技术不仅可以保证innodb的可重复读,而且可以防止幻读。但是它防止的是快照读,也就是读取的数据虽然是一致的,但是数据是历史数据。如何做到保证数据是一致的(也就是一个事务,其内部读取对应某一个数据的时候,数据都是一样的),同时读取的数据是最新的数据。innodb提供了一个间隙锁的技术。也就是结合grap锁与行锁,达到最终目的。当使用索引进行插入的时候,innodb会将当前的节点和上一个节点加锁。这样当进行select的时候,就不允许加x锁。那么在进行该事务的时候,读取的就是最新的数据。

实现:
1. 快照读(snapshot read)

简单的select操作,其他的都属于当前读。

实现为mvcc,生成快照数据,实现可重复读,不会出现幻读。

2.当前读(current read)

select ... lock in share mode

select ... for update

insert

update

delete

在RR级别下,快照读是通过MVVC(多版本控制)和undo log来实现的,当前读是通过加record lock(记录锁)和gap lock(间隙锁)来实现的。
所以从上面的显示来看,如果需要实时显示数据,还是需要通过加锁来实现。这个时候会使用next-key技术来实现。

总结:在mysql中,提供了两种事务隔离技术,第一个是mvcc,第二个是next-key技术。这个在使用不同的语句的时候可以动态选择。不加lock inshare mode之类的就使用mvcc。否则使用next-key。mvcc的优势是不加锁,并发性高。缺点是不是实时数据。next-key的优势是获取实时数据,但是需要加锁。同时需要注意几点:1.事务的快照时间点是以第一个select来确认的。所以即便事务先开始。但是select在后面的事务的update之类的语句后进行,那么它是可以获取后面的事务的对应的数据。2.mysql中数据的存放还是会通过版本记录一系列的历史数据,这样,可以根据版本查找数据。

 

 

 

 

 

 

 next key lock

 

 

 Next-Key锁是索引记录上的记录锁和索引记录之前间隙上的间隙锁的组合。

假设一个索引包含值10、11、13和20。此索引可能的next-key锁包括以下区间:(-∞, 10],(10, 11],(11, 13],(13, 20],(20, ∞ ]

对于最后一个间隙,∞不是一个真正的索引记录,因此,实际上,这个next-key锁只锁定最大索引值之后的间隙。所以,Next-Key 的锁的范围都是左开右闭的。Next-Key Lock和Gap Lock一样,只有在InnoDB的RR隔离级别中才会生效。

 

Repeatable Reads能解决幻读

  很多人看过网上的关于数据库事务级别的介绍,会认为MySQL中Repeatable Reads能解决不可重复读的问题,但是不能解决幻读,只有Serializable才能解决。但其实,这种想法是不对的。

  因为MySQL跟标准RR不一样,标准的Repeatable Reads确实存在幻读问题,但InnoDB中的Repeatable Reads是通过next-key lock解决了RR的幻读问题的。因为我们知道,因为有了next-key lock,所以在需要加行锁的时候,会同时在索引的间隙中加锁,这就使得其他事务无法在这些间隙中插入记录,这就解决了幻读的问题。

  关于这个问题,引起过广泛的讨论,可以参考:https://github.com/Yhzhtk/note/issues/42 ,这里有很多大神发表过自己的看法。

MySQL的加锁原则

  Record Lock、Gap Lock和Next-Key Lock,但是并没有说明加锁规则。关于加锁规则,看了丁奇大佬的《MySQL实战45讲》中的文章之后理解的,他总结的加锁规则里面,包含了两个“原则”、两个“优化”和一个“bug”:

  原则 1:加锁的基本单位是 next-key lock。是一个前开后闭区间。原则 2:查找过程中访问到的对象才会加锁。优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。

 

 

rr级别下当前读加锁问题,能否解决幻读问题参考更多其他文章。

参考:https://www.cnblogs.com/cat-and-water/p/6427612.html

    https://mp.weixin.qq.com/s/aG3f-bqEof3ww156T1fBzg

    https://qinxuewu.github.io/docs/#/2019/MySQL%E5%AE%9E%E6%88%9845%E8%AE%B2%E7%AC%94%E8%AE%B0?id=%e5%b0%8f%e7%bb%93

    https://mp.weixin.qq.com/s/mvG0wVr2kpbM8iqfZnimiQ

 

posted @ 2021-09-29 23:19  炼金术士0z  阅读(1778)  评论(0编辑  收藏  举报