Oracle 锁(1)

6.1           什么是锁?

锁(lock)机制用于管理对共享资源的并发访问。

首先,所有数据库本质上都不同。其次,学习每一个新数据库时,应该假设自己从未使用过数据库。在一个数据库中能做的事情在另一个数据库中可能没有必要做,或者根本不能做。

 

Oracle中,你会了解到:

l         事务是每个数据库的核心,它们是“好东西”。

l         应该延迟到适当的时刻才提交。不要太快提交,以避免对系统带来压力。这是因为,如果事务很长或很大,一般不会对系统有压力。相应的原则是:在必要时才提交,但是此前不要提交。事务的大小只应该根据业务逻辑来定。

l         只要需要,就应该尽可能长时间地保持对数据所加的锁。这些锁是你能利用的工具,而不是让你退避三舍的东西。锁不是稀有资源。恰恰相反,只要需要,你就应该长期地保持数据上的锁。锁可能并不稀少,而且它们可以防止其他会话修改信息。

l         Oracle中,行级锁没有相关的开销,根本没有。不论你是有1个行锁,还是1 000 000个行锁,专用于锁定这个信息的“资源”数都是一样的。当然,与修改1行相比,修改1 000 000行要做的工作肯定多得多,但是对1 000 000行锁定所需的资源数与对1行锁定所需的资源数完全相同,这是一个固定的常量。

l         不要以为锁升级“对系统更好”(例如,使用表锁而不是行锁)。在Oracle中,锁升级(lock escalate)对系统没有任何好处,不会节省任何资源。也许有时会使用表锁,如批处理中,此时你很清楚会更新整个表,而且不希望其他会话锁定表中的行。但是使用表锁绝对不是为了避免分配行锁,想以此来方便系统。

l         可以同时得到并发性和一致性。每次你都能快速而准确地得到数据。数据读取器不会被数据写入器阻塞。数据写入器也不会被数据读取器阻塞。这是Oracle与大多数其他关系数据库之间的根本区别之一。

 

6.2   锁定问题

6.2.1  丢失更新

6.2.2  悲观锁定

悲观锁定(pessimistic locking)仅用于有状态(stateful)或有连接(connected)环境,也就是说,你的应用与数据库有一条连续的连接,而且至少在事务生存期中只有你一个人使用这条连接。除了简单地查询值并验证数据尚未修改外,我们要使用FOR UPDATE NOWAIT锁定这一行。

 

6.2.3 乐观锁定

第二种方法称为乐观锁定(optimistic locking),即把所有锁定都延迟到即将执行更新之前才做。

1. 使用版本列的乐观锁定

这是一个简单的实现,如果你想保护数据库表不出现丢失更新问题,应对每个要保护的表增加一列。这一列一般是 NUMBER DATE/TIMESTAMP 列,通常通过表上的一个行触发器来维护。每次修改行时,这个触发器要负责递增 NUMBER 列中的值,或者更新DATE/TIMESTAMP列。

2. 使用校验和的乐观锁定

这与前面的版本列方法很相似,不过在此要使用基数据本身来计算一个“虚拟的”版本列。

3. 使用  ORA_ROWSCN 的乐观锁定

Oracle  10g  Release 1开始,你还可以使用内置的 ORA_ROWSCN 函数。它的工作与前面所述的版本列技术很相似,但是可以由Oracle自动执行,而不需要在表中增加额外的列,也不需要额外的更新/维护代码来更新这个值。除非你创建表时支持在行级维护ORA_ROWSCN,否则Oracle会在块级维护。也就是说,默认情况下,一个块上的多行会共享相同的 ORA_ROWSCN 值。如果更新一个块上的某一行,而且这个块上还有另外50行,那么这些行的ORA_ROWSCN也会推进。这往往会导致许多假警报,你认为某一行已经修改,但实际上它并没有改动。如果要在行级上维护则需要在建表时加上ROWDEPENDENCIES子句。

    另外可以把SCN转换为墙上时钟,但有一些限制:数据库的正常运行时间只有5天左右。如:select scn_to_timestamp(ora_rowscn) from dept;

6.2.4 乐观锁定还是悲观锁定?

 

6.2.5 阻塞

如果一个会话持有某个资源的锁,而另一个会话在请求这个资源,就会出现阻塞(blocking)。这样一来,请求的会话会被阻塞,它会“挂起”,直至持有锁的会话放弃锁定的资源。几乎在所有情况下,阻塞都是可以避免的。实际上,如果你真的发现会话在一个交互式应用中被阻塞,就说明很有可能同时存在着另一个 bug,即丢失更新,只不过你可能没有意识到这一点。也就是说,你的应用逻辑有问题,这才是阻塞的根源。

数据库中有5条常见的DML语句可能会阻塞,具体是:INSERTUPDATEDELETEMERGESELECT FOR UPDATE

 

1. 阻塞的 INSERT

最常见的情况是,你有一个带主键的表,或者表上有惟一的约束,但有两个会话试图用同样的值插入一行。

一种解决方法是:创建一个触发器,在插入前,把插入行的主键散列到一个数字上,然后用一个排他锁把这个数字锁住,这样另一个人在插入相同的行时得不到这个锁,而收到一个错误,从而得知正试图插入相同行。

最后,还要记住,尽管Oracle有无限多个行级锁,但是 enqueue锁(这是一种队列锁)的个数则是有限的。如果在会话中插入大量行,而没有提交,可能就会发现创建了太

多的 enqueue 队列锁,而耗尽了系统的队列资源(超出了 ENQUEUE_RESOURCES 系统参数设置的最大值),因为每行都会创建另一个enqueue锁。如果确实发生了这种情况,就需要增大ENQUEUE_RESOURCES参数的值。还可以向触发器增加一个标志,允许打开或关闭这种检查。例如,如果我准备插入数百条或数千条记录,可能就不希望启用这个检查。

问题:什么是enqueue锁?为什么插入时每行都会创建另一个enqueue锁?ENQUEUE_RESOURCES 系统参数是个什么参数?

 

2. 阻塞的 MergeUpdate Delete

在一个交互式应用中,可以从数据库查询某个数据,允许最终用户处理这个数据,再把它“放回”到数据库中,此时如果 UPDATE DELETE阻塞,就说明你的代码中可能存在一个丢失更新问题(如果真是这样,按我的说法,就是你的代码中存在bug) 。你试图UPDATE

(更新)其他人正在更新的行(换句话说,有人已经锁住了这一行)。通过使用SELECT FOR UPDATE NOWAIT查询可以避免这个问题,这个查询能做到:

l         验证自从你查询数据之后数据未被修改(防止丢失更新)。

l         锁住行(防止UPDATEDELETE被阻塞)。

 

6.2.6 死锁

根据我的经验,导致死锁的头号原因是外键未加索引(第二号原因是表上的位图索引遭到并发更新,这个内容将在第 11 章讨论)。

在以下两种情况下,Oracle在修改父表后会对子表加一个全表锁:

l         如果更新了父表的主键(倘若遵循关系数据库的原则,即主键应当是不可变的,这种情况就很少见),由于外键上没有索引,所以子表会被锁住。

l         如果删除了父表中的一行,整个子表也会被锁住(由于外键上没有索引)。

 

在以下情况下,未加索引的外键也可能带来问题:

l         如果有ON DELETE CASCADE,而且没有对子表加索引:例如,EMPDEPT的子表,DELETE DEPTNO = 10应该CASCADE(级联)至 EMP[4]。如果 EMP中的 DEPTNO没有索引,那么删除 DEPT 表中的每一行时都会对 EMP 做一个全表扫描。这个全表扫描可能是不必要的,而且如果从父表删除多行,父表中每删除一行就要扫描一次子表。

l         从父表查询子表:再次考虑 EMP/DEPT 例子。利用 DEPTNO 查询 EMP 表是相当常见的。如果频繁地运行以下查询(例如,生成一个报告) ,你会发现没有索引会使查询速度变慢:

select * from dept, emp where emp.deptno = dept.deptno and dept.deptno = :X;

那么,什么时候不需要对外键加索引呢?答案是,一般来说,当满足以下条件时不需要加索引:

l         没有从父表删除行。

l         没有更新父表的惟一键/主键值(当心工具有时会无意地更新主键!)。

l         没有从父表联结子表(如DEPT联结到EMP)。

如果满足上述全部 3 个条件,那你完全可以跳过索引,不需要对外键加索引。如果满足以上的某个条件,就要当心加索引的后果。这是一种少有的情况,即Oracle“过分地锁定了”数据。

 

6.2.7 锁升级

如果数据库认为锁是一种稀有资源,而且想避免锁的开销,这些数据库中就会频繁使用锁升级。如果数据库支持锁升级,就说明这个数据库的锁定机制中存在某些内部开销,而且管理数百个锁需要做大量的工作。在Oracle中,1个锁的开销与1 000 000个锁是一样的,都没有开销。

注意:Oracle不会升级锁,从来不会。但是它会执行锁转换(lock conversion)或锁提升(lock promotion) ,这些词通常会与锁升级混淆。“锁转换”和“锁提升”是同义词。Oracle一般称这个过程为“锁转换”。

Oracle会尽可能地在最低级别锁定(也就是说,限制最少的锁),如果必要,会把这个锁转换为一个更受限的级别。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值