数据的完整性和并发性:
在网络数据库处理数据中,需要保证数据的完整性、多事务的并发性和数据的一致性。数据库系统必须最大限度的保证数据内容的有效性和合理性,使得数据合法与合理,即保证数据的完整性。
而网络(多用户)数据库应用系统中,通常都会碰到多个事务同时请求修改同一数据。数据库的并发性处理保证数据库系统据能够合理有效地处理多个事务请求,在数据库治理系统协调下,宏观表现为并行处理,微观为逐个串行处理,保证数据的一致性,从而避免数据处理中的 “脏”数据发生。在数据库系统本身对数据的并发性和一致性有基本控制能力下,以Oracle为例的关系数据库系统已经能保证数据的并发性能,而数据库的应用设计则需要以考虑数据在并发过程中的一致性为重点。一个简单的例子:甲乙同时从数据库中读出了一条相同的数据记录。甲把数据作了修改,并且作了事务提交。(而乙此时并不知道数据已经被修改了。)称乙已经读出的数据为脏数据,破坏了数据的一致性 , 为了保证数据的一致性,此时有必要对数据进行加锁。再例如:一银行帐户有¥6000, 此时甲要从中提出¥5000,而乙也同时要从中提出¥5000,双方在不加锁查询时,双方均认为可以提款,事实上肯定有一方的提交会失败。原因是在最终事务提交时,要对数据作了加锁查询效验处理。
并发性和一致性
数据库中的数据锁 为了防止多个用户事务交替操作数据库对象,破坏性数据库完整性,同时又要保证数据的并发性和一致性,数据库治理系统一般提供了各种类型的封锁机制,可以自动为一个事务封锁资源,而在事务不需要时自动释放。以使另一个需要独占该资源的事务能够继续进程,而不会造成事务死锁。数据库对象的封锁完全能够由数据库系统自动完成,ORACLE (一般情况数据库)对加锁有以下特征:A 多种封锁模式.例如:系统表的转有锁,共享锁。B 完备的自动加/解锁功能和释放机制。C 用低级别的封锁换取高级别的并发处理。D能够在客户端/服务器模式下自动封锁过程。Oracle 的主要封锁类型有:
A 数据锁,用于封锁表或表中的指定行.属于数据操纵锁,
B 字典锁,用于保护数据库对象(表,索引)的创建维护.属于数据定义锁
C 内部锁。用于保护数据库的内部结构.
正常情况下,为完成数据操纵,一般需要两种级别的封锁方式,封锁整表或行封。通常只在热备份数据,时候,需要对整表进行锁定. 而应用程序的事务处理通常是数据行的数据操纵语句:
INSERT,
UPDATE,
DELETE,
SELECT (FOR UPDATE)
以上操作在执行过程中,数据库都会对相应的行做数据行封锁.
一般情况下,只要行封事务不放弃封锁, 此行就不可能被其他任何用户的事务修改(部分语句,如SELECT仍然答应不加锁地读出)。只有当数据库重新启动,数据表空间离线/在线处理,当事务正常提交、回滚时,行封事务才能被自动解除封锁. (通常 ROLLBACK ,COMMIT 可以自动解除行封锁。)在表上的数据锁的高低级别为:
简单的SELECT 语句无任何锁,INSERT, UPDATE, DELETE为行专有(RX),答应在操作中多次修改这些行, 提交或回滚后自动解除封锁,
SELECT (FOR UPDATE )为专有行的数据锁.在应用程序中使用最多.也是通常对数据行进行控制的简单有效的方法。例如:在Windows95下,同时分别用两个以上SQL*PLUS,以相同的用户联接Oracle Server相同的实例,在其中一个窗口下做Update/Delete中任意操作,只要没有提交,均不影响其它窗口的不加锁查询,但是不可以再其它窗口下对同一条记录作Update/Delete 操作,Insert没有提交以前,其它操作均无效。只有当一方事务完成(提交/回滚)以后立即释放相应资源,另一方申请的事务才能成功响应。
行锁在应用程序的注重事项
在实际应用中,可能由于一次事务提交的数据太多,造成数据加锁时间太长,破坏了数据的并发共享特性。假如事务独占了相应的资源,没有得到释放,而另一个事务占有该事务的下一个资源,就有可能造成数据库应用系统死锁。另外,假如在加锁过程中,出现网络中断,客户端掉电等突发问题,被加锁的资源无法解锁,其它应用程序将无法继续获得已经被加锁的资源,也可以出现死锁. 通常只有DBA才能取消事务或通过数据表空间的在线/离线操作解除加锁事务(通常大型应用系统这样做有一定的风险)。假如在程序应用中经常出现这样的问题,则造成应用数据库应用系统无法正常工作。虽然这样的情况在C/S程序的开发/应用中并不经常出现,但是在应用程序设计中都是应该严格考虑的。
常用的数据加锁方法(以ORACLE 与PB连接为例)
A 在客户端编程方法:在客户端的编程处理的方法最简单,调试也很直观、方便。但是这种方法仅仅在中、小型网络中可以使用。因为当网络用户数量增加,或应用程序在客户端应用过程中意外中断(死机,退出),则可能造成数据行的死锁。其他用户无法正确独占加锁。但是实际应用表明此种方法在网络自身可靠性好、用户数量不大时经常被采纳。
下面是用PowerBuilder 与 ORACLE的数据联结处理实例。
/**********************************************************
*变量:ls_goodscode , li_store_qty
* 表:AMS.TAB_STORE_GOODSINFO
*
*功能:从库存数中减去 2
*
**********************************************************/
STRING ls_goodscode
INTEGER li_stroe_qty
ls_goodscode = sle_goodscode.text
SELECT QTY
INTO li_store_qty
FROM AMS.TAB_STORE_GOODSINFO
WHERE GOODS_NO = SUBSTR(:ls_goodscode , 1, 13)
AND SERIAL_NO = SUBSTR(:ls_goodscode , 14, 4)
FOR UPDATE NOWAITE ; // 对数据加锁,不等待。
if SQLCA.SQLCODE = 0 then // if _01
if li_store_qty >=2 then // if _02
UPDATE AMS.TAB_STORE_GOODSINFO
SET QTY = :li_store_qty - 2
WHERE GOODS_NO = SUBSTR(:ls_goodscode , 1, 13)
AND SERIAL_NO = SUBSTR(:ls_goodscode , 14, 4);
if SQLCA.SQLCODE = 0 then
COMMIT ; // 成功提交
else
ROLLBACK ;// 及时回滚解锁
messagebox("系统提示", "库存数量已经不足!");
end if
else
ROLLBACK;
messagebox("系统提示", "库存数量已经不足!");
end if // end if 02
else //其中 ORA-00054: resource busy and acquire with NOWAIT specified
ROLLBACK ;// 及时回滚解锁
messagebox("系统提示", "数据读入有误,请稍候再试!",stopsign! ok!);
end if // end if 01
//同时在当前的FROM WINDOW中的CLOSEQUERY中显示写明ROLLBACK ;
设计说明:当第一句SELECT完成以后,假如数据加锁正确,则可以继续工作,但是此时可能数据被别的应用程序控制,则返回Oracle的错误代码:ORA-00054说明共享冲突。此句可以用一个有限的循环控制,但是一定要防止数据死锁。同时为了保证万无一失,在当前FORM的关闭事件中显示写明回滚(ROLLBACK)。
B 在客户端按传输(Transaction )方法处理:将要提交的所有SQL任务作为多个子串传送到服务器端,用动态SQL方法在客户端一次传输完成后,在服务器端一次执行完成,只有当事务执行正确,才能提交, 否则回滚。这样在执行多个表操作提交时,数据执行加锁的时间影响将大大减小。这样极大程度的缓解了多表共享数据的加锁问题。好处是不需要服务器端的调试,但是,编程调试过程复杂。但是实际效果与第一种方法基本一致。
C 存储过程方法:这种方法是直接调用服务器的存储过程,所有的对Client端程序代码可以大大减少,数据一致性,并发性,安全性都能得到保证。客户端的异常通常不会影起数据的死锁。
下面是用POWERBUILDER 与 ORACLE的数据联结处理实例。
/**********************************************************
*变量:ls_goodscode , li_store_qty
* 表:AMS.TAB_STORE_GOODSINFO
*
*功能:从库存数中减去 2
*
**********************************************************/
STRING ls_goodscode
INTEGER li_stroe_qty
ls_goodscode = st.text
if len(ls_goodscode) = 17 then
DECLARE pstore_goods PROCEDURE FOR pd_store_goods(:ls_goodscode ) ;
// 从库存数中减去 2
execute pstore_goods;
if SQLCA.SLQCODE < 0 then
ROLLBACK ;
messagebox("系统提示", "数据操作有误,请稍候再试!",stopsign! ok!);
end if
else
messagebox("系统提示", "数据输入有误!",information!, ok!);
end if
//同时在当前的FROM WINDOW中的CLOSEQUERY中显示写明ROLLBACK ;
在服务器端
-- procedure pd_store_goods
create or replace procedure pd_store_goods ( ls_goodscode in varchar2 )
IS
ln_store_qty number(10) ;
begin
SELECT nvl(QTY, -1)
INTO ln_store_qty
FROM AMS.TAB_STORE_GOODS_INFO
WHERE GOODS_NO = SUBSTR(ls_goodscode , 1, 13)
AND SERIAL_NO = SUBSTR(ls_goodscode , 14, 4)
FOR UPDATE NOWAIT;
if ln_store_qty >= 2 then
UPDATE AMS.TAB_STORE_GOODS_INFO
SET QTY = ln_store_qty - 2
WHERE GOODS_NO = SUBSTR(ls_goodscode , 1, 13)
AND SERIAL_NO = SUBSTR(ls_goodscode , 14, 4);
COMMIT ;
else
ROLLBACK ;
RAISE_application_ERROR(-200002, 'store not enougth!');
end if;
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-200001, 'store not SCUESSCULL!');
end ;
设计说明:首先在SQL*PLUS中运行存储过程,在PB的程序端调用。只有当SELECT语句正常加锁后, UPDATE语句才可以正常执行。在由此可见在客户端不知道存储过程运行的内容,调试/维护工作较麻烦。但是,因为所有的运行都在服务器端,基本可以忽略除服务器端以外的所有问题,如网络的通畅,客户端异常等等。实际应用中对大型繁重的数据处理大多采用这样的方法。
注重与小结:
在Oracle的连接中,需要解释的是数据行加锁在同一个事务中是不可见的,但是执行权限级别顺序为:Delete >Update >Insert >Select.
本文以Oracle为例,说明在数据库处理过程中,在数据库设计上,要考虑数据的数据的完整性,在数据库的应用设计上必须着重事务的并发性和数据的一致性。对于数据行锁要谨慎对待。