2006年04月05日

在大型数据库系统中,为了保证数据的一致性,在对数据库中的数据进行操作时,系统会进行对数据相应的锁定。 
—- 这些锁定中有”只读锁”、”排它锁”,”共享排它锁”等多种类型,而且每种类型又有”行级锁”(一次锁住一条记录),”页级锁”(一次锁住一页,即数据库中存储记录的最小可分配单元),”表级锁”(锁住整个表)。 
—- 若为”行级排它锁”,则除被锁住的该行外,该表中其它行均可被其它的用户进行修改(Update)或删除(delete)操作,若为”表级排它锁”,则所有其它用户只能对该表进行查询(select)操作,而无法对其中的任何记录进行修改或删除。当程序对所做的修改进行提交(commit)或回滚后(rollback)后,锁住的资源便会得到释放,从而允许其它用户进行操作。 
—- 但是,有时,由于程序中的原因,锁住资源后长时间未对其工作进行提交;或是由于用户的原因,如调出需要修改的数据后,未及时修改并提交,而是放置于一旁;或是由于客户服务器方式中客户端出现”死机”,而服务器端却并未检测到,从而造成锁定的资源未被及时释放,影响到其它用户的操作。 
—- 因而,如何迅速地诊断出锁住资源的用户以及解决其锁定便是数据库管理员的一个挑战。 
—- 由于数据库应用系统越来越复杂, 一旦出现由于锁资源未及时释放的情况,便会引起对一相同表进行操作的大量用户无法进行操作,从而影响到系统的使用。此时,DBA应尽量快地解决问题。但是,由于在Oracle 8.0.x 中执行”获取正在等待锁资源的用户名”的查询语句 
select a.username, a.sid, a.serial#, b.id1
  from v$session a, v$lock b
  where a.lockwait = b.kaddr
—- 十分缓慢,(在 Oracle 7.3.4中执行很快),而且,执行”查找阻塞其它用户的用户进程”的查询语句 
    select a.username, a.sid, a.serial#, b.id1
      from v$session a, v$lock b
     where b.id1 in
           (select distinct e.id1
              from v$session d, v$lock e
             where d.lockwait = e.kaddr)
       and a.sid = b.sid
       and b.request = 0
—- 执行得也十分缓慢。因而,往往只好通过将 v$session 中状态为”inactive”(不活动)并且最后一次进行操作时间至当前已超过 20 分钟以上(last_call_et>20*60 秒)的用户进程清除,然后才使得问题得到解决。 
—- 但是,这种方法实际上是”把婴儿与脏水一起泼掉”。因为,有些用户的进程尽管也为”inactive”,并且也已有较长时间未活动,但是,那是由于他们处于锁等待状态。 
—- 因而,我想出了一个解决办法。即通过将问题发生时的 v$lock,v$session视图中的相关记录保存于自己建立的表中,再对该表进行查询,则速度大大提高,可以迅速发现问题。经实际使用,效果非常好。在接到用户反映后,几秒钟即可查出由于锁住资源而影响其它用户的进程,并进行相应的处理。 
—- 首先,以 dba 身份(不一定为system)登录入数据库中,创建三个基本表:my_session,my_lock, my_sqltext,并在将会进行查询的列上建立相应的索引。语句如下: rem 从 v$session 视图中取出关心的字段,创建 my_session 表,并在查询要用到的字段上创建索引,以加快查询速度 
drop table my_session;
create table my_session
as
select a.username, a.sid, a.serial#,
a.lockwait, a.machine,a.status,
a.last_call_et,a.sql_hash_value,a.program
  from v$session a
 where 1=2 ;

create unique index my_session_u1 on my_session(sid);
create index my_session_n2 on my_session(lockwait);
create index my_session_n3 on my_session(sql_hash_value);
—- rem 从 v$lock 视图中取出字段,创建 my_lock 表,并在查询要用到的字段上创建索引,以加快查询速度 
drop table my_lock;
create table my_lock
as
select id1, kaddr, sid, request,type
  from v$lock
 where 1=2;

create index my_lock_n1 on my_lock(sid);
create index my_lock_n2 on my_lock(kaddr);
—- rem 从 v$sqltext 视图中取出字段,创建 my_sqltext 表,并在查询要用到的字段上创建索引,以加快查询速度 
drop table my_sqltext;
create table my_sqltext
as
select hash_value , sql_text
  from v$sqltext
 where 1=2;

create index my_sqltext_n1 on my_sqltext ( hash_value);
—- 然后,创建一个 SQL 脚本文件,以便需要时可从 SQL*Plus 中直接调用。其中,首先用 truncate table 表名命令将表中的记录删除。之所以用 truncate 命令,而不是用delete 命令,是因为delete 命令执行时,将会产生重演记录,速度较慢,而且索引所占的空间并未真正释放,若反复做 insert及delete,则索引所占的空间会不断增长,查询速度也会变慢。而 truncate命令不产生重演记录,速度执行较delete快,而且索引空间被相应地释放出来。删除记录后,再将三个视图中的相关记录插入自己创建的三个表中。最后,对其进行查询,由于有索引,同时由于在插入时条件过滤后,记录数相对来说较少,因而查询速度很快,马上可以看到其结果。 
—- 此时,若发现该阻塞其它用户进程的进程是正常操作中,则可通知该用户对其进行提交,从而达到释放锁资源的目的;若为未正常操作,即,其状态为”inactive”,且其last_call_et已为较多长时间,则可执行以下语句将该进程进行清除,系统会自动对其进行回滚,从而释放锁住的资源。 
   alter system kill session 'sid, serial#';  
—- SQL 脚本如下: 
set echo off
set feedback off
prompt '删除旧记录…..'
truncate table my_session;
truncate table my_lock;
truncate table my_sqltext;

prompt '获取数据…..'
insert into my_session
select a.username, a.sid, a.serial#,
       a.lockwait, a.machine,a.status,
       a.last_call_et,a.sql_hash_value,a.program
  from v$session a
 where nvl(a.username,'NULL')< >'NULL;

insert into my_lock
select id1, kaddr, sid, request,type
  from v$lock;

insert into my_sqltext
select hash_value , sql_text
  from v$sqltext s, my_session m
 where s.hash_value=m.sql_hash_value;

column username format a10
column machine format a15
column last_call_et format 99999 heading ”Seconds”
column sid format 9999

prompt ”正在等待别人的用户”
select a.sid, a.serial#, 
a.machine,a.last_call_et, a.username, b.id1
  from my_session a, my_lock b
 where a.lockwait = b.kaddr;

prompt ”被等待的用户”
select a.sid, a.serial#, 
a. machine,  a.last_call_et,a.username,
b.  b.type,a.status,b.id1
  from my_session a, my_lock b
 where b.id1 in
      (select distinct e.id1
         from my_session d, my_lock e
        where d.lockwait = e.kaddr)
   and a.sid = b.sid
   and b.request=0;

prompt ”查出其  sql ”
select a.username, a.sid, a.serial#,
 b.id1, b.type, c.sql_text
  from my_session a, my_lock b, my_sqltext c
 where b.id1 in
       (select distinct e.id1
          from my_session d, my_lock e
         where d.lockwait = e.kaddr)
   and a.sid = b.sid
   and b.request=0
   and c.hash_value =a.sql_hash_value;
  
—- 以上思路也可用于其它大型数据库系统如 Informix, Sybase,DB2中。通过使用该脚本,可以极大地提高获取系统中当前锁等待的情况,从而及时解决数据库应用系统中的锁等待问题。而且,由于实际上已取出其 program 名及相应的 sql 语句,故可以在事后将其记录下来,交给其开发人员进行分析并从根本上得到解决。 

本篇文章使用aigaogao Blog软件发布, “我的Blog要备份”

1 引言
  在大型数据库设计中,会经常用到触发器。它的特点是:一旦被定义,就存在于后台数据库系统(server,服务器方)中,并会在相应条件下自动地隐式执行,从而使得它的设计既与前台(client,客户机方)的平台无关,又免除了前台相关的数据操作设计。
  在文献[1]中,列举了触发器的几种应用:审计;复杂的完整性约束;复杂的安全性授权;事件登录;列值导出;分布式数据库中表复制。

2 触发器的另外几种应用
2.1 数据分散——集中式模型设计
  在实际开发过程中,经常遇到这样的数据维护要求:单位由多个部门组成,要求各部门只能维护本部门的数据,但另一方面,又需要将分散到各部门的数据集中起来进行汇总,得到本单位的汇总数据。如一个学校有多个系,学校需要各系的成绩汇总;一个工厂有多个生产车间,工厂需要各车间的产量汇总;一个公司有多个销售部门,公司需要各部门的销量汇总等等。
  在这种情况下,如果不使用触发器的话,数据库设计就存在困难:
  . 如果为每个部门都建立一个表,显然难以得到汇总的数据(在这种情况下,无法利用视图机制);
  . 如果所有的部门都共享一个表的话(这时,这张表中的数据实际就是汇总的数据),因为每个部门需要维护数据,所以都对这个表有修改权,因此在数据安全上难以控制。
  使用触发器的话,上述问题便可迎刃而解:为每个部门建立一个表(该部门的所有权限只限于对此表有修改权),再为汇总数据也建立一个表,然后在每个部门表上建立触发器,使得部门表上有数据更新时,便会对应地更改汇总表中的相关数据(见图1)。



图1 触发器应用于数据分散——集中式模型

  在这种模型中,要注意设计好部门表相关字段的完整性约束,使各部门表内的数据是唯一的,以防止不同部门表出现相同的数据记录,从而导致在汇总表中出现混乱。 
 
2.2 历史数据导出
  数据库中的表只记载最新的数据,而不记载历史数据。但在很多情况下,历史数据的记载与分析反而比现实数据更有意义(这也正是数据仓库与数据库的区别之一),比如学校中学号的变动,工厂定额的更改,公司产品和原材料价格的变化、股票的升跌等等,它们都需要记录历史数据。
  如何使数据库也能记载历史数据呢?使用触发器可以解决这类问题。
  建立这类触发器的步骤是:建立数据表后,再建立对应的历史表(一般而言,历史表在字段组成上是数据表的超集,即在原数据表字段上再增加有关时间的字段),然后在两者之间设立触发器(见图2)。这样,每当数据表有数据变动,触发器便将变动的数据记入历史数据表中,从而达到自动记录历史数据的目的。 
 


图2 历史数据的导出模式

2.3 应用系统间的数据接口
  一个完整的信息系统的建设一般不是一步到位的,往往是分期分批完成,而不同期次的系统往往又会有数据传递,然而由于需求发生变化或是其他原因,不同期次系统的数据库设计在表结构甚至字段上的设计都可能会互不一致(即使是在同一期的开发过程中,由于总体设计或数据字典方面的偏差或不足,或者需要集成多家系统,这种现象也会经常出现)。在不可能重建这些系统的情况下,它们之间的数据能无缝传递吗?换言之,它们之间能够做到无缝连接吗? 
 
  在这种情况下,触发器可以是一种较好的解决方式:建立中间表,中间表的设计符合需方应用系统的设计格式,而它的数据又与供方应用系统的数据保持一致。(见图3)



图3 中间作为不同应用系统间的数据接口

  要注意的一点是:图示应用系统间的数据是单向流动的(即数据传递);如果数据需要双向流动(即数据交换),那么在触发器设计中应有退出机制,以避免发生触发器的递归。

3 结语
  触发器对数据库开发过程中遇到的问题,往往会有独到的解决方法。触发器能使数据库的设计变得简洁和高效。文中的3个例子,代表了触发器3个方面的典型应用。

本篇文章使用aigaogao Blog软件发布, “我的Blog要备份”