事务和锁

一、事务隔离级别

事务隔离级别介绍:

  • @Transactional(isolation = Isolation.READ_UNCOMMITTED)读取未提交数据(会出现脏读, 不可重复读) 基本不使用
  • @Transactional(isolation = Isolation.READ_COMMITTED)读取已提交数据(会出现不可重复读和幻读)
  • @Transactional(isolation = Isolation.REPEATABLE_READ)可重复读(会出现幻读)
  • @Transactional(isolation = Isolation.SERIALIZABLE)串行化

 

什么是脏读、幻读、不可重复读?

  • 脏读 : 一个事务读取到另一事务未提交的更新数据
  • 不可重复读 : 在同一事务中, 多次读取同一数据返回的结果有所不同, 换句话说,在我这个事务还没结束的时候, 后续读取可以读到另外一个事务已提交的更新数据。 相反, "可重复读"在同一事务中多次读取数据时, 能够保证所读数据一样, 也就是后续读取不能读到另一事务已提交的更新数据
  • 幻读 : 一个事务读到另一个事务已提交的insert数据

 

二、事务隔离级别与锁

 

多版本:并发版本控制机制,他们把它叫做MVCC

 

 

语句级版本控制,对应的应是读提交级别,对记录进行快照,可以保证B事务一直读取这个记录,免得出现脏读

事务级版本控制,对应的应是可重复读级别,对本事务进行快照,本事务内每一次读取的数据都是相同的,即使A事务对本记录做了数据修改。

 

事务隔离级别都是锁的不同实现而已,事务隔离级别是由不同的锁机制实现的

比如读未提交级别,就是在一个事务写数据的时候上x锁,写完数据不管后面要提交还是会回滚都立马释放锁,那么其他事务肯定就读到了这个脏数据啊。

比如读提交级别,就是在A事务写数据的时候上x锁,写完数据必须提交才会释放x锁,这个时候B事务才可以读或写这行记录。在未释放x锁的时候,B事务读取的都是本版本的快照值,当A事务提交并释放x锁时版本升级,B事务再读便是新版本的值。

 

 

 

 

 

二、死锁与阻塞

1、阻塞

阻塞:  相同数据的并发操作是主因, 比如一个事务在操作这个数据时,需要等待另外一个先操作这个数据的事务commit后,才可以成功操作数据不然就一直等待。

举个阻塞的例子:

create table test_lock(c int,c2 varchar(100));

insert into test_lock values(1,'ttt');
insert into test_lock values(2,'ttt');
insert into test_lock values(3,'ttt');
insert into test_lock values(4,'ttt');
insert into test_lock values(5,'ttt');
Commit;


Session1:
update test_lock set c2='dddd' where c=3;
Session2:
update test_lock set c2='aaaa' where c=3;

#在本例中,如果session1执行sql时非常慢未能快速commit或者执行sql虽然很快但是一直不commit,也就是说session1的事务一直处于活动状态,就会导致sessions2处于阻塞中。

可以用这个sql看,本事务在等待那个事务,

select * from V$TRXWAIT

2、死锁

就是两个事务互相等待对方释放锁,比如事务A更新了一条记录

 

 事务B也更新了一条其他记录

 

 由于此时两个事务都没有提交,所以这两条id为1、2的记录都被上了x锁且未释放;

那如果此时,事务A对id未2的记录进行更新,事务B对id为1的记录进行更新,就会发生死锁,就是事务A等待事务B释放锁而事务B在等待事务A释放锁,他们处于相互等待中。

 

 

 

 

 

四、什么时候加意向锁?


3.1 Oracle的TX锁(行级锁、事务锁)  
许多对Oracle不太了解的技术人员可能会以为每一个TX锁代表一条被封锁的数据行,其实不然。TX的本义是Transaction(事务),当一个事务第一次执行数据更改(Insert、Update、Delete)或使用SELECT… FOR UPDATE语句进行查询时,它即获得一个TX(事务)锁,直至该事务结束(执行COMMIT或ROLLBACK操作)时,该锁才被释放。所以,一个TX锁,可以对应多个被该事务锁定的数据行(在我们用的时候多是启动一个事务,然后SELECT… FOR UPDATE NOWAIT)。   
在Oracle的每行数据上,都有一个标志位来表示该行数据是否被锁定。Oracle不像DB2那样,建立一个链表来维护每一行被加锁的数据,这样就大大减小了行级锁的维护开销,也在很大程度上避免了类似DB2使用行级锁时经常发生的锁数量不够而进行锁升级的情况。数据行上的锁标志一旦被置位,就表明该行数据被加X锁,Oracle在数据行上没有S锁。  
3.2 TM锁(表级锁)  
3.2.1 意向锁的引出  
表是由行组成的,当我们向某个表加锁时,一方面需要检查该锁的申请是否与原有的表级锁相容;另一方面,还要检查该锁是否与表中的每一行上的锁相容。比如一个事务要在一个表上加S锁,如果表中的一行已被另外的事务加了X锁,那么该锁的申请也应被阻塞。如果表中的数据很多,逐行检查锁标志的开销将很大,系统的性能将会受到影响。为了解决这个问题,可以在表级引入新的锁类型来表示其所属行的加锁情况,这就引出了"意向锁"的概念。  
意向锁的含义是如果对一个结点加意向锁,则说明该结点的下层结点正在被加锁;对任一结点加锁时,必须先对它的上层结点加意向锁。如:对表中的任一行加锁时,必须先对它所在的表加意向锁,然后再对该行加锁。这样一来,事务对表加锁时,就不再需要检查表中每行记录的锁标志位了,系统效率得以大大提高。  
3.2.2 意向锁的类型  
由两种基本的锁类型(S锁、X锁),可以自然地派生出两种意向锁:  
意向共享锁(Intent Share Lock,简称IS锁):如果要对一个数据库对象加S锁,首先要对其上级结点加IS锁,表示它的后裔结点拟(意向)加S锁;  
意向排它锁(Intent Exclusive Lock,简称IX锁):如果要对一个数据库对象加X锁,首先要对其上级结点加IX锁,表示它的后裔结点拟(意向)加X锁。  
另外,基本的锁类型(S、X)与意向锁类型(IS、IX)之间还可以组合出新的锁类型,理论上可以组合出4种,即:S+IS,S+IX,X+IS,X+IX,但稍加分析不难看出,实际上只有S+IX有新的意义,其它三种组合都没有使锁的强度得到提高(即:S+IS=S,X+IS=X,X+IX=X,这里的"="指锁的强度相同)。所谓锁的强度是指对其它锁的排斥程度。  
这样我们又可以引入一种新的锁的类型:  
共享意向排它锁(Shared Intent Exclusive Lock,简称SIX锁):如果对一个数据库对象加SIX锁,表示对它加S锁,再加IX锁,即SIX=S+IX。例如:事务对某个表加SIX锁,则表示该事务要读整个表(所以要对该表加S锁),同时会更新个别行(所以要对该表加IX锁)。  
这样数据库对象上所加的锁类型就可能有5种:即S、X、IS、IX、SIX。  
具有意向锁的多粒度封锁方法中任意事务T要对一个数据库对象加锁,必须先对它的上层结点加意向锁。申请封锁时应按自上而下的次序进行;释放封锁时则应按自下而上的次序进行;具有意向锁的多粒度封锁方法提高了系统的并发度,减少了加锁和解锁的开销。  
3.3 Oracle的TM锁(表级锁)  
Oracle的DML锁(数据锁)正是采用了上面提到的多粒度封锁方法,其行级锁虽然只有一种(即X锁),但其TM锁(表级锁)类型共有5种,分别称为共享锁(S锁)、排它锁(X锁)、行级共享锁(RS锁)、行级排它锁(RX锁)、共享行级排它锁(SRX锁),与上面提到的S、X、IS、IX、SIX相对应。需要注意的是,由于Oracle在行级只提供X锁,所以与RS锁(通过SELECT … FOR UPDATE语句获得)对应的行级锁也是X锁(但是该行数据实际上还没有被修改),这与理论上的IS锁是有区别的。 锁的兼容性是指当一个应用程序在表(行)上加上某种锁后,其他应用程序是否能够在表(行)上加上相应的锁,如果能够加上,说明这两种锁是兼容的,否则说明这两种锁不兼容,不能对同一数据对象并发存取。  
下表为Oracle数据库TM锁的兼容矩阵(Y=Yes,表示兼容的请求; N=No,表示不兼容的请求;-表示没有加锁请求):  

posted @ 2021-04-23 14:22  Eric-Shen  阅读(159)  评论(0编辑  收藏  举报