隔离级别
-
Read-Uncommitted(读未提交):会产生
脏读
的问题.比如事务A增加了一条数据,此时事务B正好读到了这笔数据,但是在事务B读到数据之后,事务A发生了异常,数据回滚了,那么最终这条数据是没有写入数据库的.而事务B读到了数据库不存在的数据,这就称之为:脏读
.
ps:新手的困惑解答,我个人当时对于上面的描述觉得很困惑的一点就是:为什么事务B能读到事务A未提交的数据,未提交不是没写进数据库吗?其实是这样的,提交是指事务整体的提交,一个事务通常包括多步对数据库的操作,举个例子:有一张学生表,字段是学好,姓名,性别.假设表中有两条数据,一条是:001 张三 男
和002 李红 女
,事务A需要做两步操作,第一步是添加一条记录003 赵四 男
,第二部是将001 张三 男
修改为001 张三 女
.在事务A完成第一步,但是没完成第二步的时候,事务B执行了,事务B是查询整个表中的数据,此时事务B就查到了三条数据 :001 张三 男
,002 李红 女
,003 赵四 男
.但是紧接着事务A在执行第二步的时候,因为种种原因发生了异常,这时候就会发生数据的回滚,整体回到事务A执行之前的状态,也就是添加的数据003 赵四 男
会消失.(事务的原子性,此处看不懂的小伙伴可以百度一下Mysql事务的ACID四个特性). -
Read-Commited(读已提交):会产生
不可重复读
的问题.依旧举个例子:有一张学生表,表中有两条数据,001 张三 男
,002 李红 女
.假设事务A开启事务,然后执行两条查询语句,都是查询整张表的数据.执行第一条查询语句的时候,查到了两条数据001 张三 男
,002 李红 女
.此时有个事务B也对表进行操作,将001 张三 男
修改为了001 赵四 男
,并提交了数据.然后事务A执行第二条查询语句,此时查到的数据则变成了001 赵四 男
,002 李红 女
,然后事务A提交事务,并将查询结果展示给用户,用户就会发现两次读到的数据不一致.这就叫做不可重复读
.产生不可重复读
的原因就是事务A读取数据的时候,事务B对这些数据进行了删除或修改
. -
Repeatable-Read(可重复读):刚刚我们解释了
不可重复读
产生的原因是事务A读取数据的时候,事务B同时对事务A读取的这些数据进行了删除或修改
。如果我们可以让事务A读取数据的时候,其他事务不能对这些数据进行删除或修改
,那么是不是就可以实现可重复读
了?的确如此,实现可重复读
就需要上锁,将事务A读取的数据进行上锁,被上锁的数据其他事务不能进行删除或修改
。这种上锁的方式有两种,一种是悲观锁,一种是乐观锁。
悲观锁:悲观锁依靠数据库的机制来实现,语法是:select … from table for update.被选中的数据就会被事务A上锁,此时其他事务无法对这些数据进行上锁,也无法对这些数据进行删除或修改
的操作,只有事务A结束之后,这其他事务才能对这些数据进行删除或修改
,但是上锁期间其他事务对这些数据的查询
是允许的。那么事务A从开启事务进行上锁,到提交事务解除锁的期间,只要是同样的sql语句,不论查询多少次,查询到的结果都是一样的,这就叫做可重复读
。
乐观锁:悲观锁是依靠数据库本身的机制来实现的,而且上锁,解除锁是很耗费资源的行为,加重数据库的负担.由此产生了乐观锁的概念,乐观锁并不是通过对特定数据的上锁行为来达到可重复读
的效果的,而是通过MVCC(版本控制)
或CAS
来完成可重复读
效果。
比如mysql的InnoDB数据库引擎的MVCC
就是通过在一条数据后面加上两个隐藏字段来实现目的。一个字段是创建时间(事务ID),一个字段是删除时间(事务ID)。mysql中每开启一个事务,都会给这个事务分配一个自增的ID,所以没个事务的ID不会重复,而且是可以比较大小的。假设事务A的ID是3,事务A查询表中的数据,那么事务A只能查到创建时间ID≤3,而且删除时间ID≥3的记录。即事务A只能查到在A之前创建,而且在A之后删除的记录。实际并发中常用版本控制
是通过加一个version
字段来进行版本控制
,假设卖一件商品,这个会给商品加一个version
字段,每次交易之前会获取商品此时的version
,交易时会验证获取的version
是否等于交易时的version
,如果相等,则可以交易,并且交易完成后会将version
自增或自减(此处我们假设是自增1)。假设多个线程同时获取一个version
为10,那么只会有一个最快的线程能够验证通过并完成交易,在这个线程完成交易后version
会+1,其他线程之前获得的version
和现在的version
就相差1,不相等了,sql语句就无法执行了。这样就保证了只有一个成功,不会在并发时发生超卖的问题。
CAS(Compare And Swap)
算法也是实现乐观锁的一种方式,CAS
有三个值,V
代表 内存值(内存中已存储的数据,等待被修改),A
代表旧的期望值(来自于V
),B
代表新的修改值。
假设此时的V
是内存中储存的一个数字56
,事务A需要把56
修改为60
,那么基于CAS
,事务A会先获取此时V
的值,并保存到A
中,此时A
就是56
,B
就是60
,将56
修改为60
之前,事务A会验证A
和V
是否相等,如果A
和V
相等,才会进行修改。假设在事务A修改的同时,另一个事务B也对56
进行了修改,要将56
修改为100,事务B比A块,所以事务A进行验证的时候发现V
的值为100
,而事务A的A
保存的值为56
,此时A
不等于V
,验证不通过,无法进行修改,会进行回旋,不断再次尝试验证,直到验证通过才会停止。
由于CAS
只验证值的大小,所以会产生经典的ABA
问题,以上面的例子举例,在事务A操作之前,事务B先将56
改为100
,然后再改回56
,此时事务A再验证,是可以通过的,就像V
没变过一样。这在一定的情况下会产生问题。 -
Serializable(可串行化)
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。