1. 概述
对于业务系统而言,往往都是多个线程并发执行多个事务的,对于数据库而言,它会多个事务同时执行,可能多个事务同时更新和查询同一条数据,这就导致了一些问题。
2. 产生的问题
2.1 脏写
多个事务同时更新一条数据,比如事务A和事务B,事务A先把某个值(初始值为NULL)更新为A值,事务B紧接着把他更新为B值。
同时,在A值更新之后会记录一条 undo log日志,大概就是:更新之前这行数据的值为NULL,主键为XX。
如果当B更新完了数据的值为B后,结果此时事务A突然回滚了,那么就会用它的 undo log 日志去回滚,直接就会把那行数据的值更新回之前的NULL 值。
但是对于事务B来说,结果值也没有了,这就是脏写。
脏写的本质就是事务B去修改了事务A修改的值,但是此时事务A还没提交,所以事务A随时会回滚,导致事务B修改的事务的值页没有了。
2.2 脏读
脏读的本质就是事务B去查询了事务A修改的值,但是此时事务A还没提交,所以事务A随时会回滚导致B再次查询就读不到刚才事务A修改的数据了。
2.3 不可重复读
假设的前提就是事务A只能在事务B提交之后读取到他修改的数据,所以不会产生脏读。但是,它会产生另外一个问题,叫做不可重复读。
假设缓存页里一条数据原来的值是A值,此时事务A开启之后,第一次查询这条数据,读取到的就是A值,如下图所示:
接着事务B更新了那行数据的值为B值,同时事务B立马提交了,然后此时事务A还没有提交,他在事务执行期间第二次查询数据,此时查到的是事务B修改后的值,B值。
紧接着事务C再次更新数据为C值,并且提交事务可,此时事务A在没有提交的情况下,第三次查询数据,查到的值为C值。
如果你期望的是可重复读,但是数据库表现的是不可重复读,让你事务A执行期间多次查到的值都不一样,都是别的提交过的事务修改过的值,那么此时数据库对于你来说就是有问题,这个问题就是“不可重复读”问题。
2.4 幻读
幻读就是值一个事务用一样的SQL多次查询,结果每次查询都会查询到一些之前没看到过的数据。
3. SQL标准的4种事务隔离级别
在SQL 标准中规定了4中事务隔离级别,就是说多个事务并发运行的时候,互相是隔离的,从而避免了一些事务并发的问题。
这4种级别包括了: red uncommitted(读未提交),read committed(读已提交),repeatable read(可重复读),serializable(串行化)。
3.1 red uncommitted(读未提交)
不允许发生脏写,也就是说,不可能两个事务在没提交的情况下去更新同一行数据的值,但是在这种隔离级别下,可能发生脏读,不可重复读和幻读。(不会设置)
3.2 read committed(读已提交)
B事务没有提交的情况下修改的值,A事务是绝对读不到的,但是,可能会发生不可重复度和幻读的问题。因为一旦B事务修改了值然后提交上去了,A事务读到,可能多次读到的值是不同的。
3.3 repeatable read(可重复读)
事务一旦开始,多次查询一个值,会一直读到同一个值。可能出现幻读的问题。
3.4 serializable(串行化)
不允许事务并发执行,只能串行起来执行,先执行事务A提交,然后执行B事务提交,所有问题都能避免。(不会设置)
4. MySQL 如何支持 4 种事务隔离级别
MySQL 默认设置的事务隔离级别,都是RR级别的,而且MySQL的RR级别是可以避免幻读发生的。也就是说MySQL 里执行的事务,默认情况下时不会发生脏读、脏写、不可重复读和幻读的问题,且事务的执行都是并行的。
假如要修改MySQL 默认的事务隔离级别,通过以下的命令。
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level
level 的值可以是 REPEATABLE READ、READ COMMITED、READ UNCOMMITTED、SERIALIZABLE 这几种级别。
5. Spring 怎么支持事务
在 Spring @Transactional注解里有一个isolation 参数,里面可以设置事务的隔离级别。具体的设置方法如下:
@Transactional(isolation=lsolation.DEFAULT) ,默认的值就是DEFAULT,意思就是数据库默认的是什么级别,就是什么级别。
也可以改成lsolation.READ_COMMITTED、lsolation.REPEATABLE_READ、lsolation.SERIALIZABLE 几个级别。