17@MySQL数据库读现象详解(脏读、幻读、不可重复读)

MySQL数据库读现象

一、数据库的读现象

数据库管理软件的“读现象”指的是当多个事务并发执行时,在读取数据方面可能碰到的问题,包括有脏读、不可重复读和幻读

#读现象
    对于一些数据库管理软件会自带相应的机制去解决脏读、不可重复读、幻读等问题,因为这些自带的机制,下述的一些实验现象可能在某一数据库管理软件的默认机制下并不成立,
    即我们并不能在所有数据库管理软件中看到所有的读现象。
    所以此处我们暂且抛开具体的某个数据库管理软件的默认机制的干扰,暂时假设没有附加任何机制为前提,单纯地去理解数据库的读现象

1、脏读(dirty read):读取未提交数据

一个事务读到了另一个未提交事务修改过的数据
在这里插入图片描述
会话B开启一个事务,把id=1的name为武汉市修改成温州市,此时另外一个会话A也开启一个事务,读取id=1的name,此时的查询结果为温州市,会话B的事务最后回滚了刚才修改的记录,这样会话A读到的数据是不存在的,这个现象就是脏读。(脏读只在读未提交隔离级别才会出现)

脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据
在这里插入图片描述

【案列详解】

事务一:查询一条记录, id 为 2 的 age 为 18
事务一:查询后,事务二对该条数据进行了 age=22 的更改, 但是没有进行提交
事务一:有进行查询操作, 查询到修改后的记录 age=22
事务二:进行了回滚操作, 记录中就没有了 id=2, age=22 的记录了, 所以, 事务1读到了一条脏数据(也叫无效数据)

在这里插入图片描述

2、不可重复读取 (nonrepeatable read):前后多次读取,数据内容不一致

是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。(即不能读到相同的数据内容)

一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交后,该事务都能查询得到最新值。(不可重复读在读未提交和读已提交隔离级别都可能会出现)
在这里插入图片描述
会话A开启一个事务,查询id=1的结果,此时查询的结果name为武汉市。接着会话B把id=1的name修改为温州市(隐式事务,因为此时的autocommit为1,每条SQL语句执行完自动提交),此时会话A的事务再一次查询id=1的结果,读取的结果name为温州市。会话B再此修改id=1的name为杭州市,会话A的事务再次查询id=1,结果name的值为杭州市,这种现象就是不可重复读

例如,一个编辑人员两次读取同一文档,但在两次读取之间,作者重写了该文档。当编辑人员第二次读取文档时,文档已更改。原始读取不可重复。如果只有在作者全部完成编写后编辑人员才可以读取文档,则可以避免该问题
在这里插入图片描述

【案列详解】

事务一:查询一条 id=2 的记录
事务二:随之对 id 为 2 的记录进行了更改, 并且进行了提交
事务一:再次查询 id=2 的记录, 发现第二次查询的记录已经发生了改变, 这个就叫做不可重复读

#在基于锁的并发控制中不可重复读现象发生在当执行 select 操作时没有获得读锁(read locks)或者 select 操作执行完后马上释放了读锁; 多版本并发控制中当没有要求一个提交冲突的事务回滚也会发生不可重复读现象

在这里插入图片描述

3、幻读(nonrepeatable read):前后多次读取,数据总量不一致

幻读是不可重复读的一种特殊情况, 当事务没有获取范围锁的情况下执行 select 操作可能会发生幻读现象

一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另一个事务插入的记录也读出来。(幻读在读未提交、读已提交、可重复读隔离级别都可能会出现)
在这里插入图片描述
会话A开启一个事务,查询id>0的记录,此时会查到name=武汉市的记录。接着会话B插入一条name=温州市的数据(隐式事务,因为此时的autocommit为1,每条SQL语句执行完自动提交),这时会话A的事务再以刚才的查询条件(id>0)再一次查询,此时会出现两条记录(name为武汉市和温州市的记录),这种现象就是幻读

一般解决幻读的方法是增加范围锁RangeS,锁定检锁范围为只读,这样就避免了幻读
事务一执行了两次相同的查询操作。但是两次操作中间事务二向数据库中增加了一条符合事务一的查询条件的数据,导致幻读在这里插入图片描述

【案列详解】

事务一:读取或修改了指定 where 子句过滤出来的结果集
事务二:插入了一条新记录, 并且该条记录满足 where 子句的过滤条件
事务一:使用相同的查询再次进行检索, 发现看到了事务二刚刚插入的记录, 但刚刚明明已经使用 where 子句过滤修改了这些记录, 就好像"幻觉’'一样, 这对于事务一来说是突然出现的, 这就是幻读

在这里插入图片描述

二、不可重复读与幻读之间的区别

幻读和不可重复读都是指的一个事务范围内的操作受到其他事务的影响
区别:幻读是重点在插入和删除,不可重复读重点在修改

1)#~~不可重复读~~ 是读取了其他事务更改的数据,针对update操作
解决:使用行级锁,锁定该行,事务A多次读取操作完成后才释放该锁,这个时候才允许其他事务更改刚才的数据。



2)#~~幻读~~ 是读取了其他事务新增的数据,针对insert与delete操作
解决:使用表级锁,锁定整张表,事务A多次读取数据总量之后才释放该锁,这个时候才允许其他事务新增数据。

三、事务的隔离级别

MySQL的事务隔离级别一共有四个,分别是读未提交、读已提交、可重复读以及可串行化

MySQL的隔离级别的作用就是让事务之间互相隔离,互不影响,这样可以保证事务的一致性。
隔离级别比较:可串行化>可重复读>读已提交>读未提交
隔离级别对性能的影响比较:可串行化>可重复读>读已提交>读未提交

由此看出,隔离级别越高,所需要消耗的MySQL性能越大(如事务并发严重性),为了平衡二者,一般建议设置的隔离级别为可重复读,MySQL默认的隔离级别也是可重复读

1、读未提交(READ UNCOMMITTED)

在这里插入图片描述在读未提交隔离级别下,事务A可以读取到事务B修改过但未提交的数据。
可能发生脏读、不可重复读和幻读问题,一般很少使用此隔离级别

2、读已提交(READ COMMITTED)

在这里插入图片描述
在读已提交隔离级别下,事务B只能在事务A修改过并且已提交后才能读取到事务B修改的数据
读已提交隔离级别解决了脏读的问题,但可能发生不可重复读和幻读问题,一般很少使用此隔离级别

3、可重复读(REPEATABLE READ)

在这里插入图片描述
在可重复读隔离级别下,事务B只能在事务A修改过数据并提交后,自己也提交事务后,才能读取到事务B修改的数据。
可重复读隔离级别解决了脏读和不可重复读的问题,但可能发生幻读问题。

提问:为什么上了写锁(写操作),别的事务还可以读操作?
因为InnoDB有MVCC机制(多版本并发控制),可以使用快照读,而不会被阻塞

4、可串行化(SERIALIZABLE)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
各种问题(脏读、不可重复读、幻读)都不会发生,通过加锁实现(读锁和写锁)

在这里插入图片描述
在这里插入图片描述

5、隔离级别的实现原理

使用MySQL的默认隔离级别(可重复读)来进行说明。

每条记录在更新的时候都会同时记录一条回滚操作(回滚操作日志undo log)。
同一条记录在系统中可以存在多个版本,这就是数据库的多版本并发控制(MVCC)。
即通过回滚(rollback操作),可以回到前一个状态的值

四、解决方案

其实,脏写、脏读、不可重复读、幻读,都是因为业务系统会多线程并发执行,每个线程可能都会开启一个事务,每个事务都会执行增删改查操作。
然后数据库会并发执行多个事务,多个事务可能会并发地对缓存页里的同一批数据进行增删改查操作,
于是这个并发增删改查同一批数据的问题,可能就会导致我们说的脏写、脏读、不可重复读、幻读这些问题。

所以这些问题的本质,都是数据库的多事务并发问题

那么为了解决多事务并发带来的脏读、不可重复读、幻读等读等问题,数据库才设计了锁机制、事务隔离机制、MVCC 多版本隔离机制,用一整套机制来解决多事务并发问题

【MySQL解决幻读】

在高并发的情况下,我们要保证数据的一致性和事务与事务之间的隔离性
1)多版本控制(MVCC)
2)Next-Key锁(当前读)

【多版本控制(MVCC)的原理】

InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的,这两个列分别保存了这个行的创建时间,一个保存的是行的删除时间。
这里存储的并不是实际的时间值,而是系统版本号(可以理解为事务的ID),每开始一个新的事务,系统版本号就会自动递增,事务开始时刻的系统版本号会作为事务的ID

【Next-Key锁(当前读)的原理】

将当前数据行与上一条数据和下一条数据之间的间隙锁定,保证此范围内读取数据是一致的

#Next-Key锁又分为两种:
   1)记录锁(Record Locks):加在索引上的锁,记录锁是 封锁记录,记录锁也叫行锁
         列:SELECT * FROM `test` WHERE `id`=1 FOR UPDATE;
            它会在 id=1 的记录上加上记录锁,以阻止其他事务插入,更新,删除 id=1 这一行
  
记录锁、间隙锁、临键锁都是排它锁

   2)间隙锁(Gap Locks):加在索引之间的锁,间隙锁是封锁索引记录中的间隔,或者第一条索引记录之前的范围,又或者最后一条索引记录之后的范围
   
posted @ 2021-07-15 20:56  ଲ一笑奈&何  阅读(355)  评论(0编辑  收藏  举报