学习《Oracle 9i10g编程艺术》的笔记 (十) 锁

 1.在Oracle 中,你会了解到:
事务是每个数据库的核心,它们是“好东西”。


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


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


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


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


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

 

2.丢失更新

丢失更新(lost update)是一个经典的数据库问题。实际上,所有多用户计算机环境都存在这个问
题。简单地说,出现下面的情况时(按以下所列的顺序),就会发生丢失更新:
(1) 会话Session1 中的一个事务获取(查询)一行数据,放入本地内存,并显示给一个最终用户User1。
(2) 会话Session2 中的另一个事务也获取这一行,但是将数据显示给另一个最终用户User2。
(3) User1 使用应用修改了这一行,让应用更新数据库并提交。会话Session1 的事务现在已经执行。
(4) User2 也修改这一行,让应用更新数据库并提交。会话Session2 的事务现在已经执行。
这个过程称为“丢失更新”,因为第(3)步所做的所有修改都会丢失。例如,请考虑一个员工更新屏幕,
这里允许用户修改地址、工作电话号码等信息。应用本身非常简单:只有一个很小的搜索屏幕要生成一个
员工列表,然后可以搜索各位员工的详细信息。这应该只是小菜一碟。所以,编写应用程序时没有考虑锁
定,只是简单的SELECT 和UPDATE 命令。
然后最终用户(User1)转向详细信息屏幕,在屏幕上修改一个地址,单击Save(保存)按钮,得到
提示信息称更新成功。还不错,但是等到User1 第二天要发出一个税表时,再来检查记录,会发现所列的
还是原先的地址。到底出了什么问题?很遗憾,发生这种情况太容易了。在这种情况下,User1 查询记录
后,紧接着另一位最终用户(User2)也查询了同一条记录;也就是说,在User1 读取数据之后,但在她修
改数据之前,User2 也读取了这个数据。然后,在User2 查询数据之后,User1 执行了更新,接到成功信息,
甚至还可能再次查询看看是否已经修改。不过,接下来User2 更新了工作电话号码字段,并单击Save(保
存)按钮,完全不知道他已经用旧数据重写(覆盖)了User1 对地址字段的修改!之所以会造成这种情况,
这是因为应用开发人员编写的程序是这样的:更新一个特定的字段时,该记录的所有字段都会“刷新”(只
是因为更新所有列更容易,这样就不用先得出哪些列已经修改,并且只更新那些修改过的列)。

 

3.为了保护你不丢失更新 实际上就是要使用某种锁定策略,共有两种锁定策略:悲观锁定或乐
观锁定

悲观锁定:(pessimistic locking)仅用于有状态(stateful)或有连接(connected)环境,也就是
说,你的应用与数据库有一条连续的连接,而且至少在事务生存期中只有你一个人使用这条连接。

乐观锁定(optimistic locking),即把所有锁定都延迟到即将执行更新之前才做。
换句话说,我们会修改屏幕上的信息而不要锁。我们很乐观,认为数据不会被其他用户修改;因此,会等
到最后一刻才去看我们的想法对不对。
这种锁定方法在所有环境下都行得通,但是采用这种方法的话,执行更新的用户“失败”的可能性会
加大。这说明,这个用户要更新他的数据行时,发现数据已经修改过,所以他必须从头再来。

 

那么哪种方法最好呢?根据我的经验,悲观锁定在Oracle 中工作得非常好(但是在其他数据库中可
能不是这样),而且与乐观锁定相比,悲观锁定有很多优点。不过,它需要与数据库有一条有状态的连接,
如客户/服务器连接,因为无法跨连接持有锁。正是因为这一点,在当前的许多情况下,悲观锁定不太现实。
过去,客户/服务器应用可能只有数十个或数百个用户,对于这些应用,悲观锁定是我的不二选择。不过,
如今对大多数应用来说,我都建议采用乐观并发控制。要在整个事务期间保持连接,这个代价太大了,一
般无法承受。

 

4.阻塞

如果一个会话持有某个资源的锁,而另一个会话在请求这个资源,就会出现阻塞(blocking)。这样
一来,请求的会话会被阻塞,它会“挂起”,直至持有锁的会话放弃锁定的资源。几乎在所有情况下,阻塞
都是可以避免的。实际上,如果你真的发现会话在一个交互式应用中被阻塞,就说明很有可能同时存在着
另一个bug,即丢失更新,只不过你可能没有意识到这一点。也就是说,你的应用逻辑有问题,这才是阻
塞的根源。
数据库中有5 条常见的DML 语句可能会阻塞,具体是:INSERT、UPDATE、DELETE、MERGE 和SELECT FOR
UPDATE。对于一个阻塞的SELECT FOR UPDATE,解决方案很简单:只需增加NOWAIT 子句,它就不会阻塞了。
这样一来, 你的应用会向最终用户报告,这一行已经锁定。

 

5.死锁

如果你有两个会话,每个会话都持有另一个会话想要的资源,此时就会出现死锁(deadlock)。例如,
如果我的数据库中有两个表A 和B,每个表中都只有一行,就可以很容易地展示什么是死锁。我要做的只
是打开两个会话(例如,两个SQL*Plus 会话)。在会话A 中更新表A,并在会话B 中更新表B。现在,如果
我想在会话B 中更新表A,就会阻塞。会话A 已经锁定了这一行。这不是死锁;只是阻塞而已。我还没有
遇到过死锁,因为会话A 还有机会提交或回滚,这样会话B 就能继续。
如果我再回到会话A,试图更新表B,这就会导致一个死锁。要在这两个会话中选择一个作为“牺牲
品”,让它的语句回滚。

 

根据我的经验,导致死锁的头号原因是外键未加索引(第二号原因是表上的位图索引遭到并发更新)。在以下两种情况下,Oracle 在修改父表后会对子表加一个全表锁:
如果更新了父表的主键(倘若遵循关系数据库的原则,即主键应当是不可变的,这种情况
就很少见),由于外键上没有索引,所以子表会被锁住。
如果删除了父表中的一行,整个子表也会被锁住(由于外键上没有索引)。
在Oracle9i 及以上版本中,这些全表锁都是短期的,这意味着它们仅在DML 操作期间存在,而不是
在整个事务期间都存在。即便如此,这些全表锁还是可能(而且确实会)导致很严重的锁定问题.

 

6.锁升级

出现锁升级(lock escalation)时,系统会降低锁的粒度。举例来说,数据库系统可以把一个表的
100 个行级锁变成一个表级锁。现在你用的是“能锁住全部的一个锁”,一般而言,这还会锁住以前没有锁
定的大量数据。如果数据库认为锁是一种稀有资源,而且想避免锁的开销,这些数据库中就会频繁使用锁
升级。
注意Oracle 不会升级锁,从来不会。

Oracle 从来不会升级锁,但是它会执行锁转换(lock conversion)或锁提升(lock promotion),
这些词通常会与锁升级混淆。
注意“锁转换”和“锁提升”是同义词。Oracle 一般称这个过程为“锁转换”。
Oracle 会尽可能地在最低级别锁定(也就是说,限制最少的锁),如果必要,会把这个锁转换为一个
更受限的级别。例如,如果用FOR UPDATE 子句从表中选择一行,就会创建两个锁。一个锁放在所选的行上
(这是一个排他锁;任何人都不能以独占模式锁定这一行)。另一个锁是ROW SHARE TABLE 锁,放在表本身
上。这个锁能防止其他会话在表上放置一个排他锁,举例来说,这样能相应地防止这些会话改变表的结构。
另一个会话可以修改这个表中的任何其他行,而不会有冲突。假设表中有一个锁定的行,这样就可以成功
执行尽可能多的命令。
锁升级不是一个数据库“特性”。这不是我们想要的性质。如果数据库支持锁升级,就说明这个数据
库的锁定机制中存在某些内部开销,而且管理数百个锁需要做大量的工作。在Oracle 中,1 个锁的开销与
1 000 000 个锁是一样的,都没有开销。

7.锁类型

Oracle 中主要有3 类锁,具体是:
DML 锁(DML lock): DML 代表数据操纵语言(Data Manipulation Language)。一般来讲,
这表示SELECT、INSERT、UPDATE、MERGE 和DELETE 语句。DML 锁机制允许并发执行数据修改。
例如,DML 锁可能是特定数据行上的锁,或者是锁定表中所有行的表级锁。
DDL 锁(DDL lock): DDL 代表数据定义语言(Data Definition Language),如CREATE 和
ALTER 语句等。DDL 锁可以保护对象结构定义。
内部锁和闩:Oracle 使用这些锁来保护其内部数据结构。例如,Oracle 解析一个查询并生
成优化的查询计划时,它会把库缓存“临时闩”,将计划放在那里,以供其他会话使用。闩( latch)
是Oracle 采用的一种轻量级的低级串行化设备,功能上类似于锁。不要被“轻量级”这个词搞
糊涂或蒙骗了,你会看到,闩是数据库中导致竞争的一个常见原因。轻量级指的是闩的实现,
而不是闩的作用。

DML 锁:TX 锁(事务锁)--------事务发起第一个修改时会得到TX 锁(事务锁),而且会一直持有这个锁,直至事务执行提交(COMMIT)
或回滚(ROLLBACK)。

DML 锁:TM (DML Enqueue)锁---------TM 锁(TM lock)用于确保在修改表的内容时,表的结构不会改变。例如,如果你已经更新了一个表,
会得到这个表的一个TM 锁

DDL 锁:

在DDL 操作中会自动为对象加DDL 锁(DDL Lock),从而保护这些对象不会被其他会话所修改。例如,
如果我执行一个DDL 操作ALTERTABLE T,表T 上就会加一个排他DDL 锁,以防止其他会话得到这个表的DDL
锁和TM 锁。在DDL 语句执行期间会一直持有DDL 锁,一旦操作执行就立即释放DDL 锁。实际上,通常会把
DDL 语句包装在隐式提交(或提交/回滚对)中来执行这些工作。由于这个原因,在Oracle 中DDL 一定会
提交。每条CREATE、ALTER 等语句实际上都如下执行(这里用伪代码来展示):
因此,DDL 总会提交(即使提交不成功也会如此)。DDL 一开始就提交,一定要知道这一点。它首先提
交,因此如果必须回滚,它不会回滚你的事务。如果你执行了DDL,它会使你所执行的所有未执行的工作
成为永久性的,即使DDL 不成功也会如此。如果你需要执行DDL,但是不想让它提交你现有的事务,就可
以使用一个自治事务(autonomous transaction)。

闩:闩(latch)是轻量级的串行化设备,用于协调对共享数据结构、对象和文件的多用户访问。
闩就是一种锁,设计为只保持极短的一段时间(例如,修改一个内存中数据结构所需的时间)。闩用
于保护某些内存结构,如数据库块缓冲区缓存或共享池中的库缓存。一般会在内部以一种“愿意等待”
(willing to wait)模式请求闩。这说明,如果闩不可用,请求会话会睡眠很短的一段时间,并在以后再
次尝试这个操作

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值