目录
事务的隔离性
引入
测试
读未提交
脏读
读提交
不可重复读
属于问题吗?
例子
可重复读
幻读
串行化
原理
总结
事务的隔离性
引入
当我们让两个客户端共同执行begin语句时,就开始了两个事务并发访问
- 在这个过程中,可能会出现sql交叉的问题
但我们不希望因为交叉产生问题,又因为事务是原子的
- 那么,我们就不应该让客户端看见其他客户端在事务执行过程中产生的若干中间结果
- 相当于是原子操作的中间过程被其他人看见了,这违背了事务的隔离性
我们下面的测试都是针对执行中的事务
- 因为事务是有执行过程的
- 我们为了解决在这个过程中出现的并发等问题,才设置了各种事务的特性
接下来,我们用边实验,边介绍的方式,来认识这四个隔离级别的特点和问题
测试
读未提交
当我们使用读未提交的方式,会发现在事务还没有提交的时候,操作结果就会被其他客户端看见
- 这就是读 未提交的表现,但这样的表现并不合理
脏读
一个事务在执行过程中,可以读取到另一个执行中的事务更新了但还未提交的数据
- 一般不建议采用这种方式
- 因为脏读现象并不合理,它完全破坏了事务的隔离性
- 还没提交就看见了,那岂不是会读到很多垃圾数据,要是别人一个回滚,之前读到的就是不真实的数据
读提交
更换两个客户端的隔离级别都为读提交后,还是启动两个事务,依然在一方插入数据,另一方查看时发现还是原始数据:
虽然其他客户端查看不到,但实际上已经被写入数据库了
当客户端1的事务提交后,客户端2就可以查看到被客户端1更新后的数据了:
- 注意,这里是在客户端2启动的[事务还没有结束]的时候进行查看的
这就是读 提交的隔离级别,只要客户端的事务一提交,就能被其他客户端看见,即使是在事务执行过程中
- 也就是说,其他客户端看见的不一定是最新的数据
看不见最新数据,就一定是错的吗?
- 不是
- 就像以前的人们无法在那时就看见现在的世界,出生和死亡时间的限制,就注定了每个人看见的世界可能是不一样的
- 每个客户端看到的数据不一样,才是合理的
不可重复读
在客户端2启动的事务还未提交时,可以看到其他客户端的提交结果
- 就造成了,在一个事务内部,调用同样的select语句,会出现不同的数据 -- 不可重复读
- 这里的不可重复读,重点在于修改和删除(原因在后面介绍)
在多个事务并发执行时,无法确保查看的数据不变,导致无法重复调用select
- 因为查看到的数据是不一样的
- 而这些不一样的数据可能会影响上层的决策
属于问题吗?
有这样的现象没错,但这属于问题吗?
- 理论上确实是一个问题
理论上事务是不会被其他事务干扰的
- 所以不应该会出现查询出的结果不同
- 提交后的结果可以被看见,但不能被正在执行的事务看见
- 也就是读提交会破坏客户端2的隔离性
例子
假设有一个场景,某公司需要根据工资来决定年终奖
- 那么就需要找到不同工资范围的员工
于是员工1将这件工作封装成了一个事务来完成:
如果此时临时有员工的工资发生变动(tom的工资从3200变成4500),需要一个员工2去完成修改:
- (单sql本质上也是事务,写成这样更明确一些)
然后就是很巧的,在员工1在执行第二条sql到第三条sql之间,员工2完成了修改并提交
- 那么员工1查出来的结果中,会出现两个tom -- 因为工作在读提交的隔离级别下,一旦有事务完成,其他客户端都可以看见修改后的结果
而这不符合常理
- 因为一个人只有一个工资
- 但因为不可重复读的特性,导致在事务内部进行多次查找时出现了不同结果,从而对业务带来了影响
对员工1来说
- 在他进行筛选时,员工工资就不应该发生变化了,不然还怎么筛选呢?
- 所以,读提交的隔离级别是有问题的,当然,在读未提交中这样的问题更为严重
所以,一定要保证事务的隔离性,不应该被其他执行的事务影响
- 所以,我们依然不推荐使用读提交的隔离级别,它依然没有保证事务的隔离性
- 于是我们有了第三种隔离级别,来解决这个问题
可重复读
继续更改隔离级别,然后重复前面的实验
只有当自己结束事务后,才能看见其他客户端更新的数据:
符合可重复读的特点
幻读
但其他数据库在可重复读的情况时,无法屏蔽insert后带来的数据
- 因为隔离性是通过对数据加锁实现的,而insert插入的数据原本并不存在,所以无法屏蔽
- 所以就会导致多次查找时,会出现新数据,就如同产生了幻觉(就明明已经是可重复读了,却还是有不一样的数据) -- 幻读现象
- 所以,幻读的重点在于新增
也可以将幻读问题和不可重复读放在一起理解
- 本质都是会导致多次查找的结果不同
而这里,mysql很显然是解决了幻读问题的,它真正实现了,在多个事务并发执行时互相不影响
- 解决方法 -- next-key锁
- 所以mysql以可重复读隔离级别作为默认级别
串行化
将所有事务串行化
- 也就是要让事务按照到来顺序排队
- 同一时刻只有一个事务在运行,可以保证mysql数据的绝对完整性和安全性
但这样会带来严重的效率问题
修改隔离级别:
依然是启动两个事务,都在查询的时候没有问题:
如果其中一方对数据做了修改/进行了查询,就会拦住另一个的修改操作(阻塞住,不会执行sql语句),并设置超时时间:
直到那一个提交事务后,才会让被拦住的那个执行:
只有在提交事务后,其他事务才能看见更新的数据
原理
当其中一个事务在进行查询时,会在持有锁的状态下访问表
- 如果其他事务也想要查询,可以
- 但其他事务如果想要对表进行修改,只能先在等待队列中等待,直到持有锁的客户端结束事务
总结
隔离级别越严格,安全性越高,并发效率越低
- 具体使用哪种,由业务需求来决定
- mysql只需要提供多种隔离级别让用户做选择就好