面向面试编程:MySQL事务隔离级别

152 阅读9分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第6天,点击查看活动详情

面试官:说说什么是脏写和脏读

多个事务并发更新

对于我们的业务系统去访问数据库而言,他往往都是多个线程并发执行多个事务的,对于数据库而言,他会有多个事务同时执行,可能这多个事务还会同时更新和查询同一条数据,所以这里会有一些问题需要数据库来解决。

每个事务都会执行各种增删改查的语句,把磁盘上的数据页加载到buffer pool的缓存页里来,然后更新缓存页,记录redo log和undo log,最终提交事务或者是回滚事务,多个事务会并发干上述一系列事情。如果多个事务要是对缓存页里的同一条数据同时进行更新或者查询,此时会产生哪些问题呢?

这里实际上会涉及到脏写、脏读、不可重复读、幻读,四种问题。

脏写

这个脏写的话,他的意思就是说有两个事务,事务A和事务B同时在更新一条数据,事务A先把他更新为A值,事务B紧接着就把他更新为B值。所谓脏写,就是我刚才明明写了一个数据值,结果过了一会儿却没了!真是莫名其妙。而他的本质就是事务B去修改了事务A修改过的值,但是此时事务A还没提交,所以事务A随时会回滚,导致事务B修改的值也没了,这就是脏写的定义。

脏读

接着我们继续看坑爹的脏读问题。假设事务A更新了一行数据的值为A值,此时事务B去查询了一下这行数据的值,看到的值是不是A值?现在事务B可能还挺high的,拿着刚才查询到的A值做各种业务处理。大家知道,每个事务都是业务系统发出的,所以业务系统里的事务B此时肯定会拿到刚查出来的A值在做一些业务处理。但是接着坑爹的事情发生了,事务A突然回滚了事务,导致他刚才更新的A值没了,此时那行数据的值回滚为NULL值!然后事务B紧接着此时再次查询那行数据的值,看到的居然此时是NULL值?事务B此时简直欲哭无泪。所以这就是坑爹的脏读,他的本质其实就是事务B去查询了事务A修改过的数据,但是此时事务A还没提交,所以事务A随时会回滚导致事务B再次查询就读不到刚才事务A修改的数据了!这就是脏读。

无论是脏写还是脏读,都是因为一个事务去更新或者查询了另外一个还没提交的事务更新过的数据。因为另外一个事务还没提交,所以他随时可能会反悔会回滚,那么必然导致你更新的数据就没了,或者你之前查询到的数据就没了,这就是脏写和脏读两种坑爹场景。

一个事务多次查询一条数据

 面试官:那什么是不可重复读?

不可重复读

假设我们有一个事务A开启了,在这个事务A里会多次对一条数据进行查询,然后呢,另外有两个事务,一个是事务B,一个是事务C,他们俩都是对一条数据进行更新的。

然后我们假设一个前提,就是比如说事务B更新数据之后,如果还没提交,那么事务A是读不到的,必须要事务B提交之后,他修改的值才能被事务A给读取到,其实这种情况下,就是我们首先避免了脏读的发生。因为脏读的意思就是事务A可以读到事务B修改过还没提交的数据,此时事务B一旦回滚,事务A再次读就读不到了,那么此时就会发生脏读问题。

此时会有另外一个问题,叫做不可重复读。假设缓存页里一条数据原来的值是A值,此时事务A开启之后,第一次查询这条数据,读取到的就是A值。接着事务B更新了那行数据的值为B值,同时事务B立马提交了,然后事务A此时可是还没提交!此时事务A是没提交的,他在事务执行期间第二次查询数据,此时查到的是事务B修改过的值,B值,因为事务B已经提交了,所以事务A可以读到的了。紧接着事务C再次更新数据为C值,并且提交事务了,此时事务A在没提交的情况下,第三次查询数据,查到的值为C值。

其实要说没问题也可以是没问题,毕竟事务B和事务C都提交之后,事务A多次查询查到他们修改的值,是ok的。

但是你要说有问题,也可以是有问题的,就是事务A可能第一次查询到的是A值,那么他可能希望的是在事务执行期间,如果多次查询数据,都是同样的一个A值,他希望这个A值是他重复读取的时候一直可以读到的!他希望这行数据的值是可重复读的!但是此时,明显A值不是可重复读的,因为事务B和事务C一旦更新了值并且提交了,事务A会读到别的值,所以此时这行数据的值是不可重复读的!此时对于你来说,这个不可重复读的场景,就是一种问题了!

如果你期望的是可重复读,但是数据库表现的是不可重复读,让你事务A执行期间多次查到的值都不一样,都是别的提交过的事务修改过的值,那么此时你就可以认为,数据库有问题,这个问题就是“不可重复读”的问题!

面试官:那幻读是什么情况?

幻读

一个事务A,先发送一条SQL语句,里面有一个条件,要查询一批数据出来,比如“select * from table where id>10”,类似这种SQL,然后呢,他一开始查询出来了10条数据。

接着这个时候,别的事务B往表里插入了几条数据,而且事务B还提交了,此时多了几行数据出来。接着事务A此时第三次查询,再次按照之前的一模一样的条件执行“select * from table where id>10”这条SQL语句,由于其他事务插入了几条数据,导致这次他查询出来了12条数据。

于是此时事务A开始怀疑自己的双眼了,为什么一模一样的SQL语句,第一次查询是10条数据,第二次查询是12条数据?难道刚才出现了幻觉?导致我刚才幻读了?这就是幻读这个名词的由来。

幻读指的就是你一个事务用一样的SQL多次查询,结果每次查询都会发现查到了一些之前没看到过的数据,注意,幻读特指的是你查询到了之前查询没看到过的数据!此时就说你是幻读了。

面试官:那针对上述问题,要使用哪些隔离级别呢?

SQL标准中的事务隔离级别

在SQL标准中规定了4种事务隔离级别,就是说多个事务并发运行的时候,互相是如何隔离的,从而避免一些事务并发问题。这4种级别包括:

  • read uncommitted(读未提交):不允许发生脏写的。也就是说,不可能两个事务在没提交的情况下去更新同一行数据的值,但是在这种隔离级别下,可能发生脏读,不可重复读,幻读。
  • read committed(读已提交):这个级别下,不会发生脏写和脏读,也就是说,人家事务没提交的情况下修改的值,你是绝对读不到的!但是呢,可能会发生不可重复读和幻读问题,因为一旦人家事务修改了值然后提交了,你事务是会读到的,所以可能你多次读到的值是不同的!
  • repeatable read(可重复读):这个级别下,不会发生脏写、脏读和不可重复读的问题,因为你一个事务多次查询一个数据的值,哪怕别的事务修改了这个值还提交了,没用,你不会读到人家提交事务修改过的值,你事务一旦开始,多次查询一个值,会一直读到同一个值!但是他还是会发生幻读的。
  • serializable(串行化):这种级别,根本就不允许你多个事务并发执行,只能串行起来执行,先执行事务A提交,然后执行事务B提交,接着执行事务C提交,所以此时你根本不可能有幻读的问题,因为事务压根儿都不并发执行!但是这种级别一般除非脑子坏了,否则更不可能设置了,因为多个事务串行,那数据库很可能一秒并发就只有几十了,性能会极差的。

MySQL对事务隔离级别的支持

MySQL默认设置的事务隔离级别,都是RR级别的,而且MySQL的RR级别是可以避免幻读发生的。这点是MySQL的RR级别的语义跟SQL标准的RR级别不同的,毕竟SQL标准里规定RR级别是可以发生幻读的,但是MySQL的RR级别避免了!

也就是说,MySQL里执行的事务,默认情况下不会发生脏写、脏读、不可重复读和幻读的问题,事务的执行都是并行的,大家互相不会影响,我不会读到你没提交事务修改的值,即使你修改了值还提交了,我也不会读到的,即使你插入了一行值还提交了,我也不会读到的,总之,事务之间互相都完全不影响!

MySQL里的MVCC机制,就是多版本并发控制隔离机制,依托这个MVCC机制,就能让RR级别避免不可重复读和幻读的问题。