第12章 事务与锁

target

掌握什么是事务
掌握事务的特性
掌握事务的隔离级别
了解锁
了解锁的种类
了解锁等待及死锁

1. 事务

1.1 定义

事务由事务开始和事务结束之间执行的全体操作组成。例如:在关系数据库中,一个事务可以是一条SQL语句,一组SQL语句或整个程序。

简单说,开车需要如下几步:1.打开车门、2.上车、3.扎上安全带、4.点火、5.挂挡、6.松手刹。那么开车这个事务就是以上6步,一步也不能少,中间有任何一步出现问题,整个事务都会失败。

1.2 特性:ACID

事务有四个特性:

  • 原子性(Atomicity):指事务包含的所有操作要么全部成功,要么全部失败回滚

  • 一致性(Consistency):指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。

    拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。

  • 隔离性(Isolation):通常来说,一个事务的操作对于其他的事务的不可见的,也就是说一般而言事务都是独立的。

    即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。

  • 持久性(Durability):事务一旦完成,那么该事务引起的数据变化将永久生效,不会改变。

1.3 隔离问题

事务是具有隔离性的,如果不考虑隔离,会产生如下几个常见的隔离问题:

  • 脏读:事务A读到了事务B没有提交的数据。

    如果事务B回滚了,则事务A就使用了错误的数据。

  • 不可重复读:一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。

    例如事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发生了不可重复读。

  • 虚读(幻读):幻读是事务非独立执行时发生的一种现象。

    例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。

1.4 隔离级别

为了避免产生脏读、幻读、不可重复读这些隔离问题,事务提供了以下隔离级别:

① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。

② Repeatable read (可重复读):可避免脏读、不可重复读的发生。

③ Read committed (读已提交):可避免脏读的发生。

④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。

以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,当然级别越高,执行效率就越低。

像Serializable这样的级别,就是以锁表的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。

在MySQL数据库中默认的隔离级别为Repeatable read (可重复读)。Oracle的默认隔离级别为Read committed(读已提交)

1.5 事务案例

事务基本上符合下面的模板:

新事务开启
sql statement
...
commit | rollback;

Oracle中事务不需要设置开始标志,通常有下列情况之一时,事务会开启。

  • 登录数据库后,第一次执行DML语句
  • 当事务结束后,第一次执行DML语句

当有下列情况之一时,事务会结束:

  • 使用commit事务提交,rollback事务回滚
  • 执行DDL语句,事务自动提交。例如,使用CREATE、DROP、GRANT、REVOKE等命令。
  • 正常退出SQL*Plus时自动提交事务,非正常退出则ROLLBACK事务回滚。

需要注意:

Oracle的自动提交默认是关闭的,即 autocommit为false。可以使用show autocommit查看。

开启自动提交:set autocommit on

:演示事务的一致性

① 登录SQL *plus ,我们称之为窗口一,执行下面两条语句:

update productinfo set description = '促销' where productid='1';
insert into productinfo values ('13','测试商品',10000,'测试',131,'台湾','无');

以上操作完成后,查看表数据,是否操作成功。(已经成功)

注意:并没有提交事务。

② 打开一个新的SQL *plus ,我们称之为窗口二,查新productinfo表的信息:

select * from productinfo;

发现,窗口二的数据并没有发生改变。原因是窗口一没有提交事务。

③ 回到窗口一,提交事务:

commit;

④ 回到窗口二,再次查询:

select * from productinfo;

发现,窗口一的数据已经成功同步。这就是事务的一致性。

1.6 事务的保存点

需求:实现AB(必须),CD(可选的)这样一组事务。

例如:你上班以后发工资,银行会给你发短信。发工资这个事件,公司的账户会扣钱,你的账户会加钱,这是最重要的。当你的账户加钱了,银行需要给你发短信,可是由于信号不好,短信没法成功。问:工资发不发?

有人说:短信没法成功,工资回滚一下。(需要回滚吗?不需要)公司账户扣钱,你的账户加钱,这事必选项。你的短信发送成不成功是可选项。可以用savepoint去实现

需要注意:

  • 事务只回滚保存点之后的操作。
  • 保存点可以有多个,回滚到某个保存点时,他以后的保存点将被删除。

:在事务中使用保存点

① 向productinfo 中增加一条数据:

insert into productinfo values('14','保存点测试',100,'测试',1234,'郑州','无');

② 创建保存点:

savepoint first;

③ 继续向productinfo 中增加一条数据:

insert into productinfo values('15','保存点测试2',200,'测试2',1234,'郑州','无');

④ 查看 productinfo 表中的数据:

select * from productinfo ;

发现两条数据都插入成功。

⑤ 回滚到保存点处:

rollback to first;

此时,保存点后的数据,也就是第二条插入的数据就撤销了。

2. 锁的简单了解

数据库是一个庞大的多用户数据管理系统,由于在多用户系统中,同一时刻多个用户同时操作某相同资源的情况时有发生,而在逻辑上这些用户想要同时操作该资源是不可能的,数据库中利用锁消除了多用户操作同一资源时可能产生的隐患。

2.1 什么是锁

锁出现在数据共享的环境中,它是一种机制,在访向相同资源时,可以防止事务之间的破坏性交互。例如,在多个会话同时操作某表时,优先操作的会话需要对其锁定,这样其他资源就无法操作了。

事务的独立性要求当前事务不能影响其他的事务,所以当多个会活访问相同的资源时,数据库系统会利用锁确保它们像队列一样依次进行。

Oracle处理数据时用到的锁是自动获取的, 我们不用对此有过多的关注,但Oracle允许我们手动锁定数据。

Oracle利用很低的约束提供最大程度的并发性,例如某会话正在修改一条记录,那么仅仅该记录会被锁定,而其他会话可以随时做读取操作,但读取的依然是修改前的数据。

Oracle的锁保证了数据的完整性。例如,当一个会话对表A的某行记录进行修改时,另一个会话也来修改该行记录,这里强烈不可取的,因为会脏数据。如果此时使用了行级锁,第一个会话修改记录时封锁该行,那么第 二个会活此时只能等待,这样就避免了脏数据的产生。

2.2 按级别分类

按锁级别分可以分为排他锁(exclusive lock,即X锁)和共享锁(share lock,即S锁)。

(1) 排它锁

排他锁也可以叫写锁。这种模式的锁防止资源的共享,用做数据的修改。

假如有事务T给数据A加上该锁,那么其他的事务将不能对A加任何的锁,所以此时只允许T对该数据进行读取和修改,直到事务完成将该类型的锁释放为止。

(2) 共享锁

共享锁也可以叫读锁。该模式锁下的数据只能被读取,不能被修改。

如果有事务T给数 据A加上共享锁后,那么其他事务不能对其加排他锁,只能加共享锁。加了该锁的数据可以被并发地读取。

锁是实现并发的主要手段,当事务提交后会自动释放锁。

3. Oracle 多粒度锁机制介绍

根据保护对象的不同,Oracle数据库锁可以分为以下几大类:

(1) DML lock(data locks,数据锁):用于保护数据的完整性;

(2) DDL lock(dictionary locks,字典锁):用于保护数据库对象的结构(例如表、视图、索引的结构定义);

(3) Internal locks 和latches(内部锁与闩):保护内部数据库结构;

(4) Distributed locks(分布式锁):用于OPS(并行服务器)中;

(5) PCM locks(并行高速缓存管理锁):用于OPS(并行服务器)中。

在Oracle中最主要的锁是DML(也可称为data locks,数据锁)锁。从封锁粒度(封锁对象的大小)的角度看,Oracle DML锁共有两个层次,即行级锁和表级锁。

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 意向锁

① 意向锁的引出

表是由行组成的,当我们向某个表加锁时,一方面需要检查该锁的申请是否与原有的表级锁相容;另一方面,还要检查该锁是否与表中的每一行上的锁相容。 比如一个事务要在一个表上加S锁,如果表中的一行已被另外的事务加了X锁,那么该锁的申请也应被阻塞。如果表中的数据很多,逐行检查锁标志的开销将很大, 系统的性能将会受到影响。为了解决这个问题,可以在表级引入新的锁类型来表示其所属行的加锁情况,这就引出了"意向锁"的概念。

意向锁的含义是如果对一个结点加意向锁,则说明该结点的下层结点正在被加锁;对任一结点加锁时,必须先对它的上层结点加意向锁。如:对表中的任一行 加锁时,必须先对它所在的表加意向锁,然后再对该行加锁。这样一来,事务对表加锁时,就不再需要检查表中每行记录的锁标志位了,系统效率得以大大提高。

② 意向锁的类型

由两种基本的锁类型(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,表示不兼容的请求;-表示没有加锁请求):

S X RS RX SRX -
S Y N Y N N Y
X N N N N N Y
RS Y N Y Y Y Y
RX N N Y Y N Y
SRX N N Y N N Y
- Y Y Y Y Y Y

一方面,当Oracle执行SELECT…FOR UPDATE、INSERT、UPDATE、DELETE等DML语句时,系统自动在所要操作的表上申请表级RS锁(SELECT…FOR UPDATE)或RX锁(INSERT、UPDATE、DELETE),当表级锁获得后,系统再自动申请TX锁,并将实际锁定的数据行的锁标志位置位(指 向该TX锁);另一方面,程序或操作人员也可以通过LOCK TABLE语句来指定获得某种类型的TM锁。下表总结了Oracle中各SQL语句产生TM锁的情况:

SQL语句 表锁模式 允许的锁模式
select * from table_name... RS、RX、S、SRX、X
insert into table_name RX RS、RX
update table_name RX RS、RX
delete from table_name for update RX RS、RX
select * from table_name for update RS RS、RX、S、SRX
lock table table_name in row share mode RS RS、RX、S、SRX
lock table table_name in row exclusiive mode RX RS、RX
lock table table_name in share mode S RS、S
lock table taable_name in share row exclusive mode SRX RS
lock table table_name in exclusive mode X

我们可以看到,通常的DML操作(SELECT…FOR UPDATE、INSERT、UPDATE、DELETE),在表级获得的只是意向锁(RS或RX),其真正的封锁粒度还是在行级;另外,Oracle数 据库的一个显著特点是,在缺省情况下,单纯地读数据(SELECT)并不加锁,Oracle通过回滚段(Rollback segment)来保证用户不读"脏"数据。这些都提高了系统的并发程度。

由于意向锁及数据行上锁标志位的引入,减小了Oracle维护行级锁的开销,这些技术的应用使Oracle能够高效地处理高度并发的事务请求。

4. Oracle 多粒度封锁机制的监控

为了监控Oracle系统中锁的状况,我们需要对几个系统视图有所了解:

4.1 v$lock视图

v$lock视图列出当前系统持有的或正在申请的所有锁的情况,其主要字段说明如下:

字段名称 类型 说明
SID number 回话(session)标识
TYPE varchar2(2) 区分该锁保安湖对象的类型
ID1 number 锁标识1
ID2 number 锁标识2
LMODE number 锁模式:0(None),1(null),2(row share),3(row exclusion),4(share),5(share row exclusion),6(exclusion)
REQUEST number 申请的锁模式,具体值同上面的lmode
CTIME number 已持有或等待锁的时间
BLOCK number 是否阻塞其他锁申请

其中在TYPE字段的取值中,本文只关心TM、TX两种DML锁类型;

4.2 v$locked_object视图

v$locked_object视图列出当前系统中哪些对象正被锁定,其主要字段说明如下:

字段名称 类型 说明
XIDUSN number 回滚段号
XIDSLOT number 槽号
XIDSQN number 序列号
OBJECT_ID number 被锁对象标识
SESSION_ID number 持有锁的会话(session)标识
ORACLE_USERNAME varchar(30) 持有锁的用户的Oracle用户名
OS_USER_NAME varchar(15) 持有该锁的用户的操作系统用户名
PROCESS varchar(9) 操作系统的进程号
LOCKED_MODE number 锁模式

4.3 Oracle锁监控脚本

根据上述系统视图,可以编制脚本来监控数据库中锁的状况。

① showlock.sql

第一个脚本showlock.sql,该脚本通过连接v$locked_object与all_objects两视图,显示哪些对象被哪些会话锁 住:

column o_name format a10column lock_type format a20column object_name format a15select rpad(oracle_username,10) o_name,session_id sid,decode(locked_mode,0,'None',1,'Null',2,'Row share',3,'Row Exclusive',4,'Share',5,'Share Row Exclusive',6,'Exclusive') lock_type,object_name ,xidusn,xidslot,xidsqnfrom V$LOCKED_OBJECT,all_objectswhere V$LOCKED_OBJECT.object_id=all_objects.object_id;

第二个脚本showalllock.sql,该脚本主要显示当前所有TM、TX锁的信息:

select sid,type,id1,id2,decode(lmode,0,'None',1,'Null',2,'Row share',3,'Row Exclusive',4,'Share',5,'Share Row Exclusive',6,'Exclusive')lock_type,request,ctime,blockfrom v$lockwhere TYPE IN('TX','TM');

5. 锁等待

在某些情况下由于占用的资源不能及时释放,而造成锁等待,也可叫锁冲突。

锁等待会严重地影响数据库性能和日常工作。例如,当一个会话修改表A的记录时,它会对该记录加锁, 而此时如果另一个会话也来修改此记录,那么第二个会活将因得不到排他锁而一直等待,此时会出现执行SQL时数据库长时间没有响应的现象,直到第一个会活把事务提交,释放锁,第二个会活才能对数据进行操作。

:演示锁等待的现象,具体分为如下两个步骤。

① 打开SQL *Plus窗口,修改 productinfo 表中的productid为1的记录:

update productinfo set origin = '修改1' where productid = '1';

此时,虽然已经更新,但事务并没有提交。

② 打开另一个SQL *Plus窗口,同样修改 productinfo 表中的productid为1的记录:

update productinfo set origin = '修改2' where productid = '1';

此时的执行效果不是提示已经更新,而是一直等待。

原因:第一个会话封锁了该记录,但事务没有结束,锁不会释放,而第二个会话也要修改同一条记录,但他却没有办法获得锁,所以只能等待。

6. 死锁

死锁的发生和锁等待不同,它是锁等待的一个特例,通常发生在两个或多个会话之间。假设一个会活想要修改两个资源对象,可以是表也可以是字段,修改这两个资源的操作在一个事 务当中。当它修改第一个对象时需要对其锁定,然后等待第二个对象,这时如果另外一个会话也需要修改这两个资源对象,并且已经获得并锁定了第二个对象,那么就会出现死锁,因为当前会话锁定了第一个对象等待第二个对象,而另一个会话锁定了第二个对象等待第一个对象。 这样,两个会话都不能得到想要得到的对象,于是出现死锁。

:演示死锁的发生,分为4个步骤

① 打开SQL *Plus窗口,修改 productinfo 表中的productid为1的记录:

update productinfo set origin = '修改1' where productid = '1';

② 打开另一个SQL *Plus窗口,修改 productinfo 表中的productid为2的记录:

update productinfo set origin = '修改2' where productid = '2';

到目前为止,第一个会话锁定了id为1的记录,第二个会话锁定了id为2的记录。

③ 第一个会话修改第二个会话已经修改的记录

update productinfo set origin = '修改' where productid = '2';

此时第一个会话将出现所等待,因为他修改的对象已经被第二个会话锁定。

④ 第二个会话修改第一个会话已经修改的记录

update productinfo set origin = '修改1' where productid = '1';

此时就出现了死锁的情况。Oracle会自动检测死锁的情况,并释放一个冲突锁,并把消息传递给对方事务。

第 1 行出现错误:ORA-00060: 等待资源时检测到死锁

习题

一、填空题

  1. Oracle中使用( )命令提交事务。

  2. Oracle中使用( )命令回滚事务。

  3. Oracle中使用( )命令设置保存点。

  4. 锁被分成 ( )、( )两种基本类型。

二、选择题

  1. TM锁中下-列哪种模式和其他模式都不兼容?

    A.RS B.S C.RX D.X

  2. 下面不属于事务操作语句的是( ).
    A. ROLLBACK

    B. COMMIT
    C. SAVEPOINT

    D. BEGIN

三、简答题

  1. 事务有哪些特性?
  2. 保存点的作用是什么?

你可能感兴趣的:(第12章 事务与锁)