一文详解幻读、脏读和不可重复读
一、简介
经常有面试官提出这么一个问题:什么是脏读、不可重复读和幻读?
关于这个问题,我们还得从数据库的管理系统说起,当数据库管理系统在写入或者更新数据的过程中,为了保证数据是正确可靠的,需要满足四个特性:原子性、一致性、隔离性和持久性,简称 ACID !
● Atomicity(原子性):一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,能被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
● Consistency(一致性):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入之前和写入之后的数据必须完全符合预期设定的结果。
● Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
● Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
例如以银行转账为例,从原账户扣除金额,以及向目标账户添加金额,这两个阶段的操作,被视为一个完整的逻辑过程,不可拆分,简单的说,要么全部成功,要么全部失败!这个过程被称为一个事务,具有 ACID 四个特点!
说了这么多,跟我们今天要说的脏读、不可重复读和幻读有什么关系呢?
我们都知道,当下主流的数据库,都支持多个事务并发执行,当一个事务在写入数据,另一个事务也要读这条数据,会出现哪些问题?当一个事务在写入数据,另一个事务也要写入这条数据,又会发生什么哪些问题?
当多个事务并发处理同一条数据时,如果事务隔离性不合理,就会产生我们今天要介绍的内容,具体的说就是:脏读、不可重复读和幻读!
在事务的四个特性里面,其中隔离性总共分为四种级别:由低到高依次为 Read uncommitted 、Read committed 、Repeatable read 、Serializable ,这四个级别可以逐个解决脏读 、不可重复读 、幻读等这几类问题。
● read uncommitted:俗称读未提交,指的是一个事务还没提交时,它做的变更就能被别的事务看到。
● Read committed:俗称读提交,指的是一个事务提交之后,它做的变更才会被其他事务看到。
● Repeatable read:俗称可重复读,指的是一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的,同时当其他事务在未提交时,变更是不可见的。
● Serializable:俗称串行化,顾名思义就是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
不同的隔离级别,产生的结果是不一样,下面我们一起来具体分析分析!
二、场景分析
2.1、脏读
所谓的脏读,指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会保存到数据库,也可能会回滚,不保存到数据库。当这个数据发生了回滚,就意味着这个数据不存在,这就是脏读!
脏读最大的问题就是可能会读到不存在的数据。比如在上图中,事务 B 的更新数据被事务 A 读取,但是事务 B 回滚了,更新数据全部还原。也就是说事务 A 刚刚读到的数据并没有存在于数据库中。
从结果上看,事务 A 读出了一条不存在的数据,这个问题比较很严重!
当数据库的事务隔离级别为读未提交,就会发生脏读现象!
2.2、不可重复读
不可重复读,指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据出现不一致的情况。
比如上图,事务 A 两次读取同一数据,第一次读取结果为 1,当事务 B 修改了数据并提交,此时的事务 A 第二次读取结果为 2,两次读取结果不一致!
当数据库的事务隔离级别为读未提交、读提交时,就会发生不可重复读现象!
2.3、幻读
幻读和不可重复读,有点类似,但是表达的侧重点不一样。
例如事务 A 对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。此时,突然事务 B 插入了一条数据并提交了,当事务 A 提交了修改数据操作之后,再次读取全部数据,结果发现还有一条数据未更新,给人感觉好像产生了幻觉一样。这就是幻读!
当有别的事务,在插入或者删除同一条数据的时候,就容易产生幻读的现象!
当数据库的事务隔离级别为读未提交、读提交、可重复读时,就会发生幻读现象!
三、如何解决
为了解决上述问题,数据库通过锁机制来解决并发访问的问题。
以 Mysql 为例,根据锁定对象不同,分为:行级锁和表级锁;根据并发事务锁定的关系上看,分为:共享锁定和独占锁定。
共享锁定会防止独占锁定,但允许其他的共享锁定;而独占锁定既防止共享锁定也能防止其他独占锁定;为了更改数据,数据库在进行更改的行上施加了行级独占锁定,insert、update、delete和selsct for update语句都会隐式采用必要的行锁定,当冲突加剧,会上升到表级锁定,此时会影响到其他表的访问操作。
直接使用锁机制管理是很复杂的,基于锁机制,数据库给用户提供了不同的事务隔离级别,只要设置了事务隔离级别,数据库就会分析事务中的 sql 语句然后自动选择合适的锁,可以依次有效的解决脏读、不可重复读和幻读问题!
整体的来说,事务的隔离级别和数据库并发性是成反比的,隔离级别越高,并发性越低。
四、小结
本位主要围绕什么是脏读、不可重复读和幻读,进行了简单的介绍,如果有些地方理解不到位,欢迎网友指出!