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

 一.锁

数据库使用锁(lock) 来保证任何给定时刻最多只有一个事务在修改给定的一段数据。实质上讲,
正是锁机制才使并发控制成为可能。例如,如果没有某种锁定模型来阻止对同一行的并发更新,数据库就
不可能提供多用户访问。不过,如果滥用或者使用不当,锁反倒会阻碍并发性。如果你或数据库本身不必
要地对数据锁定,能并发地完成操作的人数就会减少。因此,要理解什么是锁定,你的数据库中锁定是怎
样工作的,这对于开发可扩缩的、正确的应用至关重要。
还有一点很重要,你要知道每个数据库会以不同的方式实现锁定。有些数据库可能有页级锁,另外
一些则有行级锁;有些实现会把行级锁升级为页级锁,另外一些则不然;有些使用读锁,另外一些不使用;
有些通过锁定实现串行化事务,另外一些则通过数据的读一致视图来实现(没有锁)。如果你不清楚这些微
小的差别,它们就会逐步膨胀为严重的性能问题,甚至演变成致命的bug。
以下是对Oracle 锁定策略的总结:
Oracle 只在修改时才对数据加行级锁。正常情况下不会升级到块级锁或表级锁(不过两段提
交期间的一段很短的时间内除外,这是一个不常见的操作)。
如果只是读数据,Oracle 绝不会对数据锁定。不会因为简单的读操作在数据行上锁定。
60
/ 849
写入器(writer)不会阻塞读取器(reader)。换种说法:读(read)不会被写(write)阻
塞。这一点几乎与其他所有数据库都不一样。在其他数据库中,读往往会被写阻塞。尽管听上
去这个特性似乎很不错(一般情况下确实如此),但是,如果你没有充分理解这个思想,而且想
通过应用逻辑对应用施加完整性约束,就极有可能做得不对。第7 章介绍并发控制时还会更详
细地讨论这个内容。
写入器想写某行数据,但另一个写入器已经锁定了这行数据,此时该写入器才会被阻塞。读
取器绝对不会阻塞写入器。
开发应用时必须考虑到这些因素,而且还要认识到这个策略是Oracle 所独有的,每个数据库实现锁
定的方法都存在细微的差别。即使你在应用中使用最通用的SQL,由于各数据库开发商采用的锁定和并发
控制模型不同,你的应用也可能有不同的表现。倘若开发人员不了解自己的数据库如何处理并发性,肯定
会遇到数据完整性问题。(开发人员从另外某种数据库转向Oracle,或者从Oracle 转向其他数据库时,如
果没有考虑在应用中采用不同的并发机制,这种情况就尤为常见。)

 

2.防止丢失更新

Oracle 的无阻塞方法有一个副作用,如果确实想保证一次最多只有一个用户访问一行数据,开发人
员就得自己做些工作。
考虑下面这个例子。一位开发人员向我展示了他刚开发的一个资源调度程序(可以用来调度会议室、
投影仪等资源),这个程序正在部署当中。应用中实现了这样一个业务规则:在给定的任何时间段都不能
将一种资源分配给多个人。也就是说,应用中包含了实现这个业务规则的代码,它会明确地检查此前这个
时间片没有分配给其他用户(至少,这个开发人员认为是这样)。这段代码先查询SCHEDULES 表,如果不
存在与该时间片重叠的记录行(该时间片尚未分配),则插入新行。所以,开发人员主要考虑两个表:
在分配资源(如预订房间)之前,应用将查询:
create table resources ( resource_name varchar2(25) primary key, ... );
create table schedules
( resource_name references resources,
start_time date not null,
end_time date not null,
check (start_time < end_time ),
primary key(resource_name,start_time)
);
select count(*)
from schedules
where resource_name = :room_name

看上去很简单,也很安全(在开发人员看来):如果得到的计数为0,这个房间就是你的了。如果返
回的数非0,那在此期间你就不能预订这个房间。了解他的逻辑后,我建立了一个非常简单的测试,来展
示这个应用运行时可能出现的一个错误,这个错误极难跟踪,而且事后也很难诊断。有人甚至以为这必定
是一个数据库bug。
我所做的其实很简单,就是让另外一个人使用这个开发人员旁边的一台机器,两个人都浏览同一个屏
幕,然后一起数到3 时,两人都单击Go 按钮,尽量同时预订同一个房间,一个人想预订下午3:00 到下午
4:00 这个时段,另一个人要预订下午3:30 到下午4:00 这个时段。结果两个人都预订成功。这个逻辑独立
执行时原本能很好地工作,但到多用户环境中就不行了。为什么会出现这个问题?部分原因就在于Oracle
的非阻塞读。这两个会话都不会阻塞对方,它们只是运行查询,然后完成调度房间的逻辑。两个会话都通
过运行查询来查找是否已经有预订,尽管另一个会话可能已经开始修改SCHEDULES 表,但查询看不到这些
修改(所做的修改在提交之前对其他会话来说是不可见的,而等到提交时已为时过晚)。由于这两个会话并
没有试图修改SCHEDULES 表中的同一行,所以它们不会相互阻塞。由此说来,这个应用不能像预期的那样
保证前面提到的业务规则。
开发人员需要一种方法使得这个业务规则在多用户环境下也能得到保证,也就是要确保一次只有一个
人预订一种给定的资源。在这种情况下,解决方案就是加入他自己的一些串行化机制。他的做法是,在对
SCHEDULES 表进行修改之前,先对RESOURCES 表中的父行锁定。这样一来, SCHEDULES 表中针对给定
RESOURCE_NAME 值的所有修改都必须依次按顺序进行,一次只能执行一个修改。也就是说,要预订资源X
一段时间,就要锁定RESOURCES 表中对应X 的那一行,然后修改SCHEDULES 表。所以,除了前面的count(*)
外,开发人员首先需要完成以下查询:
这里,他在调度资源之前先锁定了资源(这里指房间),换句话说,就是在SCHEDULES 表中查询该资
源的预订情况之前先锁定资源。通过锁定所要调度的资源,开发人员可以确保别人不会同时修改对这个资
源的调度。其他人都必须等待,直到他提交了事务为止,此时就能看到他所做的调度。这样就杜绝了调度
重叠的可能性。
开发人员必须了解到,在多用户环境中,他们必须不时地采用多线程编程中使用的一些技术。在这里,
FOR UPDATE 子句的作用就像是一个信号量(semaphore),只允许串行访问RESOURCES 表中特定的行,这样
就能确保不会出现两个人同时调度的情况。我建议把这个逻辑实现为一个事务API,也就是说,把所有逻
辑都打包进一个存储过程中,只允许应用通过这个API 修改数据。代码如下:
and (start_time <= :new_end_time)
and (end_time >= :new_start_time)
select * from resources where resource_name = :room_name FOR UPDATE;
create or replace procedure schedule_resource
( p_resource_name in varchar2,
p_start_time in date,
p_end_time in date
)
as

首先在RESOURCES 表中锁定我们想调度的那个资源的相应行。如果别人已经锁定了这一行,我们就会
阻塞并等待:
既然我们已经有了锁,那么只有我们能在这个SCHEDULES 表中插入对应此资源名的调度,所以如下查
看这个表是安全的:
如果能运行到这里而没有发生错误,就可以安全地在SCHEDULES 表中插入预订资源的相应记录行,而
不用担心出现重叠:
l_resource_name resources.resource_name%type;
l_cnt number;
begin
select resource_name into l_resource_name
from resources
where resource_name = p_resource_name
FOR UPDATE;
select count(*)
into l_cnt
from schedules
where resource_name = p_resource_name
and (start_time <= p_end_time)
and (end_time >= p_start_time);
if ( l_cnt <> 0 )
then
raise_application_error
(-20001, 'Room is already booked!' );
end if;
insert into schedules
( resource_name, start_time, end_time )
values

这个解决方案仍是高度并发的,因为可能有数以千计要预订的资源。这里的做法是,确保任何时刻只
能有一个人修改资源。这是一种很少见的情况,在此要对并不会真正更新的数据手动锁定。我们要知道哪
些情况下需要这样做,还要知道哪些情况下不需要这样做(稍后会给出这样一个例子),这同样很重要。另
外,如果别人只是读取数据,就不会锁定资源不让他们读(但在其他数据库中可能不是这样),所以这种
解决方案可以很好地扩缩。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值