事务的隔离级别
- 读未提交:出现脏读
- 读已提交:解决脏读,出现不可重复读(有人开始操作数据,其它事务仍能够改动、插入或删除数据)
- 可重复读,解决不可重复度,出现幻读,所有人可以读取,只要有一人开始操作,便上锁,其他人不能修改,但是可以新增或删除(幻读)
- 序列化读:解决幻读
a.脏读:指当一个事务正在访问数据,并且对数据进行了修改,而这种数据还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据还没有提交那么另外一个事务读取到的这个数据我们称之为脏数据。依据脏数据所做的操作肯能是不正确的。
b.不可重复读:不可重复读是指在事务1内,读取了一个数据,事务1还没有结束时,事务2也访问了这个数据,修改了这个数据,并提交。紧接着,事务1又读这个数据。由于事务2的修改,那么事务1两次读到的的数据可能是不一样的,因此称为是不可重复读。
c.幻象读:一个事务先后读取一个范围的记录,但两次读取的纪录数不同,我们称之为幻象读(两次执行同一条 select 语句会出现不同的结果,第二次读会增加一数据行,并没有说这两次执行是在同一个事务中)
注意:不可重复读和幻读的区别是:前者是指读到了已经提交的事务的更改数据(修改),后者是指读到了其他已经提交事务的新增或删除数据。
对于这两种问题解决采用不同的办法,防止读到更改数据,只需对操作的数据添加行级锁,防止操作中的数据发生变化;而防止读到新增数据,往往需要添加表级锁,将整张表锁定,防止新增数据
悲观锁和乐观锁
悲观锁
的情况下,为了保证事务的隔离性,就必须要一致性锁定读。
-
读取数据时给加锁,其他事务无法改动这些数据。
-
改动删除数据时也要加锁,其他事务无法读取这些数据。
乐观锁
机制在一定程度上攻克了这个问题。乐观锁,大多是基于数据版本号( Version )记录机制实现。何谓数据版本号?即为数据添加一个版本号标识,在基于数据库表的版本号解决方式中,通常是通过为数据库表添加一个 “version” 字段来实现。
读取出数据时,将此版本号号一同读出,之后更新时,对此版本号号加一。
此时。将提交数据的版本号数据与数据库表相应记录的当前版本号信息进行比对,假设提交的数据版本号号大于数据库表当前版本号号,则予以更新。否则觉得是过期数据。
应用场景
乐观锁
因此一般乐观锁只用在高并发、多读少写的场景,GIT,SVN,CVS等代码版本控制管理器,就是一个乐观锁使用很好的场景,例如:A、B程序员,同时从SVN服务器上下载了code.html文件,当A完成提交后,此时B再提交,那么会报版本冲突,此时需要B进行版本处理合并后,再提交到服务器。这其实就是乐观锁的实现全过程。如果此时使用的是悲观锁,那么意味者所有程序员都必须一个一个等待操作提交完,才能访问文件,这是难以接受的。
悲观锁
synchronized关键字的实现也是悲观锁。
比较适合写入操作比较频繁的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。
悲观锁的特点是先获取锁,再进行业务操作,即“悲观”的认为获取锁是非常有可能失败的,因此要先确保获取锁成功再进行业务操作。通常所说的“一锁二查三更新”即指的是使用悲观锁
共享锁和排他锁
共享锁又称读锁,是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。
排他锁又称写锁,如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。
用于数据修改操作,例如 INSERT、UPDATE 或 DELETE。确保不会同时同一资源进行多重更新。
事务隔离级别和锁的关系
未提交读:读操作不会请求共享锁,如果读操作不请求共享锁,就绝对不会和排他锁的写操作发生冲突。这意味这读操作也能读取未提交的修改(也称为脏读)。
已提交读:它要求读操作必须获得共享锁才能进行,从而防止读取未提交的修改。在该级别中,读操作一旦完成,就立即释放共享锁。他不会在事务持续期间保留它;这意味着在一个事务中间对相同数据资源的两次访问,没有共享锁来锁定该资源,这样的话,其他事务可以在两个读操作之间修改资源,读操作也有可能每次取到不同的值。这种现象称为不可重复读(non-repeatable read)或不一致分析
可重复度:在这种隔离级别下,事务中的读操作不但需要获得共享锁才能读取操作,而且获得的共享锁将一直保持到事务结束。但是事务只锁定第一个运行时找到的那些数据资源,而不会锁定查询结果范围以外的其他行。因此,在同一事务进行第二次读取之前,如果其他事务插入了新行,而且新行也能满足读操作的查询过滤条件,那么这些新行也会出现第二次读取操作返回的结果中。这些新行称为幻影(phantom),这种读操作称为幻读(phantom read)。
可序列化:即读操作需要获得共享锁才能读取数据,并保留共享锁直到事务结束。不过SERIALIZABLE隔离级别增加一个新内容-------逻辑上,这个隔离级别会让读操作锁定满足搜索条件的键整个范围。这就意味着读操作不仅锁定了满足条件的那些行,还锁定了未来可能满足搜索条件的行。
死锁产生的原因和解决办法
死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。
产生死锁条件:
-
互斥
-
请求和保持
-
不可剥夺
-
循环等待
第一种情况:
一个用户A 访问表A(锁住了表A),然后又访问表B;另一个用户B 访问表B(锁住了表B),然后企图访问表A;这时用户A由于用户B已经锁住表B,它必须等待用户B释放表B才能继续,同样用户B要等用户A释放表A才能继续,这就死锁就产生了。
第二种情况:
用户A查询一条纪录,然后修改该条纪录;这时用户B修改该条纪录,这时用户A的事务里锁的性质由查询的共享锁企图上升到独占锁,而用户B里的独占锁由于A 有共享锁存在所以必须等A释放掉共享锁,而A由于B的独占锁而无法上升的独占锁也就不可能释放共享锁,于是出现了死锁。
解决办法:
- 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;
- 对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率;