数据库事务(简称:事务)是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。
当多个用户并发操作数据库时,数据库为每一个用户开启不同的事务。这些事务如果不加以隔离,会产生一些问题。看下面的例子:
脏读 | 事务A | 事务B |
前提 | Amy账户余额是100 | |
时间1: B事务开始 | Bob给自己老妈转500,结果转给了Amy,这个事务做了,但未提交 | |
时间2: A事务开始,并完成 | 如果Amy可以读取未提交的数据,这时候她会查到余额为600 | |
时间3: B事务回滚 | Bob意识到自己的错误,回滚刚才的事务 | |
总结 | 100才是Amy真实的余额,600为脏数据读取的产物,是实实在在的误导。 |
不可重复读 | 事务A | 事务B |
前提 | Amy账户余额是100 | |
时间1: A事务开始 | Amy查询到自己的余额是100 | |
时间2: B事务开始,并完成 | Bob给Amy转了500 | |
时间3: A事务重复,并完成 | Amy查询到自己的余额是600 | |
总结 |
在事务A存续期间,针对相同的数据做了两次相同的查询,但数额不同(尽管数额都是当时时间点真实的余额)。 知道真相的也就算了,不知道的以为自己被欺负/骗/耍了。 |
幻读 | 事务A | 事务B |
前提 | Amy在某app买了三份基金,当前金额分别是300,600,900 | |
时间1: A事务开始 | Amy查询,当前持有金额 >500 的基金个数为多少?结果返回2个 | |
时间2: B事务开始,并完成 | 三个产品分别被扣了管理费100块,成了100,500,800 | |
时间3: A事务重复,并完成 | Amy查询,当前持有金额 >500 的基金个数为多少?结果返回1个 | |
总结 |
在事务A存续期间,针对同一批数据做了两次相同的检索,但得结果不同(尽管检索出来的数据在当时时间点是真实的)。 知道真相的也就算了,不知道的以为自己被欺负/骗/耍了。 |
归纳一下这些问题的特点:
问题 | 特点1 | 特点2 | 特点3** | |
脏读 | Dirty read |
某事务A读取了另一事务B未提交的数据 注:未提交意味着之后会有提交和回滚两个状态,回滚才是导造成A读数据前后不一致的根本原因;但一般认为,只要是A读了B未提交的数据,就是脏读 |
||
不可重复读 | Non-repeatable read | 某事务A读取了另一事务B已提交的数据 | A查询的是某一个数据项 | 重点在于更新修改数据 |
幻读 | Phantom read | 某事务A读取了另一事务B已提交的数据 | A查询的是一批数据整体(如个数) | 重点在于新增或删除数据 |
对于特点3**来说,还有一种解释:
不可重复读重点在于update和delete,而幻读的重点在于insert。如果用锁机制来解释的话,即想要避免“不可重复读”,在事务A第一次读数据的时候,把这些数据加锁,就可以阻止update和delete操作,其他事物无法改写数据。但这个方法不能阻止另一个事务B进行insert操作,之后A会发现莫名其妙多了一条数据,即发生了幻读。幻读需要靠Serializable隔离级别来避免(即读用读锁,写用写锁,读锁和写锁互斥,但会极大的降低数据库的并发能力),所以说,不可重复读和幻读,从锁机制角度来说,区别比较大,也可以认为,从控制的角度来看, 两者的区别就比较大。
不可重复读, 只需要锁住满足条件的记录
幻读, 要锁住满足条件及其相近的记录
其实,除了脏读,不可重复读,幻读,还有另一种问题,丢失更新,即两个事务同时读取同一条记录,A先修改记录,B也修改记录(B是不知道A修改过),B提交数据后B的修改结果覆盖了A的修改结果。