十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
oracle数据库分行级锁和表级锁。用select * from table-name for update完成行级锁。用delete或update完成表级锁。你锁定的资源 别人会等待你的提交语句或回退语句完成以后再继续进行。
成都网站建设、成都网站设计的开发,更需要了解用户,从用户角度来建设网站,获得较好的用户体验。创新互联公司多年互联网经验,见的多,沟通容易、能帮助客户提出的运营建议。作为成都一家网络公司,打造的就是网站建设产品直销的概念。选择创新互联公司,不只是建站,我们把建站作为产品,不断的更新、完善,让每位来访用户感受到浩方产品的价值服务。
乐观锁一开始也说了,就是一开始假设不会造成数据冲突,在最后提交的时候再进行数据冲突检测。在乐观锁中,我们有3种
常用的做法来实现。
[1]第一种就是在数据取得的时候把整个数据都copy到应用中,在进行提交的时候比对当前数据库中的数据和开始的时候更新前取得的数据。当发现两个数据一模一样以后,就表示没有冲突可以提交,否则则是并发冲突,需要去用业务逻辑进行解决。
[2]第二种乐观锁的做法就是采用版本戳,这个在Hibernate中得到了使用。采用版本戳的话,首先需要在你有乐观锁的数据库table上建立一个新的column,比如为number型,当你数据每更新一次的时候,版本数就会往上增加1。比如同样有2个session同样对某条数据进行操作。两者都取到当前的数据的版本号为1,当第一个session进行数据更新后,在提交的时候查看到当前数据的版本还为1,和自己一开始取到的版本相同。就正式提交,然后把版本号增加1,这个时候当前数据的版本为2。当第二个session也更新了数据提交的时候,发现数据库中版本为2,和一开始这个session取到的版本号不一致,就知道别人更新过此条数据,这个
时候再进行业务处理,比如整个Transaction都Rollback等等操作。在用版本戳的时候,可以在应用程序侧使用版本戳的验证,也可以在数据库侧采用Trigger(触发器)来进行验证。不过数据库的Trigger的性能开销还是比较的大,所以能在应用侧进行验证的话还是推荐不用Trigger。
[3]第三种做法和第二种做法有点类似,就是也新增一个Table的Column,不过这次这个column是采用timestamp型,存储数据最后更新的时间。在Oracle9i以后可以采用新的数据类型,也就是timestamp with time zone类型来做时间戳。这种Timestamp的数据精度在Oracle的时间类型中是最高的,精确到微秒(还没与到纳秒的级别),一般来说,加上数据库处理时间和人的思考动作时间,微秒级别是非常非常够了,其实只要精确到毫秒甚至秒都应该没有什么问题。和刚才的版本戳类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。如果不想把代码写在程序中或者由于别的原因无法把代码写在现有的程序中,也可以把这个时间戳乐观锁逻辑写在Trigger或者存储过程中。
这些是基础,必须掌握:锁定的概念:锁定是数据库用来控制共享资源并发布访问的机制。在多用户环境下,多个用户可同时访问相同的数据。Oracle 提供锁以确保在多用户环境下数据的完整性和一致性。在提交或回滚事务之前,Oracle 会锁定正被修改的数据。在用户完成或回滚事务之后,锁会自动释放。只有在提交或回滚事物之后,其他用户才可以更新这些数据。锁的两种级别为:行级锁和表级锁。行级锁主要用于特定的行,表级锁主要用于整个表,下面我就行级锁和表级锁来个说明:行级锁行级锁只对用户正在访问的行进行锁定。如果该用户正在修改某行,那么其他用户就可以更新同一表中该行之外的数据。例如:如果用户1正在更新Jobs 表中的第一行,则用户2可以同时修改该表中的第二行。也就是说除了该表中的第一行,其他用户可以修改任意行,但是第一行的数据其他用户只能select。行级锁是一种排他锁,防止其他事务修改此行,但是不会阻止读取此行的操作。在使用INSERT、UPDATE、DELETE 和SELECT…FOR UPDATE 等 语句时,Oracle 会自动应用行级锁锁定。SELECT...FOR UPDATE 语句允许用户每次选择多行记录进行更新,这些记录会被锁定,且只能由发起查询的用户进行编辑。只有在回滚或提交事务之后,锁定才会释放,其他用户才可以编辑这些记录。SELECT...FOR UPDATE 语句的语法如下: SELECT ... FOR UPDATE [OF column_list][WAIT n|NOWAIT];其中:OF 子句用于指定即将更新的列,即锁定行上的特定列。WAIT 子句指定等待其他用户释放锁的秒数,防止无限期的等待。“使用FOR UPDATE WAIT”子句的优点如下:⒈防止无限期地等待被锁定的行;⒉允许应用程序中对锁的等待时间进行更多的控制。⒊对于交互式应用程序非常有用,因为这些用户不能等待不确定举例:比如一个用户在SQL PLUS下输入这条语句:SQLSELECT * FROM order_master WHERE vencode='V002' FOR UPDATE;此时再开启一个SQL PLUS,以相同的用户登陆,执行下面的命令。SQL SELECT * FROM order_master WHERE vencode='V02' FOR UPDATE WAIT 5;由于要更新的行已经被锁定,上述命令在等待5秒钟之后返回,并给出如下的错误信息:ERROR 位于第1行;ORA-30006;资源已被占用;执行操作时出现WAIT超时。PS:再开启的SQL PLUS 是以相同用户登陆的,用其他用户登陆更是不可能。表级锁表级锁被锁定的表,暂时放在内存中,不提交不进去数据库,也就是说,多个用户在同一时间,同时修改同一个表的同一行时,同时点提交,但是还是按随机的先后被提交进数据库,而不是同时被提交,而是先随机存储后,被再次提交的覆盖。表级锁将保护表数据,在事务处理过程中,表级锁会限制对整个表的访问。可以使用LOCK TABLE 语句显示地锁定表。表级锁用来限制对表执行添加、更新和删除等修改操作。锁定表的语法如下:LOCK TABLE table_name IN lock_mode MODE [NOWAIT];其中:lock_mode 是锁定的模式。NOWAIT 关键字用于防止无限期的等待其他用户释放锁。表级锁的模式包括以下内容:行共享 (ROW SHARE, RS): 允许其他用户访问和锁定该表,但是禁止排他锁定整个表。行共享锁锁定后,在同一时刻,不同用户可以对同一个表中的被行共享锁锁定后的该行,具备增、删、改、查的功能。行排他(ROW EXCLUSIVE, RX):与行共享模式相同,同时禁止其他用户在此表上使用共享锁。使用SELECT...FOR UPDATE 语句会在表上自动应用排他锁。被行排他后,其他用户不能同时修改该行,但是可以插入行,可以查询该行,其他用户也不能再在该表上对此行进行排他。共享(SHARE, S):共享锁将锁定表,仅允许其他用户查询表中的行,但不允许插入、更新或删除行。多个用户可以同时在同一个表中放置共享锁,即允许资源共享,因此得名“共享锁”。例如,如果用户每天都需要在结帐时更新日销售表,则可以在更改该表时使用共享锁以确保数据的一致性。也就是说该表只能查,其他用户想修改表中行的数据,只需要对该表进行共享锁。共享行排他(SHARE ROW EXCLUSIVE , SPX):执行比共享表更多的限制。防止其他事务在表上应用共享锁、共享行排他锁以及排他锁。共享行排他是除了该行以外的其他行也不能增、删、改。只能在此表中加低级表。要是想在该表中更改其他行的数据,就只有其他用户对该行进行共享行排他锁,也仅仅只能修改被这个用户锁定的行,而其他的行也修改不了。排他(EXCLUSIVE,E):对表执行最大限制。除了允许其他用户查询该表的记录,排他锁防止其他事务对表做任何更改或在表上应用任何类型的锁。这个锁应该叫锁中之王,他锁住了的话,其他用户就只有查询的功能了,就别想在该表中干别的事了。BTW:在能加很多锁的表中,如果第一个用户对该表锁定时,没有使用“NOWAIT”语句,是需要第一个用户对该表COMMIT或ROLLBACK 命令释放锁定后,其他用户才能对该表进行锁定。如果其他用户违反了该条,就会无期限的等待。。。。SQL LOCK TABLE order_mater IN SHARE MODE;而使用下面的语句就可以很少的预防这种情况的存在:SQL LOCK TBALE order_mater IN SHARE MODE NOWAIT;这是些扩展的内容,供大家参考
1 引言—数据库锁的基本概念 为了确保并发用户在存取同一数据库对象时的正确性(即无丢失修改、可重复读、不读“脏”数据),数据库中引入了锁机制。基本的锁类型有两种:排它锁(Exclusive locks记为X锁)和共享锁(Share locks记为S锁)。 排它锁:若事务T对数据D加X锁,则其它任何事务都不能再对D加任何类型的锁,直至T释放D上的X锁;一般要求在修改数据前要向该数据加排它锁,所以排它锁又称为写锁。 共享锁:若事务T对数据D加S锁,则其它事务只能对D加S锁,而不能加X锁,直至T释放D上的S锁;一般要求在读取数据前要向该数据加共享锁,所以共享锁又称为读锁。 2 Oracle 多粒度封锁机制介绍 根据保护对象的不同,Oracle数据库锁可以分为以下几大类: (1) DML lock(data locks,数据锁):用于保护数据的完整性; (2) DDL lock(dictionary locks,字典锁):用于保护数据库对象的结构(例如表、视图、索引的结构定义); (3) internal locks 和l a t c h es(内部锁与闩):保护内部数据库结构; (4) distributed locks(分布式锁):用于OPS(并行服务器)中; (5) PCM locks(并行高速缓存管理锁):用于OPS(并行服务器)中。 本文主要讨论DML(也可称为data locks,数据锁)锁。从封锁粒度(封锁对象的大小)的角度看,Oracle DML锁共有两个层次,即行级锁和表级锁。 2.1 Oracle的TX锁(行级锁、事务锁) 许多对Oracle不太了解的技术人员可能会以为每一个TX锁代表一条被封锁的数据行,其实不然。TX的本义是Transaction(事务),当一个事务第一次执行数据更改(Insert、Update、Delete)或使用SELECT… FOR UPDATE语句进行查询时,它即获得一个TX(事务)锁,直至该事务结束(执行COMMIT或ROLLBACK操作)时,该锁才被释放。所以,一个TX锁,可以对应多个被该事务锁定的数据行。 在Oracle的每行数据上,都有一个标志位来表示该行数据是否被锁定。Oracle不象其它一些DBMS(数据库管理系统)那样,建立一个链表来维护每一行被加锁的数据,这样就大大减小了行级锁的维护开销,也在很大程度上避免了其它数据库系统使用行级封锁时经常发生的锁数量不够的情况。数据行上的锁标志一旦被置位,就表明该行数据被加X锁,Oracle在数据行上没有S锁。
2.2 TM锁(表级锁) 2.2.1 意向锁的引出 表是由行组成的,当我们向某个表加锁时,一方面需要检查该锁的申请是否与原有的表级锁相容;另一方面,还要检查该锁是否与表中的每一行上的锁相容。比如一个事务要在一个表上加S锁,如果表中的一行已被另外的事务加了X锁,那么该锁的申请也应被阻塞。如果表中的数据很多,逐行检查锁标志的开销将很大,系统的性能将会受到影响。为了解决这个问题,可以在表级引入新的锁类型来表示其所属行的加锁情况,这就引出了“意向锁”的概念。 意向锁的含义是如果对一个结点加意向锁,则说明该结点的下层结点正在被加锁;对任一结点加锁时,必须先对它的上层结点加意向锁。如:对表中的任一行加锁时,必须先对它所在的表加意向锁,然后再对该行加锁。这样一来,事务对表加锁时,就不再需要检查表中每行记录的锁标志位了,系统效率得以大大提高。 2.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要对一个数据库对象加锁,必须先对它的上层结点加意向锁。申请封锁时应按自上而下的次序进行;释放封锁时则应按自下而上的次序进行;具有意向锁的多粒度封锁方法提高了系统的并发度,减少了加锁和解锁的开销。 2.2.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,表示不相容的请求;-表示没有加锁请求):
T2
T1 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数据库TM锁的相容矩阵 一方面,当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…… 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 exclusive mode RX RS、RX
lock table table_name in share mode S RS、S
lock table table_name in share row exclusive mode SRX RS
lock table table_name in exclusive mode X 无
为了得到最大的性能,一般数据库都有并发机制,不过带来的问题就是数据访问的冲突。为了解决这个问题,大多数数据库用的方法就是数据的锁定。
数据的锁定分为两种方法,第一种叫做悲观锁,第二种叫做乐观锁。什么叫悲观锁呢,悲观锁顾名思义,就是对数据的冲突采取一种悲观的态度,也就是说假设数据肯定会冲突,所以在数据开始读取的时候就把数据锁定住。而乐观锁就是认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让用户返回错误的信息,让用户决定如何去做。
先从悲观锁开始说。在SqlServer等其余很多数据库中,数据的锁定通常采用页级锁的方式,也就是说对一张表内的数据是一种串行化的更新插入机制,在任何时间同一张表只会插1条数据,别的想插入的数据要等到这一条数据插完以后才能依次插入。带来的后果就是性能的降低,在多用户并发访问的时候,当对一张表进行频繁操作时,会发现响应效率很低,数据库经常处于一种假死状态。而Oracle用的是行级锁,只是对想锁定的数据才进行锁定,其余的数据不相干,所以在对Oracle表中并发插数据的时候,基本上不会有任何影响。
注:对于悲观锁是针对并发的可能性比较大,而一般在我们的应用中用乐观锁足以。
Oracle的悲观锁需要利用一条现有的连接,分成两种方式,从SQL语句的区别来看,就是一种是for update,一种是for update nowait的形式。比如我们看一个例子。首先建立测试用的数据库表。
CREATE TABLE TEST(ID,NAME,LOCATION,VALUE,CONSTRAINT test_pk PRIMARY KEY(ID))AS SELECT deptno, dname, loc, 1 FROM scott.dept
这里我们利用了Oracle的Sample的scott用户的表,把数据copy到我们的test表中。首先我们看一下for update锁定方式。首先我们执行如下的select for update语句。
select * from test where id = 10 for update
通过这条检索语句锁定以后,再开另外一个sql*plus窗口进行操作,再把上面这条sql语句执行一便,你会发现sqlplus好像死在那里了,好像检索不到数据的样子,但是也不返回任何结果,就属于卡在那里的感觉。这个时候是什么原因呢,就是一开始的第一个Session中的select for update语句把数据锁定住了。由于这里锁定的机制是wait的状态(只要不表示nowait那就是wait),所以第二个Session(也就是卡住的那个sql*plus)中当前这个检索就处于等待状态。当第一个session最后commit或者rollback之后,第二个session中的检索结果就是自动跳出来,并且也把数据锁定住。不过如果你第二个session中你的检索语句如下所示。
select * from test where id = 10
也就是没有for update这种锁定数据的语句的话,就不会造成阻塞了。另外一种情况,就是当数据库数据被锁定的时候,也就是执行刚才for update那条sql以后,我们在另外一个session中执行for update nowait后又是什么样呢。比如如下的sql语句。 由于这条语句中是制定采用nowait方式来进行检索,所以当发现数据被别的session锁定中的时候,就会迅速返回ORA-00054错误,内容是资源正忙, 但指定以 NOWAIT 方式获取资源。所以在程序中我们可以采用nowait方式迅速判断当前数据是否被锁定中,如果锁定中的话,就要采取相应的业务措施进行处理。
select * from test where id = 10 for update nowait
那这里另外一个问题,就是当我们锁定住数据的时候,我们对数据进行更新和删除的话会是什么样呢。比如同样,我们让第一个Session锁定住id=10的那条数据,我们在第二个session中执行如下语句。
update test set value=2 where id = 10
这个时候我们发现update语句就好像select for update语句一样也停住卡在这里,当你第一个session放开锁定以后update才能正常运行。当你update运行后,数据又被你update语句锁定住了,这个时候只要你update后还没有commit,别的session照样不能对数据进行锁定更新等等。
总之,Oracle中的悲观锁就是利用Oracle的Connection对数据进行锁定。在Oracle中,用这种行级锁带来的性能损失是很小的,只是要注意程序逻辑,不要给你一不小心搞成死锁了就好。而且由于数据的及时锁定,在数据提交时候就不呼出现冲突,可以省去很多恼人的数据冲突处理。缺点就是你必须要始终有一条数据库连接,就是说在整个锁定到最后放开锁的过程中,你的数据库联接要始终保持住。与悲观锁相对的,我们有了乐观锁。乐观锁一开始也说了,就是一开始假设不会造成数据冲突,在最后提交的时候再进行数据冲突检测。在乐观锁中,我们有3种
常用的做法来实现。
[1]第一种就是在数据取得的时候把整个数据都copy到应用中,在进行提交的时候比对当前数据库中的数据和开始的时候更新前取得的数据。当发现两个数据一模一样以后,就表示没有冲突可以提交,否则则是并发冲突,需要去用业务逻辑进行解决。
[2]第二种乐观锁的做法就是采用版本戳,这个在Hibernate中得到了使用。采用版本戳的话,首先需要在你有乐观锁的数据库table上建立一个新的column,比如为number型,当你数据每更新一次的时候,版本数就会往上增加1。比如同样有2个session同样对某条数据进行操作。两者都取到当前的数据的版本号为1,当第一个session进行数据更新后,在提交的时候查看到当前数据的版本还为1,和自己一开始取到的版本相同。就正式提交,然后把版本号增加1,这个时候当前数据的版本为2。当第二个session也更新了数据提交的时候,发现数据库中版本为2,和一开始这个session取到的版本号不一致,就知道别人更新过此条数据,这个
时候再进行业务处理,比如整个Transaction都Rollback等等操作。在用版本戳的时候,可以在应用程序侧使用版本戳的验证,也可以在数据库侧采用Trigger(触发器)来进行验证。不过数据库的Trigger的性能开销还是比较的大,所以能在应用侧进行验证的话还是推荐不用Trigger。
[3]第三种做法和第二种做法有点类似,就是也新增一个Table的Column,不过这次这个column是采用timestamp型,存储数据最后更新的时间。在Oracle9i以后可以采用新的数据类型,也就是timestamp with time zone类型来做时间戳。这种Timestamp的数据精度在Oracle的时间类型中是最高的,精确到微秒(还没与到纳秒的级别),一般来说,加上数据库处理时间和人的思考动作时间,微秒级别是非常非常够了,其实只要精确到毫秒甚至秒都应该没有什么问题。和刚才的版本戳类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。如果不想把代码写在程序中或者由于别的原因无法把代码写在现有的程序中,也可以把这个时间戳乐观锁逻辑写在Trigger或者存储过程中。
不知道这个是不是你需要的...
锁是 数据库保护数据表的一种机制,通常是自动的,分级别的,如果你访问一个表的并发量太大,可以试试拆分这个表,比如按日期拆分成月表,或者利用oracle的功能(分区)进行拆分来分散压力,如果没有依据拆分的话,可以做成实体化快照,将一些查询类的操作指向这个快照,分摊表的访问量,也可以通过提升硬件,比如上SSD磁盘,然后将这个表放到这个磁盘上,提高访问速度。
lock table 表名 exclusive mode nowait; -- 锁整个表
select * from 表名 where XXX for update nowaitl -- 锁符合条件的记录