MySQL数据库的四种事务隔离级别以及事务的并发问题(脏读/不可重复读/幻读)
一、事务的四大特性(ACID)
1.原子性
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
2.一致性
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
3.隔离性
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
4.持久性
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
二、事务的并发问题
数据库事务无非就两种:读取事务(select)、修改事务(update,insert),在没有事务隔离的时候,多个事务同一时刻对同一数据操作可能会影响到最终结果,可能会产生以下四种情况:
1.两个更新事务同时修改同一条数据时,会发生很严重的情况,会造成更新数据的丢失。 2.一个更新事务在更新一条数据时,另一个读取事务读取了还没有提交的更新数据,这种情况会出现读取到脏数据。 3.一个读取事务在读取一条数据时,另一个更新事务同时修改了这条数据,这样会出现不可重复读。 4.一个读取事务在读取数据时,另一个事务插入了一条数据,这样可能多读出一条数据,出现幻读。
以上的这四种情况,前三种是对同一条数据的并发操作,对程序的结果可能产生致命影响。综合以上四种情况可以大致这样简单的理解:
1.修改时允许修改(丢失数据)
2.修改时允许读取(脏读)
3.读取时允许修改(不可重复读)
4.读取时允许插入(幻读)
因为不同的系统允许不同级别的情况,所以就出现了事务隔离这个东西,来允许我们设定数据库的并发行为。
总结如果不考虑事务的隔离性,会发生以下几种问题:
1.脏读(针对未提交的数据)
脏读是指在一个事务读取了另一个未提交的事务中的数据
2.不可重复读(针对修改操作) 不可重复读的意思就是前后两次读取的结果不一样(******)
不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了,导致了同一事务中查询到的结果不一样。
不可重复读和脏读的区别:脏读是某一事物读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据
3.幻读(针对增删操作)
幻读是一个事务在读取数据时,另一个事务插入了新数据,读取数据的时候会多读出数据,产生幻觉一样,这就是幻读
幻读和不可重复读都是读取了另一条已经提交的事务(脏读不是这样),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)
三、MySQL数据库的四种事务隔离级别
Read Uncommitted(读取未提交内容)
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)
Read Committed(读取提交内容) 因为读取了另一个事务提交的数据,所以前后查询数据是不一样的,所以造成不可重复读
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。
这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果
Repeatable Read(可重复读) 可重复读就是读取的数据和之前一样
这是MySQL的默认事务隔离级别,同一事务的多个实例在并发读取数据时,会看到同样的数据。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。
简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。
Serializable(可串行化)
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争
在MySQL中,实现了四种隔离级别,分别可能产生问题如下所示:
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
读未提交(read-uncommitted) | 是 | 是 | 是 |
读已提交(read-committed) | 否 | 是 | 是 |
可重复读(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,当然级别越高,执行效率就越低。
像Serializable这样的级别,就是以锁表的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。
在MySQL数据库中默认的隔离级别为Repeatable read (可重复读)
在MySQL数据库中,支持上面四种隔离级别,默认的为Repeatable read (可重复读);
而在Oracle数据库中,只支持Serializable (串行化)级别和Read committed (读已提交)这两种级别,其中默认的为Read committed级别
在MySQL数据库中查看当前事务的隔离级别: select @@tx_isolation;