演进式理解数据库四大隔离级别

首先了解下数据库四大隔离级别:读未提交,读已提交,可重复读,串行化

再了解数据库四大隔离级别下演化出来的三大问题:脏读,不可重复读,幻读

先从隔离级别最低的开始逐步演进。

读未提交

什么是读未提交,简单的理解就是一个事务可以读取到另一个事务中还没提交的数据,这时候就会出现脏读的情况,试想下,事务还没被提交的数据,有一定概率不是有效的数据,也许事务被回滚了呢,假设有两个事务,下面模拟下读未提交下的脏读情况:

时刻 事务1 事务2 备注
T0 开始事务 开始事务 初始化账户余额为1000
T1 读取余额为1000
T2 扣减100余额 此时余额为900
T3 读取余额为900 读取到事务1中还未被提交的余额数据900
T4 扣减100余额 此时余额为800
T5 提交事务 账户余额更新为800
T6 回滚事务 这里事务1被回滚了,所以不应该多扣事务1的100,真正的余额应该是900

由于第一类更新丢失已经被克服了,标准定义的所有隔离界别都不允许第一类丢失更新发生,所以事务1的回滚并不会导致数据变回1000,那么最终的结果就是800。所以这就出现脏读了,读到了未被提交的无效脏数据,影响了本身事务的运行结果。

为了解决脏读问题,接下来引入读已提交隔离级别

顾名思义,读已提交就是一个事务只可以读取到另一个事务已经提交的事务的数据,所以读未提交的情景变成了如下这样:

时刻 事务1 事务2 备注
T0 开始事务 开始事务 初始化账户余额为1000
T1 读取余额为1000
T2 扣减100余额 此时余额为900
T3 读取余额为1000 由于事务1还未提交 ,不能读取未提交的事务数据,所以读取到的 还是余额1000
T4 扣减100余额 此时余额为900
T5 提交事务 事务2提交,账户余额更新为900
T6 回滚事务 事务1被回滚了,操作不生效,所以实际上只扣减了事务2的100,最终结果应该为900

看起来使用读已提交就可以解决问题了,但是如果两个事务都是要提交的,读已提交可能就会出现如下情况:

时刻 事务1 事务2 备注
T0 开始事务 开始事务 初始化账户余额为100
T1 读取余额为100
T2 扣减100余额 此时余额为0
T3 读取余额为100 由于事务1还未提交 ,不能读取未提交的事务数据,所以读取到的余额还是100,所以事务2还认为有余额可以扣减
T4 提交事务 更新余额为0
T5 读取余额为0
T6 扣减100余额 这时候余额已经为0了,扣减失败

可以发现使用了读已提交这个隔离级别,一个事务只能读取到另一个事务已提交的事务 ,但是如果一个事务中做了多次查询,第一次查到的是另一个事务未提交的数据,第二次查询到的是另一个事务已经提交了的数据,就会导致一个事务中的多次查询结果出现不一致,当然了,个人感觉,多次查询结果不一致是可以理解的,基于最新的数据做操作,不是挺正常的想法么,所以Oralce的默认隔离级别才会是读已提交,那么是不是读已提交隔离级别真的就没问题了呢,参考下以下场景:

时刻 事务1 事务2 备注
T0 开始事务 开始事务 初始化账户余额为1000
T1 读取余额为1000
T2 扣减100余额 此时余额为900
T3 读取余额为1000
T4 提交事务 更新余额为900
T5 添加100余额 这时候没有去再做第二次查询,所以该事务中的余额还是1000,那么余额就变为1100
T6 提交事务 余额更新为1100,事务1扣减余额100的操作丢失了,真实余额一来一回应该是1000

以上场景是当两个事务更新同一个数据源,后完成的事务覆盖了先完成的事务,这种情况也被称为第二丢失更新,试想下,只要是允许能够出现如上操作顺序的隔离级别,那么就避免不了出现第二丢失更新的情况。

所以这时候引入可重复读的隔离级别

可重复读,这也是MySQL默认的事务隔离级别,它可以确保同一事务的多个实例在并发读取数据时,看到的数据行结果不变。

接下来模拟下可重复读的情景:

时刻 事务1 事务2 备注
T0 开始事务 开始事务 初始化账户余额为1000
T1 读取余额为1000
T2 扣减100余额 此时余额为900,事务1未提交,这里又做了更新操作,相应数据源会加上行锁
T3 尝试读取余额 读取失败,等待事务1提交
T4 提交事务 更新余额为900
T5 读取余额为900 此时事务1已经提交,实际余额为900
T6 扣减100余额
T7 提交事务 余额更新为800

通过行锁的配合,就让事务间对数据的更新显得有序和数据可见了。更新时,事务间数据是可见的,那么自然就可以避免出现第二更新丢失的情况。

不过可重复读只是针对的行数据做了限制,如果有事务读取的是记录行数,该隔离级别就显得有些无力了,参考下以下情景:

时刻 事务1 事务2 备注
T0 开始事务 开始事务 初始化账户余额为1000,交易记录为100笔
T1 读取余额为1000
T2 查询交易记录为100笔
T3 扣减100余额 此时事务1中账户余额为900
T4 插入一条交易记录 此时事务1中交易记录为101
T5 提交事务 更新账户余额为900,交易记录为101
T6 查询交易记录为101笔 此时事务2发现第二次查询结果多了一笔记录,在事务2看来这1笔是虚幻的

由于一个事务在另一个事务的执行过程中,新增或删除了数据并提交了事务,导致另一个事务两次读取数据记录行出现的条数不一致,就像是出现了虚幻的条数一样,以上就是幻读现象了,与不可重复读的现象相比,幻读针对的是多条记录,而不可重复读针对的是单条记录。解决不可重复读使用了行锁,而如果要解决幻读,就只能使用表锁了。

接下来就是最高的隔离级别,串行化,如果对性能要求不高,串行化是能够完全保证数据一致的。

看看串行化的场景:

时刻 事务1 事务2 备注
T0 开始事务 初始化账户余额为1000,交易记录为100笔
T1 读取余额为1000
T3 扣减100余额
T4 提交事务 余额更新为900
T5 开始事务
T6 读取余额为900
T7 扣减100余额
T8 提交事务 余额更新为800

如上场景锁表示的一样,串行化要求所有的sql都按照顺序执行,这自然可以克服其余隔离级别导致的种种问题,完全保证数据的一致性。

以下通过一个图表总结隔离级别和问题:

隔离级别 脏读 不可重复读 幻读
读未提交 YEW YES YES
读已提交 NO YES YES
可重复读 NO NO YES
串行化 NO NO NO

Ps:不可重复读是问题,可重复读是隔离级别,可以这样理解会比较好记,因为两次查询可能会读取到不同的数据,可能会影响事务的运行结果,所以这时候不允许重复读的,因此问题叫不可重复读,而可重复读就是用来处理不可重复读问题的隔离级别,让同一事务的多个实例在并发读取数据时,看到的数据行结果不变。

猜你喜欢

转载自blog.csdn.net/weixin_38106322/article/details/107868582