志当存高远,功到自然成!

少年强则中国强,少年进步则中国进步!

BlogJava 首页 新随笔 联系 聚合 管理
  53 Posts :: 2 Stories :: 2 Comments :: 0 Trackbacks

#

首先,有一句话要认识 : 80%的性能问题由SQL语句引起。

经过看 SYBASE 的书,结合从 MSSQL 迁移过来的系统过程 ,发现以下几个问题比较重要:
     经验一、where 条件左边最好不要使用函数,比如 ★★★★★
           select ...     where     datediff(day,date1,getdate())>;0
           这样即使在 date1 列上建立了索引,也可能不会使用索引,而使用表扫描。
            这样的语句要重新规划设计,保证不使用函数也能够实现。通过修改,一个系统过程的运行效率提高大约100倍!此外不要使用诸如like '%ab',不能充分利用索引,而要在%前加字符
      经验二、两个比较字段最好使用相同数据类型,而不是兼容数据类型。比如 int 与 numeric(感觉一般不是太明显)★★

      经验三、复合索引的非前导列做条件时,基本没有起到索引的作用。★★★★★
            比如 create index idx_tablename_ab on tablename(a,b)
                update tablename set c = XX where b>;= XXX and ...
                在这个语句中,基本上索引没有发挥作用。 导致表扫描引起blocking 甚至运行十几分钟后报告失败。
                一定要认真检查 改正措施: 在接口中附加条件
                    update tablename set c = XX where a = XXX     and b>;= XXX
                 或者建立索引类似于
                    create index idx_tablename_ba on tablename(b,a)  
      经验四、 多个大表的关联查询,如果性能不好,并且其中一个大表中取的数据比较少,可以考虑将查询分两步执行。★★★★
             先将一个大表中的少部分数据 select * into #1 from largetable1 where ...
              然后再用 #1 去做关联,效果可能会好不少。(前提:生成 #1表应该使用比较好的索引,速度比较快)

      经验五、 tempdb 的使用。★★★★★
              最好多用 select into     ,这样不记日志 ,尤其是有大量数据的报表时。虽然写起来麻烦,但值得。
                create table #tmp1 (......)这样写性能不好。尤其是大量使用时,容易发生tempdb 争用。
      经验六、 系统级别的参数设置     ★★★★
               一定要估计一下,不要使用太多,占用资源     ,太少,发生性能问题。
                 连接数,索引打开个数、锁个数 等、 当然 ,内存配置不要有明显的问题,比如,procedure cache
                不够 (一般缺省20%,如果觉得太多,可以减少一些)。如果做报表经常使用大数据量读,可以考虑使用
                16Kdata cache

       经验七、索引的建立。很重要。★★★★★
                  clustered index     /nonclustered index 的差异,自己要搞清楚。各适用场合,另外如果
                clustered index 不允许 重复数,也一定要说明。
               索引设计是以为数据访问快速为原则的,不能 完全(!!) 参照数据逻辑设计的,逻辑设计时的一些东西,
              可能对物理访问不起作用

       经验八、统计数据的更新:大约10天进行 update statistics     ,sp_recompile table_name(★★★)

       经验九、强制索引使用 (★★★★)
             如果怀疑有表访问时不是使用索引,而且这些条件字段上建立了合适的索引,可以强制使用
               select * from tableA (index idx_name) where ...
              这个对一些报表程序可能比较有用。

       经验十、找一个好的监视工具 ★★★
             工欲善其事,比先利其器,一点都不错呀。
              我用 DBartisian 5.4 ,监视哪些表被锁定时间长, blocking 等
                  还有 sp_object_status 20:00:00 , sp_sysmon 20:00:00 等
       以上是我的一点经验,在不到一个月的时间内,我修改了20个左右的语句和系统过程     ,

         系统性能明显改善,cpu利用 高峰时大约50% 平时 不到30%IO 明显改善。所有月报表能顺利完成 5min 以内。

    经验十一: 综合以上对IN/EXISTS的讨论,我们可以得出一个基本通用的结论:IN适合于外表大而内表小的情况;EXISTS适合于外表小而内表大的情况。


         另外,系统中确认不使用的中间数据,可以进行转移。这些要看系统的情况哦
          最后祝你好运气。

以上为个人经验,欢迎批评指正!     

     呵呵 写完后忘记一个     一定要注意热点表 ,这是影响并发问题的一个潜在因素。!解决方法: 行锁模式 如果表的行比较小,可以故意增加一些不用的字段
     比如     char(200)     让一页中存放的行不要太多。

posted @ 2008-07-05 17:44 存鹰之心于高远,取鹰之志而凌云,习鹰之性以涉险,融鹰之神在山巅. 阅读(660) | 评论 (0)编辑 收藏

维护目的:

监测数据库的当前运行状况,保证数据库稳定运行。

做好数据库的日常的备份工作,减轻问题发生时的风险和责任

检测数据库的整体运行状况,对数据库的性能进行调整,保证数据库高效的运行。

可以减少紧急故障发生频率,减少对系统的影响。

尽早发现系统存在的潜在问题,使可能的故障消除在萌芽状态。

数据库日常维护的主要内容
监测数据库运行情况

监控CPU和I/O的使用情况

监控空间的使用情况

监控数据库的错误日志errorlog

制定一个合理的备份计划

检查数据库的一致性

数据库的排错

数据库性能调整

数据库日常维护的方法

数据库服务和备份服务的启动和关闭方法

数据库和备份服务的启动

$cd  $SYBASE/ASE-12_*/install

$startserver –f RUN_Servername(Servername为你的数据库服务名)

$startserver –f RUN_Servername_back

数据库和备份服务的关闭

isql –Usa –Pxxx –Sservername

>shutdown SYB_BACKUP (关闭备份服务)

>go

>use dbname (用户库)

>go

>checkpoint

>go

>shutdown

>go

查看sybase用户的运行环境是否正常

$env

查看SYBASE,SYBASE_ASE,SYBASE_OCS等环境是否正常

查看数据库服务和备份服务进程是否运行正常

$ps –ef | grep  dataserver

$ps –ef | grep  backupserver

查看数据库的版本和补丁信息

$dataserver –v

或是

isql –Usa –Pxxx –Sservername

>select  @@version

检查数据库的配置是否合理.

检查数据库内存分配、锁个数、存储过程缓冲、多CPU配置、用户连接配置、网络包尺寸等重要参数的设置。

命令:

sp_configure ‘allocate max shared memory’

sp_configure ‘max memory‘

sp_configure ‘procedure cache size‘

sp_configure ‘user log cache size’

sp_configure ‘default network packet size’

sp_cacheconfig

sp_configure ‘number of user connections‘

sp_configure ‘number of locks‘

sp_configure ‘number of engines at startup’

sp_configure ‘max online engines’

sp_configure “number of open index”

sp_configure “number of open objects”

sp_configure “number of open partitions”

查看数据库的用户,数据库的锁状况

sp_who(用户)

sp_lock(锁)

select spid,blocked from sysprocesses where blocked>0(检查阻塞进程)

查看最早进程,发现不完整事务

select *  from master..syslogshold

其中对应的spid为最早的进程,如果最早的进程开始时间距离当前时间已经非常长(如2、3天),则可能该进程有问题,确认后,应该将其kill,下面有一个存储过程来检测不完整的事务,可以将该存储过程放在操作系统的crontab中。

create proc proc_kill
@last_min int=720

AS

/*

** kill the processes that didn’t commit for a impossible time

** the parameter's time unit is minute,default is 12 hours

** the procedure is just for user transactions ,not  for xa transaction

** you can add this procedure to your opration system crontab

*/

declare @spid     int

declare @cspid     char(20)

select spid into #killtab from master..syslogshold where datediff(mi,starttime,getdate()) >@last_min and spid>0

declare t1_cur cursor for select * from #killtab

open t1_cur

fetch t1_cur into @spid

while @@sqlstatus!=2

begin

     select @cspid=convert(char(20),@spid)

     select "kill "+@cspid
exec("kill "+@cspid)
     fetch t1_cur into @spid

end

查看CPU和I/O的使用

#sar #iostat

#vmstat

查看Sybase的资源使用

sp_sysmon “00:10:00”

如果CPU或是I/O的使用率长期高于80%,则要看系统使用是否正常还是系统资源配置不够,具体细节可参考下面数据库的优化部分。

定期检查数据库日志使用的情况,定期清除数据库的日志

如果数据库日志满,则业务无法进行。

sp_helpdb dbname(用户库名)

发现数据库的日志空间不足,应立刻清除已经完成的事务,或是根据需要扩大日志空间。

定期清除日志:

dump tran dbname with  truncate_only

可以将上述的命令放在crontab中。

定期检查磁盘空间的使用。 如果发现操作系统有磁盘分区使用达到100%,应尽快处理。

#df -k

定期检查数据库的错误日志(errorlog),如果错误日志中发现有数据库的严重错误,应立即处理。

有计划的截断数据库的错误日志。

数据库运行一定时间后,可以在数据库没有业务时,(下班或周末),将数据库stop后,将错误日志更名,然后重新启动数据库,产生一个新的数据库错误日志。

在错误日志中,以下的错误级别为不严重错误,通常是用户错误,如语法,读写权限空间不足等。
10 Status information

11  Specified database object not found

12 Wrong datatype encountered

13 User transaction syntax error

14 Insufficient permissions to execute commands

15 Syntax error in SQL statement

16 Miscellaneous user error

 Hardware/software errors

17 Insufficient resources

18 Non-fatal internal error

在错误级别17中,要注意错误号1105,1105表示空间不足(数据库数据或是日志)。

在错误级别18中,最多的是1608,1608表示一个客户连接不是正常方式退出的,

这类错误不用关注。

以下错误级别为严重错误,通常是系统(数据库、磁盘损坏、操作系统)错误
19 Fatal error in resource

20 Fatal error in current process

21 Fatal error in database process

22 Fatal error: table integrity suspect

23 Fatal error: database integrity suspect

24 Hardware error or system table corruption

在错误日志中,可以按照"Sybase Technical Support"来搜索,

找到的错误通常是严重错误。

检查数据库的一致性
检测系统数据库、用户数据库的数据分配页物理可用性,检查数据库系统表和用户表的数据完整性,查看是否有表损坏(可能是逻辑也可能是物理的损坏,如磁盘错误可能导致数据库的表损坏)

做数据库的一致性通常需要单用户状态,并且都是一些耗时操作(如果数据库大的话),因此,这些检查可以考虑在系统检修时运行。

dbcc checkalloc(dbname)检查数据库的空间分配,必须在单用户下执行

checkcatalog(dbname) 检查系统表的一致性

dbcc checkdb(dbname) 检查用户数据库的数据库一致性(包括系统表和用户表)

dbcc checktable(tablename) 检查一张表的数据一致性。

数据库的备份
数据库的备份对于日常的维护来说十分重要,系统管理员一定要注意数据库每天都有成功备份。需要检查备份的介质(磁盘或是磁带)是否正常。

备份命令:

dump database  to ‘/xx/xxx’ (设备名或是磁盘上的文件名)

用户可以规划一个备份的计划,然后将备份的命令放在crontab 中,让系统自动定时做数据库的备份。

数据库的排错
数据库通常的错误主要是以下几种

数据库服务无法启动

解决:查看数据库的错误日志,根据错误日志找到无法启动的原因,原因通常是ip地址,端口不对或是被占用,内存配置过大,或是数据库设备的属性不对,sybase用户没有访问权限。

数据库不能恢复(recovery)

解决:查看数据库的错误日志,找到数据库不能恢复的原因,然后做相应的处理。通常是由于系统突然断电或是系统非正常关机。

用户表不能访问

解决:查看数据库的错误日志,找到不能访问的原因。通常是由于访问权限或是表损坏,

最常见的数据库重大故障分析
根据我们的经验,除去硬件故障外,造成重大数据库故障都是因为日志满,重启动时异常删除日志导致,

而日志满主要是一下几个原因造成:

1)数据库配置不合理

主要是内存、锁的配置不合理。

2)存在不完整的事务或是进程

出现这种情况是由于不完整的事务引起的。引起不完整的事务主要有两个方面的原因:第一是网络质量不佳,如果在客户端向ASE服务端进行事务时,如果网络突然中断,会导致事务的不完整。第二是应用程序存在问题,而在这种情况下,重新启动数据库服务后,数据库的恢复可能是非常缓慢的,主要是看日志的大小和事务的类型,(有时用户为了快速启动,通常会异常清除数据库的日志,就有可能会造成数据库表损坏)所以,在这种情况下千万不要急于重新启动数据库服务。

如果存在不完整的事务,在该事务之后的所有事务都不能被清除,导致数据库日志满。

不完整的事务可以从master..syslogshold表中发现,如果其中starttime(最早的事务开始时间)距离当前时间很长,比如一天,一月,则该事务应该是一个不完整的事务,可以将该事务对应的数据库进程kill。

3)出现过大的事务
这类问题完全是应用或是人为造成的问题。例如一次删除一个5000万条记录的表,导致日志满。这种情况下重启动服务,数据库的恢复也将是十分缓慢的,防止出现这类问题,是将大事务转换成许多小事务来执行。在事务之间来删除数据库的日志。例如手工删除日志或是将数据库设置成自动清日志。

数据库的性能优化
查看数据库的配置,看能否有不合理的数据库配置。

查看方法参考上一节查看数据库的配置。

更新数据库的统计信息(页锁)

update statistics tablename

对行锁表回收空间(表锁)

reorg rebuild  tablename

做空间回收时需要在系统维护时做

监测数据库的运行,查看是否阻塞缩

sp_lock

sp_who

select spid,bloced from master..sysprocesses where blocked>0

对数据库系统运行进行监测,发现可能引起性能差的因素

在系统运行忙或是性能不佳时运行

sp_sysmon “00:10:00”


分析的一些常用工具:

在应用开发或是执行之前,对可能引起问题的语句检查命令:

set showplan on
set noexec on

这样,可以看到该语句的执行过程,而该语句并不执行。


set showplan off

set noexec off

off 以上的选项

set statistics io on /off
set statistics time on/off

在语句执行时可以看到I/O的实际情况(包括物理I/O,逻辑I/O)

以上语句基于session

在运行过程中查找可能有问题的语句的命令:

运行几次 sp_lock
查找其中对表意向锁 (sh_intent,ex_intent)较长时间的进程,记录下spid

( 比如,update 一张100万条记录中的一条,如果表不使用索引,最终会有一个Ex_row锁,但在表扫描期间,一直会有一个

Ex_intent意向锁)

通过spid,可以看到执行命令和执行过程:

dbcc traceon(3604)

go

dbcc sqltext(spid)

go

sp_showplan spid,null,null,null

go

查找阻塞进程:
select spid,blocked from master..sysprocesses where blocked>0

其中blocked 对应阻塞别人的进行,spid对应被阻塞经常,

查看blocked对应进程执行的命令和执行过程

方法同上

查找最耗资源的进程:下面有两个存储过程,可以分析在数据库繁忙时最消耗cpu和i/o资源的数据库进程,并显示该进程所执行的语句以及执行的过程,根据执行过程来判断问题的原因。

if exists( select name from  sysobjects where  type='P' and name='proc_who_do_io')
 drop proc  proc_who_do_io

go

/* print  top n (of  physical_io usages)  applications 's  execute plan and sql */
/* example useage : proc_who_do_io  */ 

create proc  proc_who_do_io

@inter_time    char(8)='00:00:05',

@topn  int=3

as

declare  @spid  int

select @spid=11

dbcc traceon(3604)

select  spid,cmd,physical_io,hostname,program_name into #t1 from  master..sysprocesses order

by spid

waitfor delay @inter_time

select spid,cmd,physical_io ,hostname,program_name into #t2 from  master..sysprocesses order

by spid

select  #t1.spid,#t1.cmd,#t1.program_name,#t1.physical_io as phy_io,#t1.hostname,#t2.physical_io - #t1.physical_io as
phy_io_add  into #t3 from #t1,#t2

where  #t1.spid=#t2.spid  and abs(#t2.physical_io - #t1.physical_io) >2 order by  #t2.physical_io - #t1.physical_io  desc

select *  from #t3

select  *  into #t4  from #t3 where 1=2

set rowcount 1

while  @topn >0

begin

 insert  #t4 select * from #t3

 delete #t3

 select @spid=spid from  #t4

 select "execute plan :"+  str(@spid)

 exec sp_showplan @spid,null,null,null

 dbcc sqltext(@spid)

 delete #t4

 select @topn = @topn-1

end

set rowcount 0

go

 

if exists( select name from  sysobjects where  type='P' and name='proc_who_use_cpu')
 drop proc  proc_who_use_cpu

go

/* print  top n (of  cpu usages)  applications 's  execute plan and sql */
/* example useage : proc_who_use_cpu  */ 

create proc  proc_who_use_cpu

@inter_time    char(8)='00:00:05',

@topn  int=3

as

declare  @spid  int

select @spid=11

dbcc traceon(3604)

select  spid,cmd,cpu,hostname,program_name into #t1 from  master..sysprocesses order

by spid

waitfor delay @inter_time

select spid,cmd,cpu ,hostname,program_name into #t2 from  master..sysprocesses order

by spid

select  #t1.spid,#t1.cmd,#t1.program_name,#t1.cpu ,#t1.hostname,#t2.cpu - #t1.cpu as
cpu_add  into #t3 from #t1,#t2

where  #t1.spid=#t2.spid  and abs(#t2.cpu - #t1.cpu) >2 order by  #t2.cpu - #t1.cpu  desc

select *  from #t3

select  *  into #t4  from #t3 where 1=2

set rowcount 1

while  @topn >0

begin

 insert  #t4 select * from #t3

 delete #t3

 select @spid=spid from  #t4

 select "execute plan :"+  str(@spid)

 exec sp_showplan @spid,null,null,null

 dbcc sqltext(@spid)

 delete #t4

 select @topn = @topn-1

end

set rowcount 0

go

数据库版本在 ASE12.5.0.3以上, 寻找索引使用情况
select s.SPID,s.CpuTime,t.LineNumber,t.SQLText

from monProcessStatement s,monProcessSQLText t

where s.SPID=t.SPID

order by s.CpuTime,s.SPID,t.LineNumber desc

posted @ 2008-07-05 17:19 存鹰之心于高远,取鹰之志而凌云,习鹰之性以涉险,融鹰之神在山巅. 阅读(5131) | 评论 (0)编辑 收藏

print         'sp_hotobjects'  
  SETUSER     'dbo'  
  go  
  use   sybsystemprocs  
  go  
   
  drop   proc   sp_hotobjects  
  go    
   
  create   procedure   sp_hotobjects  
  @interval   char(12)   =   "",             /*   time   interval   string   */  
  @interval_sample   char(12)   =   "00:05:00"               /*   sample   interval   every   5   minutes   by   default   */  
  as  
   
  declare   @TmpTime   datetime       /*   temporary   datetime   variable   */  
  declare   @Seconds   int                 /*   Interval   converted   to   seconds   */  
  declare   @Endtime   datetime  
   
   
  create   table   #hotobjects_totals  
  (dbname   char(30)   not   null,  
  objname   char(30)   not   null,  
  lockcount   int   null,  
  locktable   int   null,      
  lockshared   int   null,  
  lockexclusive   int   null,  
  lockrow   int   null,  
  lockpage   int   null,  
  lockblk   int   null  
  )  
   
  create   table   #hotobjects  
  (dbname   char(30)   not   null,  
  objname   char(30)   not   null,  
  lockcount   int   null,  
  locktype   int   not   null,  
  primary   key(dbname,   objname,   locktype))  
   
   
  /*   loop   for   the   interval   specified   */  
  select   @TmpTime   =   convert(datetime,   @interval)  
  select   @Seconds   =   datepart(hour,@TmpTime)*3600+datepart(minute,@TmpTime)*60+datepart(second,@TmpTime)  
  select   @Endtime   =   dateadd(second,   @Seconds,   getdate())  
   
  /*   create   a   holding   table   */  
  select   dbname,   objname   into   #hotobjects_holding    
  from   #hotobjects   where   1=2  
   
   
   
   
  while   (getdate()   <   @Endtime)  
  begin  
   
  /*   populate   the   initial   records   */  
  delete   from   #hotobjects  
  insert   into   #hotobjects(dbname,   objname,   lockcount,   locktype)  
  select   distinct   db_name(dbid),   object_name(id,dbid),   count(type),   type   from   master..syslocks    
  where   object_name(id,dbid)   not   like   "#%"       --   and   object_name(id,dbid)   not   like   "sys%"    
  and   object_name(id,dbid)   not   like   "hot%"     group   by   type    
   
  waitfor   delay   @interval_sample  
   
  /*   add   a   record   into   #hotobjects_totals   if   it   does   not   exist   */  
  if   not   exists(select   1   from   #hotobjects_totals   TOT,   #hotobjects   HOT  
  where   TOT.dbname   =   HOT.dbname   and   TOT.objname   =   HOT.objname)    
  /*   add   this   new   lock   record   */  
  begin  
   
        /*   populate   a   holding   table   */  
  delete   from   #hotobjects_holding  
  insert   into   #hotobjects_holding  
  select   distinct   dbname,   objname   from   #hotobjects  
   
        /*   now   delete   from   the   holding   table   all   records   we   have   done   before   */  
  delete   from   #hotobjects_holding  
  from   #hotobjects_holding   HOLD,   #hotobjects_totals   TOT  
  where   HOLD.dbname   =   TOT.dbname   and   HOLD.objname   =   TOT.objname  
  /*   what   is   left   is   the   new   records.....add   these   into   the   totals   table   */  
   
  insert   into   #hotobjects_totals(dbname,   objname,     lockcount,   locktable,   lockshared,      
                                                                                                                                                  lockexclusive,   lockrow,   lockpage,   lockblk)  
                          select   distinct   HOLD.dbname,   HOLD.objname,   0,   0,0,0,0,0,0    
  from   #hotobjects_holding   HOLD  
   
  end  
   
   
  /*   from   here   on   we   will   update   this   record     */  
   
  update   #hotobjects_totals  
  set   lockcount   =   TOT.lockcount   +   HOT.lockcount  
  from   #hotobjects_totals   TOT,   #hotobjects   HOT  
  where   TOT.dbname   =   HOT.dbname   and   TOT.objname   =   HOT.objname  
   
  update   #hotobjects_totals  
  set   locktable   =   locktable   +   HOT.lockcount  
  from   #hotobjects_totals   TOT,   #hotobjects   HOT  
  where   TOT.dbname   =   HOT.dbname   and   TOT.objname   =   HOT.objname  
  and   HOT.locktype   in   (1,2)  
   
  update   #hotobjects_totals  
  set   lockshared   =   lockshared   +   HOT.lockcount  
  from   #hotobjects_totals   TOT,   #hotobjects   HOT  
  where   TOT.dbname   =   HOT.dbname   and   TOT.objname   =   HOT.objname  
  and   HOT.locktype   in   (2,6,9)  
   
  update   #hotobjects_totals  
  set   lockexclusive   =   lockexclusive   +   HOT.lockcount  
  from   #hotobjects_totals   TOT,   #hotobjects   HOT  
  where   TOT.dbname   =   HOT.dbname   and   TOT.objname   =   HOT.objname  
  and   HOT.locktype   in   (1,5,8)  
   
  update   #hotobjects_totals  
  set   lockrow   =   lockrow   +   HOT.lockcount  
  from   #hotobjects_totals   TOT,   #hotobjects   HOT  
  where   TOT.dbname   =   HOT.dbname   and   TOT.objname   =   HOT.objname  
  and   HOT.locktype   in   (8,9,10)  
   
  update   #hotobjects_totals  
  set   lockpage   =   lockpage   +   HOT.lockcount  
  from   #hotobjects_totals   TOT,   #hotobjects   HOT  
  where   TOT.dbname   =   HOT.dbname   and   TOT.objname   =   HOT.objname  
  and   HOT.locktype   in   (5,6,7)  
   
  update   #hotobjects_totals  
  set   lockblk   =   lockblk   +   HOT.lockcount  
  from   #hotobjects_totals   TOT,   #hotobjects   HOT  
  where   TOT.dbname   =   HOT.dbname   and   TOT.objname   =   HOT.objname  
  and   HOT.locktype   >   255   and   HOT.locktype   <   269  
   
   
  end  
   
  select   "In   "   +   rtrim(dbname)   +   "the   table   "   +   rtrim(objname)   +   "   had   "   +    
  case    
  when   locktable   >   1   then   "table   level,   "  
  when   lockshared   >   1   then   "shared,   "    
  when   lockexclusive   >   1   then   "exclusive,   "  
  when   lockrow   >   1   then   "row,   "  
  when   lockpage   >   1   then   "page,   "  
  when   lockblk   >   1   then   "blocking   "  
  end  
  +   "   locks"  
    from   #hotobjects_totals    
  return   (0)  
  go  
   
  grant   exec   on   sp_hotobjects   to   public  
  go   
    
   
posted @ 2008-07-05 16:29 存鹰之心于高远,取鹰之志而凌云,习鹰之性以涉险,融鹰之神在山巅. 阅读(435) | 评论 (0)编辑 收藏

死锁的发生对系统的性能和吞吐量都有重要影响,经检测发现,管理信息系统的死锁主要是因为两个或多个线程(登录)抢占同一表数据资源。引起长时间抢占同一资源不是因为我们需要处理的事务太复杂,时间太长,而往往是因为我们在前端应用程序对数据库作操作时忘了提交。本文介绍一种处理解决这种死锁的方法。

Sybase封锁原理

数据共享与数据一致性是一对不可调和的矛盾,为了达到数据共享与数据一致,必须进行并发控制。并发控制的任务就是为了避免共享冲突而引起的数据不一致。Sybase SQL Server并发控制的方法是加锁机制(LOCKING).

锁的类型

可申请的锁
已有的锁
S U X
S ×
U × ×
X × × ×

Sybase SQL Server有三种封锁类型:排它锁(exclusive lock,简称X锁);共享锁(share lock,简称S锁);更新锁(update lock,简称U锁)。这三种锁的相容矩阵表如下:

×:表示不兼容。∨:表示兼容。

Sybase SQL Server是自动决定加锁类型的。一般来说,读(SELECT)操作使用S锁,写(UPDATE,INSERT和delete)操作使用X锁。U锁是建立在页级上的,它在一个更新操作开始时获得,当要修改这些页时,U锁会升级为X锁。

锁的力度

SQL Server有两级锁:页锁和表锁。通常页锁比表锁的限制更少(或更小)。页锁对本页的所有行进行锁定,而表锁则锁定整个表。为了减小用户间的数据争用和改进并发性,SQL Server试图尽可能地使用页锁。

当SQL Server决定一个语句将访问整个表或表的大多数页时,它用表锁来提供更有效的锁定。锁定策略直接受查询方案约束,如果update或delete语句没有可用的索引,它就执行表扫描或请求一个表锁定。如果update或delete语句使用了索引,它就通过请求页锁来开始,如果影响到大多数行,它就要请求表锁。一旦一个语句积累的页锁超过锁提升阈值,SQL Server就设法给该对象分配一个表锁。如果成功了,页锁就不再必要了,因此被释放。表锁也在页层提供避免锁冲突的方法。对于有些命令SQL Server自动使用表锁。

锁的状态

SQL SERVER加锁有三种状态:

1)意向锁(intend)—是一种表级锁,它表示在一个数据页上获得一个S或X锁的意向。意向锁可以防止其他事务在该数据页的表上获得排它锁。

2)阻塞(blocking,简记blk)—它表明目前加锁进程的状态,带有blk后缀的锁说明该进程目前正阻塞另一个需要获得锁的进程,只有这一进程完成,其他进程才可以进行。

3)需求锁(demand)—表示此时该进程企图得到一个排它锁。它可以防止在这一表或页上加过多的S锁,她表示某一事务是下一个去锁定该表和该页的事务。

需求锁是一个内部过程,因此用sp_lock是无法看见的。

死锁DEADLOCK

简单地说,有两个用户进程,每个进程都在一个单独的页或表上有一个锁,而且每个进程都想在对方进程的页或表上请求不相容锁时就会发生“死锁”。在这种情况下,第一个进程在等待另一进程释放锁,但另一进程要等到第一个进程的对象释放时才会释放自己的锁。

SQL Server检查是否死锁,并终止事务中CPU时间积累最小的用户(即最后进入的用户)。SQL Server回滚该用户的事务,并用消息号1205通知有此死锁行为的应用程序,然后允许其他用户进程继续进行。

在多用户情形下,每个用户的应用程序都应检查每个修改数据的事务是否有1205号消息,以此确定是否有可能死锁。消息号1025表示该用户的事务因死锁而终止并被回滚。应用程序必须重新开始这个事务处理。

查找死锁原因

既然管理信息系统长时间死锁的原因是由于我们提交或者是提交不当,那么我们就可以通过修改程序防止出现死锁。定位死锁出错处主要经过以下三步:

1)在死锁出现时,用SP_WHO,SP_LOCK获得进程与锁的活动情况。
2)结合库表sysobjects和相应的操作员信息表查出被锁的库表与锁住别人的操作员。
3)根据锁定的库表与操作员的岗位,可以估计出程序大约出错处。询问操作员在死锁时执行的具体操作即可完全定位出错处。最后查找程序并修改之。

用sp_who获取关于被阻碍进程的信息

系统过程sp_who给出系统进程的报告。如果用户的命令正被另一进程保持的锁阻碍,则:
◆status列显示“lock sleep”。
◆blk列显示保持该锁或这些锁的进程标识,即被谁锁定了。
◆loginame列显示登录操作员。结合相应的操作员信息表,便可知道操作员是谁。
Fid spid status loginame origname blk dbname cmd
0 1 lock sleep lm lm 18 QJYD SELECT
0 2 sleeping NULL NULL 0 master NETWORK HANDLER
0 3 sleeping NULL NULL 0 master NETWORK HANDLER
……

用sp_lock浏览锁
要得到关于当前SQL Server上保持的锁的报告,可用系统过程sp_lock [spid1[,spid2]],spid1,spid2是表master.dbo.sysprocesses中的sql server进程id号,用sp_who可以得到锁定与被锁定的spid号:

◆locktype列显示加锁的类型和封锁的粒度,有些锁的后缀还带有blk表明锁的状态。前缀表明锁的类型:Sh—共享锁,Ex—排它锁或更新锁,中间表明锁死在表上(”table”或’intent’)还是在页上(page). 后缀“blk”表明该进程正在障碍另一个需要请求锁的进程。一旦正在障碍的进程一结束,其他进程就向前移动。“demand”后缀表明当前共享锁一释放, 该进程就申请互斥锁。

◆table_id列显示表的id号,结合sysobjects即可查出被封锁的表名。

执行该进程后屏幕显示

Fid Spid locktype table_id page row dbname Class context
0 1 Sh_intent 678293476 0 0 QJYD Non Cursor LockFam dur
0 1 Sh_page 678293476 31764 0 QJYD Non Cursor Lock
0 18 Ex_intent 9767092 0 0 QJYD Non Cursor LockFam dur
……

定位出错处

根据sp_who与sp_lock命令的结果,结合sysobjects和相应的操作员信息表。得到操作员及其在死锁时所操作的库表,便大约可以知道应用程序的出错处,再询问操作员在死锁时执行什么操作以进一步认证。最后查找程序并修正之。


附:
锁的问题一般来讲是这样的  
   
  首先判断是否存在锁竞争  
   
  1、基于系统管理员的经验找出热点表  
   
  2、通过以下方法判断:  
   
  sp_who  
  察看系统中是否有status为lock   sleep的用户。如果存在,再运行命令  
  sp_lock  
  察看系统中被锁住的表的table_id  
  继续运行命令  
  select   *   from   sysobjects   where   id   =   table_id  
  找到被锁住的表名。  
  如果发现sp_who中有多个用户被锁,那么,就可认为是锁造成了数据库系统的性能底下。  
   
  调优方法1:修改对应表的加锁模式由allpages为行级锁,如:  
  alter   table   XXXX   lock   datarows  
   
  调优方法2:改大环境参数number   of   locks,如:  
  sp_configure   “number   of   locks”,   50000  
  注意,在这种情况下需要保持total   memory与number   of   locks的匹配性。  
   
  调优方法3:配置锁提升阈值,可修改的配置包括两项内容:  
  Page(HWM)、Row(HWM),命令如下:  
   
  Sp_configure   ‘page   lock   promotion   HWM’,   500  
  Sp_configure   ‘row   lock   promotion   HWM’,   500  

posted @ 2008-07-02 20:58 存鹰之心于高远,取鹰之志而凌云,习鹰之性以涉险,融鹰之神在山巅. 阅读(470) | 评论 (0)编辑 收藏

sp_who报告的状态值

状态          表示       kill命令的效果

recv sleep  等待网络读取  立即
send sleep  等待网络发送  立即
alarm sleep 等待警报      立即
lock sleep  等待获取锁    立即
sync sleep  等待同系列另一进程的同步消息   立即.系列中的其它进程也必须变为可注销的状态
sleeping    等待磁盘I/O或某种其它资源.或许表示正在运行的进程,但正在执行大量的磁盘I/O  少数休眠进程不能苏醒,因而需要服务器重启以将其清除.
runnable    在可运行进程队列中       立即
running     活跃地运行在一个服务器引擎中  立即
infected    服务器已检测到严重的错误情况;极其少见    不要使用kill命令.可能需要重启服务器以清除进程.
background  由Adaptive Server而不是用户进程运行的进程例如阈值过程   立即;使用kill时必须极其小心.建议在注销背景进程之前,仔细检查sysprocesses
log suspend   到达日志的最后机会阈值时挂起的进程   立即

参考<sybase数据库系统管理指南>


 ---★ 本文转摘自『onlyhot blog ※ blog.onlyhot.cn』http://www.onlyhot.cn/blog/?action=show&id=1178

posted @ 2008-07-02 11:48 存鹰之心于高远,取鹰之志而凌云,习鹰之性以涉险,融鹰之神在山巅. 阅读(2598) | 评论 (0)编辑 收藏

用一个实例讲解了Sybase数据库性能优化的具体过程,具体内容请参考下文:

共享锁

sp_getapplock 锁定应用程序资源

sp_releaseapplock 为应用程序资源解锁

SET LOCK_TIMEOUT 1800 锁超时期限设置

sp_configure 'deadlock checking period',5000 设置锁检测周期

sp_configure 'lock wait period',5000 设置锁的等待时间

sp_setrowlockpromote 设置基本个表的最大行锁升级数(锁数)

sp_setrowlockpromote 'TABLE',TREECODE,500,500,100

sp_setrowlockpromote 'TABLE',LCD05,500,500,100

[Lock Manager]

number of locks = 50000 #锁数

deadlock checking period = DEFAULT

freelock transfer block size = DEFAULT

max engine freelocks = DEFAULT

lock spinlock ratio = DEFAULT

lock hashtable size = DEFAULT

lock scheme = DEFAULT

lock wait period = DEFAULT

read committed with lock = DEFAULT

当很多事务同时访问同一个数据库时,会加剧锁资源争夺,严重时事务之间会发生死锁。可用sp_object_stats查明死锁位置。该过程报告资源争夺最激烈的10张表、一个数据库中资源争夺的表和单个表的争夺情况。语法为sp_object_stats interval [, top_n [, dbname [, objname [, rpt_option ]]]],查看锁争夺情况只需设置interval为“hh:mm:ss”。如果显示每种锁的争夺程度超过15%,应该改变加锁方式,比如表的全页锁改成数据页锁,数据页锁改成数据行锁等。

Parameter Name Default Memory Used Config Value Run Value

-------------- ------- ----------- ------------ ---------

allow remote access 1 0 1 1

print recovery information 0 0 0 0

recovery interval in minutes 5 0 5 5

tape retention in days 0 0 0 0

Parameter Name Default Memory Used Config Value Run Value

-------------- ------- ----------- ------------ ---------

global async prefetch limit 10 0 10 10

global cache partition number 1 0 1 1

memory alignment boundary 2048 0 2048 2048

number of index trips 0 0 0 0

number of oam trips 0 0 0 0

procedure cache percent 20 22426 20 20

total data cache size 0 89698 0 89698

total memory 47104 196608 98304 98304

Parameter Name Default Memory Used Config Value Run Value

-------------- ------- ----------- ------------ ---------

cis bulk insert batch size 0 0 0 0

cis connect timeout 0 0 0 0

cis cursor rows 50 0 50 50

cis packet size 512 0 512 512

cis rpc handling 0 0 0 0

enable cis 1 0 1 1

max cis remote connections 0 0 0 0

max cis remote servers 25 19 25 25

Parameter Name Default Memory Used Config Value Run Value

-------------- ------- ----------- ------------ ---------

dtm detach timeout period 0 0 0 0

dtm lock timeout period 300 0 300 300

enable xact coordination 1 0 1 1

number of dtx participants 500 149 500 500

strict dtm enforcement 0 0 0 0

txn to pss ratio 16 3692 16 16

xact coordination interval 60 0 60 60

Parameter Name Default Memory Used Config Value Run Value

-------------- ------- ----------- ------------ ---------

average cap size 200 0 200 200

caps per ccb 50 0 50 50

dump on conditions 0 0 0 0

maximum dump conditions 10 0 10 10

number of ccbs 0 0 0 0

Parameter Name Default Memory Used Config Value Run Value

-------------- ------- ----------- ------------ ---------

allow sql server async i/o 1 0 1 1

disable disk mirroring 0 0 0 0

disk i/o structures 256 31 256 256

number of devices 10 #5 10 10

page utilization percent 95 0 95 95

Parameter Name Default Memory Used Config Value Run Value

-------------- ------- ----------- ------------ ---------

event log computer name LocalSystem 0 LocalSystem LocalSystem

event logging 1 0 1 1

log audit logon failure 0 0 0 0

log audit logon success 0 0 0 0

Parameter Name Default Memory Used Config Value Run Value

-------------- ------- ----------- ------------ ---------

esp execution priority 8 0 8 8

esp execution stacksize 77824 0 77824 77824

esp unload dll 0 0 0 0

start mail session 0 0 0 0

xp_cmdshell context 1 0 1 1

Parameter Name Default Memory Used Config Value Run Value

-------------- ------- ----------- ------------ ---------

configuration file 0 0 0 /sybase/hgd

Parameter Name Default Memory Used Config Value Run Value

-------------- ------- ----------- ------------ ---------

enable java 0 0 0 0

size of global fixed heap 300 0 300 300

size of process object heap 300 0 300 300

size of shared class heap 3072 0 3072 3072

Parameter Name Default Memory Used Config Value Run Value

-------------- ------- ----------- ------------ ---------

default character set id 1 0 1 1

default language id 0 0 0 0

default sortorder id 50 0 50 50

disable character set conversi 0 0 0 0

enable unicode conversions 0 0 1 1

number of languages in cache 3 4 3 3

size of unilib cache 0 140 0 0

Parameter Name Default Memory Used Config Value Run Value

-------------- ------- ----------- ------------ ---------

deadlock checking period 500 0 500 500

freelock transfer block size 30 0 30 30

lock address spinlock ratio 100 0 100 100

lock hashtable size 2048 48 2048 2048

lock scheme allpages 0 allpages allpages

lock spinlock ratio 85 0 85 85

lock table spinlock ratio 20 0 20 20

lock wait period 2147483647 0 2147483647 2147483647

max engine freelocks 10 0 10 10

number of locks 5000 2344 10000 10000

print deadlock information 0 0 1 1

read committed with lock 0 0 0 0

Parameter Name Default Memory Used Config Value Run Value

-------------- ------- ----------- ------------ ---------

additional network memory 0 0 0 0

allow resource limits 0 0 0 0

audit queue size 100 42 100 100

average cap size 200 0 200 200

caps per ccb 50 0 50 50

deadlock pipe max messages 0 0 0 0

default network packet size 512 #505 512 512

disk i/o structures 256 31 256 256

enable rep agent threads 0 0 0 0

errorlog pipe max messages 0 0 0 0

event buffers per engine 100 #11 100 100

executable codesize + overhead 0 20261 0 20261

lock hashtable size 2048 48 2048 2048

lock spinlock ratio 85 0 85 85

max cis remote servers 25 19 25 25

max number network listeners 5 868 5 5

max online engines 1 216 1 1

max roles enabled per user 20 #22 20 20

memory per worker process 1024 0 1024 1024

number of alarms 40 3 40 40

number of aux scan descriptors 200 #258 200 200

number of ccbs 0 0 0 0

number of devices 10 #5 10 10

number of languages in cache 3 4 3 3

number of large i/o buffers 6 97 6 6

number of locks 5000 2344 10000 10000

number of mailboxes 30 1 30 30

number of messages 64 3 64 64

number of open databases 12 1239 12 12

number of open indexes 500 512 500 500

number of open objects 500 561 500 500

number of remote connections 20 86 50 50

number of remote logins 20 23 20 20

number of remote sites 10 1729 10 10

number of user connections 25 43141 250 250

number of worker processes 0 0 0 0

partition groups 1024 904 1024 1024

permission cache entries 15 #227 15 15

plan text pipe max messages 0 0 0 0

procedure cache percent 20 22426 20 20

process wait events 0 0 0 0

remote server pre-read packets 3 #83 3 3

size of global fixed heap 300 0 300 300

size of process object heap 300 0 300 300

size of shared class heap 3072 0 3072 3072

size of unilib cache 0 140 0 0

sql text pipe max messages 0 0 0 0

stack guard size 4096 #1108 4096 4096

stack size 86016 #23269 86016 86016

statement pipe max messages 0 0 0 0

total data cache size 0 89698 0 89698

total memory 47104 196608 98304 98304

txn to pss ratio 16 3692 16 16

wait event timing 0 0 0 0

Parameter Name Default Memory Used Config Value Run Value

-------------- ------- ----------- ------------ ---------

number of open databases 12 1239 12 12

number of open indexes 500 512 500 500

number of open objects 500 561 500 500

open index hash spinlock ratio 100 0 100 100

open index spinlock ratio 100 0 100 100

open object spinlock ratio 100 0 100 100

Parameter Name Default Memory Used Config Value Run Value

-------------- ------- ----------- ------------ ---------

Q diagnostics active 0 0 0 0

SQL batch capture 0 0 0 0

deadlock pipe active 0 0 0 0

deadlock pipe max messages 0 0 0 0

errorlog pipe active 0 0 0 0

errorlog pipe max messages 0 0 0 0

object lockwait timing 0 0 0 0

per object statistics active 0 0 0 0

plan text pipe active 0 0 0 0

plan text pipe max messages 0 0 0 0

process wait events 0 0 0 0

sql text pipe active 0 0 0 0

sql text pipe max messages 0 0 0 0

statement pipe active 0 0 0 0

statement pipe max messages 0 0 0 0

statement statistics active 0 0 0 0

wait event timing 0 0 0 0

Parameter Name Default Memory Used Config Value Run Value

-------------- ------- ----------- ------------ ---------

additional network memory 0 0 0 0

allow remote access 1 0 1 1

allow sendmsg 0 0 0 0

default network packet size 512 #505 512 512

max network packet size 512 0 512 512

max number network listeners 5 868 5 5

number of remote connections 20 86 50 50

number of remote logins 20 23 20 20

number of remote sites 10 1729 10 10

remote server pre-read packets 3 #83 3 3

syb_sendmsg port number 0 0 0 0

tcp no delay 0 0 0 0

Parameter Name Default Memory Used Config Value Run Value

-------------- ------- ----------- ------------ ---------

max async i/os per engine 2147483647 0 2147483647 2147483647

max async i/os per server 2147483647 0 2147483647 2147483647

o/s file descriptors 0 0 0 1024

tcp no delay 0 0 0 0

Parameter Name Default Memory Used Config Value Run Value

-------------- ------- ----------- ------------ ---------

max parallel degree 1 0 1 1

max scan parallel degree 1 0 1 1

memory per worker process 1024 0 1024 1024

number of worker processes 0 0 0 0

Parameter Name Default Memory Used Config Value Run Value

-------------- ------- ----------- ------------ ---------

additional network memory 0 0 0 0

lock shared memory 0 0 0 0

max SQL text monitored 0 7 0 0

shared memory starting address 0 0 0 0

total memory 47104 196608 98304 98304

Parameter Name Default Memory Used Config Value Run Value

-------------- ------- ----------- ------------ ---------

max online engines 1 216 1 1

min online engines 1 0 1 1

Parameter Name Default Memory Used Config Value Run Value

-------------- ------- ----------- ------------ ---------

enable rep agent threads 0 0 0 0

Parameter Name Default Memory Used Config Value Run Value

-------------- ------- ----------- ------------ ---------

SQL Perfmon Integration 1 0 1 1

abstract plan cache 0 0 0 0

abstract plan dump 0 0 0 0

abstract plan load 0 0 0 0

abstract plan replace 0 0 0 0

allow backward scans 1 0 1 1

allow nested triggers 1 0 1 1

allow resource limits 0 0 0 0

allow updates to system tables 0 0 1 1

audit queue size 100 42 100 100

cpu accounting flush interval 200 0 200 200

cpu grace time 500 0 500 500

deadlock retries 5 0 5 5

default database size 2 0 2 2

default exp_row_size percent 5 0 5 5

default fill factor percent 0 0 0 0

enable DTM 0 0 0 0

enable HA 0 0 0 0

enable housekeeper GC 1 0 1 1

enable sort-merge join and JTC 0 0 0 0

event buffers per engine 100 #11 100 100

housekeeper free write percent 1 0 1 1

i/o accounting flush interval 1000 0 1000 1000

i/o polling process count 10 0 10 10

identity burning set factor 5000 0 5000 5000

identity grab size 1 0 1 1

license information 25 0 25 25

number of alarms 40 3 40 40

number of aux scan descriptors 200 #258 200 200

number of large i/o buffers 6 97 6 6

number of mailboxes 30 1 30 30

number of messages 64 3 64 64

number of open databases 12 1239 12 12

number of open indexes 500 512 500 500

number of open objects 500 561 500 500

number of pre-allocated extent 2 0 2 2

number of sort buffers 500 0 500 500

page lock promotion HWM 200 0 200 200

page lock promotion LWM 200 0 200 200

page lock promotion PCT 100 0 100 100

partition groups 1024 904 1024 1024

partition spinlock ratio 10 0 10 10

print deadlock information 0 0 1 1

row lock promotion HWM 200 0 200 200

row lock promotion LWM 200 0 200 200

row lock promotion PCT 100 0 100 100

runnable process search count 2000 0 2000 2000

size of auto identity column 10 0 10 10

sql server clock tick length 100000 0 100000 100000

text prefetch size 16 0 16 16

time slice 100 0 100 100

upgrade version 1100 0 12000 12000

Parameter Name Default Memory Used Config Value Run Value

-------------- ------- ----------- ------------ ---------

allow procedure grouping 1 0 1 1

auditing 0 0 0 0

check password for digit 0 0 0 0

curread change w/ open cursors 1 0 1 1

current audit table 1 0 1 1

max roles enabled per user 20 #22 20 20

maximum failed logins 0 0 0 0

minimum password length 6 0 6 6

msg confidentiality reqd 0 0 0 0

msg integrity reqd 0 0 0 0

secure default login guest 0 guest guest

select on syscomments.text 1 0 1 1

suspend audit when device full 1 0 1 1

unified login required 0 0 0 0

use security services 0 0 0 0

Parameter Name Default Memory Used Config Value Run Value

-------------- ------- ----------- ------------ ---------

default network packet size 512 #505 512 512

number of pre-allocated extent 2 0 2 2

number of user connections 25 43141 250 250

permission cache entries 15 #227 15 15

stack guard size 4096 #1108 4096 4096

stack size 86016 #23269 86016 86016

systemwide password expiration 0 0 0 0

user log cache size 2048 0 2048 2048

user log cache spinlock ratio 20 0 20 20

posted @ 2008-07-01 16:48 存鹰之心于高远,取鹰之志而凌云,习鹰之性以涉险,融鹰之神在山巅. 阅读(753) | 评论 (0)编辑 收藏

注:在下面做的介绍都是以Weblogic8.1为例的,其它版本的Weblogic可能会有些许不同。
1) 设置JAVA参数;
a) 编辑Weblogic Server启动脚本文件;
l   BEA_HOME\user_projects\domains\domain-name\startWebLogic.cmd(startWebLogic.sh on Unix)
l BEA_HOME\user_projects\domains\domain-name\startManagedWebLogic.cmd(startManagedWebLogic.sh on Unix)
b) 编辑set JAVA_OPTIONS命令,如:set JAVA_OPTIONS=-Xms256m –Xmx256m;
c) 保存,重启即可。
注:在WebLogic中,为了获得更好的性能,BEA公司推荐最小Java堆等于最大Java堆。
2) 开发模式 vs. 产品模式;
开发模式和产品模式的一些参数的默认值不同,可能会对性能造成影响,下面是对性能有影响的参数列表:
参数         开发模式默认值         产品模式默认值
Execute Queue: Thread Count         15 threads         25 threads
JDBC Connection Pool: MaxCapacity         15 connnections         25 connections
通过启动管理控制台,在域(如:mydomain)> 配置 > 常规选择产品模式。
3) 尽量开启本地I/O;
通过启动管理控制台,在域(如:mydomain)> 服务器 > server实例(如:myserver)> 配置 > 调整选择启用本地I/O。
注:此值也可通过手动的修改config.xml配置文件。
4) 调优执行队列线程;
a) 修改默认执行线程数
在这里,执行队列的线程数表示执行队列能够同时执行的操作的数量。但此值不是设的越大越好,应该恰到好处的去设置它,太小了,执行队列中将会积累很多待处理的任务,太大了,则会消耗大量的系统资源从而影响整体的性能。在产品模式下默认为25个执行线程。
为了设置理想的执行队列的线程数,我们可以启动管理控制台,在域(如:mydomain)> 服务器 > server实例(如:myserver)> 监视 > 性能中监控最大负载时执行队列的吞吐量和队列中的等待请求数,据此确定理想的数值。
理想的默认执行线程数是由多方面的因素决定的,比如机器CPU性能、总体体系架构、I/O、操作系统的进程调度机制、JVM的线程调度机制。随着CPU个 数的增加,WebLogic可以近乎线性地提高线程数。线程数越多,花费在线程切换的时间也就越多;线程数越小,CPU可能无法得到充分的利用。为获取一 个理想的线程数,需要经过反复的测试。在测试中,可以以25*CPU个数为基准进行调整。当空闲线程较少,CPU利用率较低时,可以适当增加线程数的大小 (每五个递增)。对于PC Server和Windows 2000,则最好每个CPU小于50个线程,以CPU利用率为90%左右为最佳。
通过启动管理控制台,在域(如:mydomain)> 服务器 > server实例(如:myserver)> Execute Queue > weblogic.kernel.Defalt > 配置中修改线程计数。
b) 设定执行队列的溢出条件;
Weblogic Server提供给默认的执行队列或用户自定义的执行队列自定义溢出条件的功能,当满足此溢出条件时,服务器改变其状态为“警告”状态,并且额外的再分配一些线程去处理在队列中的请求,而达到降低队列长度的目的。
通过启动管理控制台,在域(如:mydomain)> 服务器 > server实例(如:myserver)> Execute Queue > weblogic.kernel.Defalt > 配置下面几项:
L 队列长度:此值表示执行队列中可容纳的最大请求数,默认值是65536,最后不要手动改变此值。
L 队列长度阈值百分比:此值表示溢出条件,在此服务器指出队列溢出之前可以达到的队列长度大小的百分比。
L 线程数增加:当检测到溢出条件时,将增加到执行队列中的线程数量。如果CPU和内存不是足够的高,尽量不要改变默认值“0”。因为Weblogic一旦增加后不会自动缩减,虽然最终可能确实起到了降低请求的作用,但在将来的运行中将影响程序的性能。
L   最大线程数:为了防止创建过多的线程数量,可以通过设定最大的线程数进行控制。
在实际的应用场景中,应根据具体情况适当的调整以上参数。
c) 设定执行队列监测行为
Weblogic Server能够自动监测到当一个执行线程变为“阻塞”。变为“阻塞”状态的执行线程将无法完成当前的工作,也无法再执行新请求。如果执行队列中的所有执 行线程都变为“阻塞”状态,Weblogic server可能改变状态为“警告”或“严重”状态。如果Weblogic server变为“严重”状态,可以通过Node Manager来自动关闭此服务器并重新启动它。具体请参考:Node Manager Capabilities文档。
通过启动管理控制台,在域(如:mydomain)> 服务器 > server实例(如:myserver)>配置 > 调整下可配置下面几项:
l   阻塞线程最长时间:在此服务器将线程诊断为阻塞线程之前,线程必须连续工作的时间长度(秒)。默认情况下,WebLogic Server 认为线程在连续工作 600 秒后成为阻塞线程。
l   阻塞线程计时器间隔:WebLogic Server 定期扫描线程以查看它们是否已经连续工作了 "阻塞线程最长时间" 字段中指定的时间长度的间隔时间(秒)。默认情况下,WebLogic Server 将此时间间隔设置为 600 秒。
5) 调优TCP连接缓存数;
WebLogic Server用Accept Backlog参数规定服务器向操作系统请求的队列大小,默认值为50。当系统重载负荷时,这个值可能过小,日志中报Connection Refused,导致有效连接请求遭到拒绝,此时可以提高Accept Backlog 25%直到连接拒绝错误消失。对于Portal类型的应用,默认值往往是不够的。Login Timeout和SSL Login Timeout参数表示普通连接和SSL连接的超时时间,如果客户连接被服务器中断或者SSL容量大,可以尝试增加该值。
通过启动管理控制台,在域(如:mydomain)> 服务器 > server实例(如:myserver)>配置 > 调整下可配置“接受预备连接”。
6) 改变Java编译器;
标准的Java编译器是javac,但编译JSP servlets速度太慢,为了提高编译速度,可以使用sj或jikes编译器取代javac编译器。下面说说更改Java编译器:
通过启动管理控制台,在域(如:mydomain)> 服务器 > server实例(如:myserver)>配置 > 常规下改变Java 编译器,默认为javac。输入完整路径,如:c:\visualcafe31\bin\sj.exe。然后打开高级选项,在预规划到类路径填写编译 Java 代码时为 Java 编译器类路径预规划的选项,如:BEA_HOME\jdk141_02\jre\lib\rt.jar。
7) 使用Webogic Server集群提高性能;
具体关于如何配置Weblogic集群,我就不细说了。详情可参考:Introduction to WebLogic Server Clustering。
8) Weblogic EJB调优
由于EJB2.0已经很少项目在用了,EJB3.0再成熟一点,我再补充这一部分吧!
9) JDBC应用调优
JDBC Connection Pool的调优受制于WebLogic Server线程数的设置和数据库进程数,游标的大小。通常我们在一个线程中使用一个连接,所以连接数并不是越多越好,为避免两边的资源消耗,建议设置连 接池的最大值等于或者略小于线程数。同时为了减少新建连接的开销,将最小值和最大值设为一致。
增加Statement Cache Size对于大量使用PreparedStatement对象的应用程序很有帮助,WebLogic能够为每一个连接缓存这些对象,此值默认为10。在保 证数据库游标大小足够的前提下,可以根据需要提高Statement Cache Size。比如当你设置连接数为25,Cache Size为10时,数据库可能需要打开25*10=250个游标。不幸的是,当遇到与PreparedStatement Cache有关的应用程序错误时,你需要将Cache Size设置为0。
尽管JDBC Connection Pool提供了很多高级参数,在开发模式下比较有用,但大部分在生产环境下不需调整。这里建议最好不要设置测试表, 同时Test Reserved Connections和Test Released Connections也无需勾上。 当然如果你的数据库不稳定,时断时续,你就可能需要上述的参数打开。
最后提一下驱动程序类型的选择,以Oracle为例,Oracle提供thin驱动和oci驱动,从性能上来讲,oci驱动强于thin驱动,特别是大数 据量的操作。但在简单的数据库操作中,性能相差不大,随着thin驱动的不断改进,这一弱势将得到弥补。而thin驱动的移植性明显强于oci驱动。所以 在通常情况下建议使用thin驱动。而最新驱动器由于WebLogic server/bin目录下的类包可能不是最新的,请以Oracle网站为准: http://www.oracle.com/technology ... tdocs/jdbc9201.html
10) JSP调优
l   设置jsp-param pageCheckSeconds=-1;
l   设置serlet-reload-check=-1或ServletReloadCheckSecs=-1;
l   设置jsp-param precompile=true,关闭JSP预编译选项。(转载)
posted @ 2008-06-30 20:55 存鹰之心于高远,取鹰之志而凌云,习鹰之性以涉险,融鹰之神在山巅. 阅读(371) | 评论 (0)编辑 收藏

ORACLE9i连接SYBASE的透明网关的配置

ORACLE实现异种数据库连接服务的技术叫做透明网关(Transparent Gateway)。
目前ORACLE利用透明网关可以实现和SQL SERVER、SYBASE、DB2等多种主流数据库的互联。

现在通过oracle访问sybase数据库,把配置oracle9i TRANSPARENT GATEWAY FOR SYBASE
的步骤写成文档,供需要的网友参考!
 
配置TRANSPARENT GATEWAY FOR SYBASE步骤

1.
oracle所在服务器上安装sybase client(或者在同一台server上安装oracle、sybase服务器)
确保能够访问sybase数据库

2.
安装TRANSPARENT GATEWAY FOR SYBASE选件,要用自定义安装。
正确选择sybase的安装目录

3.
选择一个sid字符串准备赋给sybase数据库。如:tg4sybs
设置SYBASE的dll路径到环境变量PATH(这一步很重要)

4.
修改初始化文件,默认的是:
ORACLE_HOME g4sybsadmininittg4sybs.ora
设置参数
HS_FDS_CONNECT_INFO
格式:HS_FDS_CONNECT_INFO= server_name. database_name[,INTERFACE= interface_file]
server_name. database_name是大小写敏感的。
INTERFACE可选

例子:如下
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
# This is a sample agent init file that contains the HS parameters that are
# needed for the Transparent Gateway for Sybase

#
# HS init parameters
#
HS_FDS_CONNECT_INFO=migration_serv.tax
HS_FDS_TRACE_LEVEL=OFF
HS_FDS_RECOVERY_ACCOUNT=RECOVER
HS_FDS_RECOVERY_PWD=RECOVER

#
# Environment variables required for Sybase
#
set SYBASE=d:sybase
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
上例中
server_name是migration_serv
database_name是tax

5.
配置oracle网络服务的listener,配置文件是:listener.ora
默认路径:ORACLE_HOME etworkadmin
加入如下

SID_LIST_LISTENER=
(SID_LIST=
(SID_DESC=
(SID_NAME= gateway_sid)
(ORACLE_HOME= oracle_home_directory)
(PROGRAM=tg4sybs)
)
)

gateway_sid就是3选择的sid字符串
oracle_home_directory是ORACLE_HOME
tg4sybs若是SYBASE是特定的。如果是其他数据库,会不同。
例子如下:
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
    (SID_DESC=
      (SID_NAME=tg4sybs)
      (ORACLE_HOME = D:oracleora92)
      (PROGRAM=tg4sybs)
    )
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

6.
停止监听

lsnrctl stop


重新启动监听程序

lsnrctl start

7.
配置oracle server的tnsnames.ora使其能够访问sybase
connect_descriptor=
(DESCRIPTION=
(ADDRESS=
(PROTOCOL=TCP)
(HOST= host_name)
(PORT= port_number)
)
(CONNECT_DATA=
(SID= gateway_sid))
(HS=OK))

connect_descriptor是连接串,任取,一般为sybs
host_name:oracle server的name
port_number:oracle监听端口
gateway_sid就是3选择的sid字符串

例子如下:
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
sybs=
   (DESCRIPTION=
       (ADDRESS_LIST =
       (ADDRESS=(PROTOCOL=TCP)(HOST= dw-server1)(PORT= 1521))
       )
   (CONNECT_DATA=
       (SID= tg4sybs)
   )
       (HS=OK)
   )

$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

8.建立database link

如:
CREATE  DATABASE LINK sybs  CONNECT TO sa
    IDENTIFIED BY prient 
    USING "SBYS" ;
   
即可访问sybase 数据库。


需要注意的是,sybase数据库的表名,字段名,如果是小写的,那么在oracle里访问的时候要加上双引号""

如:
SQL〉select "a" from
"b"@sybs;

 

posted @ 2008-06-29 21:40 存鹰之心于高远,取鹰之志而凌云,习鹰之性以涉险,融鹰之神在山巅. 阅读(242) | 评论 (0)编辑 收藏

一种理想的在关系数据库中存储树型结构数据的方法
2008-03-07 15:49
    在各种基于关系数据库的应用系统开发中,我们往往需要存储树型结构的数据,目前有很多流行的方法,如邻接列表模型(The Adjacency List Model),在此基础上也有很多人针对不同的需求做了相应的改进,但总是在某些方面存在的各种各样的缺陷。
    那么理想中的树型结构应具备哪些特点呢?数据存储冗余小、直观性强;方便返回整个树型结构数据;可以很轻松的返回某一子树(方便分层加载);快整获以某节 点的祖谱路径;插入、删除、移动节点效率高等等。带着这些需求我查找了很多资料,发现了一种理想的树型结构数据存储及操作算法,改进的前序遍历树模型 (The Nested Set Model)。

一、数据

    在本文中,举一个在线食品店树形图的例子。这个食品店通过类别、颜色和品种来组织食品。树形图如下:

二、邻接列表模型(The Adjacency List Model)

在这种模型下,上述数据在关系数据库的表结构数据通常如下图所示:

由于该模型比较简单,在此不再详细介绍其算法,下面列出它的一些不足:

    在大多数编程语言中,他运行很慢,效率很差。这主要是“递归”造成的。我们每次查询节点都要访问数据库。每次数据库查询都要花费一些时间,这让函数处理庞 大的树时会十分慢。造成这个函数不是太快的第二个原因可能是你使用的语言。不像Lisp这类语言,大多数语言不是针对递归函数设计的。对于每个节点造成这 个函数不是太快的第二个原因可能是你使用的语言。不像Lisp这类语言,大多数语言不是针对递归函数设计的。对于每个节点,函数都要调用他自己,产生新的 实例。这样,对于一个4层的树,你可能同时要运行4个函数副本。对于每个函数都要占用一块内存并且需要一定的时间初始化,这样处理大树时递归就很慢了。

三、改进的前序遍历树模型(The Nested Set Model)

原理:

    我们先把树按照水平方式摆开。从根节点开始(“Food”),然后他的左边写上1。然后按照树的顺序(从上到下)给“Fruit”的左边写上2。这样,你 沿着树的边界走啊走(这就是“遍历”),然后同时在每个节点的左边和右边写上数字。最后,我们回到了根节点“Food”在右边写上18。下面是标上了数字 的树,同时把遍历的顺序用箭头标出来了。

    我们称这些数字为左值和右值(如,“Food”的左值是1,右值是18)。正如你所见,这些数字按时了每个节点之间的关系。因为“Red”有3和6两个 值,所以,它是有拥有1-18值的“Food”节点的后续。同样的,我们可以推断所有左值大于2并且右值小于11的节点,都是有2-11的“Fruit” 节点的后续。这样,树的结构就通过左值和右值储存下来了。这种数遍整棵树算节点的方法叫做“改进前序遍历树”算法。

表结构设计:

常用的操作:

下面列出一些常用操作的SQL语句

返回完整的树(Retrieving a Full Tree)
SELECT node.name
  
FROM nested_category node, nested_category parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
   
AND parent.name = 'electronics'
ORDER BY node.lft

返回某结点的子树(Find the Immediate Subordinates of a Node)
SELECT V.*
  
FROM (SELECT node.name,
                (
COUNT(parent.name) - (AVG(sub_tree.depth) + 1)) depth
          
FROM nested_category node,
                nested_category parent,
                nested_category sub_parent,
                (
SELECT V.*
                  
FROM (SELECT node.name, (COUNT(parent.name) - 1) depth
                          
FROM nested_category node, nested_category parent
                         
WHERE node.lft BETWEEN parent.lft AND parent.rgt
                           
AND node.name = 'portable electronics'
                         
GROUP BY node.name) V,
                        nested_category T
                 
WHERE V.name = T.name
                 
ORDER BY T.lft) sub_tree
         
WHERE node.lft BETWEEN parent.lft AND parent.rgt
           
AND node.lft BETWEEN sub_parent.lft AND sub_parent.rgt
           
AND sub_parent.name = sub_tree.name
         
GROUP BY node.name) V,
        nested_category T
WHERE V.name = T.name
   
and V.depth <= 1
   
and V.depth > 0
ORDER BY T.Lft

返回某结点的祖谱路径(Retrieving a Single Path)
SELECT parent.name
  
FROM nested_category node, nested_category parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
   
AND node.name = 'flash'
ORDER BY node.lft

返回所有节点的深度(Finding the Depth of the Nodes)
SELECT V.*
  
FROM (SELECT node.name, (COUNT(parent.name) - 1) depth
          
FROM nested_category node, nested_category parent
         
WHERE node.lft BETWEEN parent.lft AND parent.rgt
         
GROUP BY node.name) V,
        nested_category T
WHERE V.name = T.name
ORDER BY T.Lft

返回子树的深度(Depth of a Sub-Tree)
SELECT V.*
  
FROM (SELECT node.name,
                (
COUNT(parent.name) - (AVG(sub_tree.depth) + 1)) depth
          
FROM nested_category node,
                nested_category parent,
                nested_category sub_parent,
                (
SELECT V.*
                  
FROM (SELECT node.name, (COUNT(parent.name) - 1) depth
                          
FROM nested_category node, nested_category parent
                         
WHERE node.lft BETWEEN parent.lft AND parent.rgt
                           
AND node.name = 'portable electronics'
                         
GROUP BY node.name) V,
                        nested_category T
                 
WHERE V.name = T.name
                 
ORDER BY T.lft) sub_tree
         
WHERE node.lft BETWEEN parent.lft AND parent.rgt
           
AND node.lft BETWEEN sub_parent.lft AND sub_parent.rgt
           
AND sub_parent.name = sub_tree.name
         
GROUP BY node.name) V,
        nested_category T
WHERE V.name = T.name
ORDER BY T.Lft

返回所有的叶子节点(Finding all the Leaf Nodes)
SELECT name FROM nested_category WHERE rgt = lft + 1

插入节点(Adding New Nodes)
LOCK TABLE nested_category WRITE;

SELECT @myRight := rgt FROM nested_category WHERE name = 'TELEVISIONS';

UPDATE nested_category SET rgt = rgt + 2 WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft + 2 WHERE lft > @myRight;

INSERT INTO nested_category
   (name, lft, rgt)
VALUES
   (
'GAME CONSOLES', @myRight + 1, @myRight + 2);

UNLOCK TABLES;

删除节点(Deleting Nodes)
LOCK TABLE nested_category WRITE;

SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1
  
FROM nested_category
WHERE name = 'GAME CONSOLES';

DELETE FROM nested_category WHERE lft BETWEEN @myLeft AND @myRight;

UPDATE nested_category SET rgt = rgt - @myWidth WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft - @myWidth WHERE lft > @myRight;

UNLOCK TABLES;
posted @ 2008-06-27 15:56 存鹰之心于高远,取鹰之志而凌云,习鹰之性以涉险,融鹰之神在山巅. 阅读(479) | 评论 (0)编辑 收藏

无论你要构建自己的论坛,在你的网站上发布消息还是书写自己的cms [1]程序,你都会遇到要在数据库中存储层次数据的情况。同时,除非你使用一种像XML [2]的数据库,否则关系数据库中的表都不是层次结构的,他们只是一个平坦的列表。所以你必须找到一种把层次数据库转化的方法。

存储树形结构是一个很常见的问题,他有好几种解决方案。主要有两种方法:邻接列表模型和改进前序遍历树算法

在本文中,我们将探讨这两种保存层次数据的方法。我将举一个在线食品店树形图的例子。这个食品店通过类别、颜色和品种来组织食品。树形图如下:

1105_tree

本文包含了一些代码的例子来演示如何保存和获取数据。我选择PHP [3]来写例子,因为我常用这个语言,而且很多人也都使用或者知道这个语言。你可以很方便地把它们翻译成你自己用的语言。

邻接列表模型(The Adjacency List Model)

我们要尝试的第一个——也是最优美的——方法称为“邻接列表模型”或称为“递归方法”。它是一个很优雅的方法因为你只需要一个简单的方法来在你的树中进行迭代。在我们的食品店中,邻接列表的表格如下:

1105_table1

如你所见,对每个节点保存一个“父”节点。我们可以看到“Pear [4]”是“Green”的一个子节点,而后者又是“Fruit”的子节点,如此类推。根节点,“Food”,则他的父节点没有值。为了简单,我只用了“title”值来标识每个节点。当然,在实际的数据库中,你要使用数字的ID。

显示树

现在我们已经把树放入数据库中了,得写一个显示函数了。这个函数将从根节点开始——没有父节点的节点——同时要显示这个节点所有的子节点。对于这些子节点,函数也要获取并显示这个子节点的子节点。然后,对于他们的子节点,函数还要再显示所有的子节点,然后依次类推。

也许你已经注意到了,这种函数的描述,有一种普遍的模式。我们可以简单地只写一个函数,用来获得特定节点的子节点。这个函数然后要对每个子节点调用自身来再次显示他们的子节点。这就是“递归”机制,因此称这种方法叫“递归方法”。

<?php
// $parent 是我们要查看的子节点的父节点
// $level 会随着我们深入树的结构而不断增加,
//        用来显示一个清晰的缩进格式
function display_children($parent, $level) {
// 获取$parent的全部子节点
$result = mysql_query('SELECT title FROM tree '.
'WHERE parent="'.$parent.'";');
// 显示每个节点
while ($row = mysql_fetch_array($result)) {
// 缩进并显示他的子节点的标题
echo str_repeat('  ',$level).$row['title']."\n";
// 再次调用这个函数来显着这个子节点的子节点
display_children($row['title'], $level+1);
}
}
?>

要实现整个树,我们只要调用函数时用一个空字符串作为$parent$level = 0: display_children('',0); 函数返回了我们的食品店的树状图如下:

Food
Fruit
Red
Cherry
Yellow
Banana
Meat
Beef
Pork

注意如果你只想看一个子树,你可以告诉函数从另一个节点开始。例如,要显示“Fruit”子树,你只要display_children('Fruit',0);


节点的路径

利用差不多的函数,我们也可以查询某个节点的路径如果你只知道这个节点的名字或者ID。例如,“Cherry”的路径是“Food”>“Fruit”>“Red”。要获得这个路径,我们的函数要获得这个路径,这个函数必须从最深的层次开始:“Cheery”。但后查找这个节点的父节点,并添加到路径中。在我们的例子中,这个父节点是“Red”。如果我们知道“Red”是“Cherry”的父节点。

<?php
// $node 是我们要查找路径的那个节点的名字
function get_path($node) {
// 查找这个节点的父节点
$result = mysql_query('SELECT parent FROM tree '.
'WHERE title="'.$node.'";');
$row = mysql_fetch_array($result);
// 在这个array [5] 中保存数组
$path = array();
// 如果 $node 不是根节点,那么继续
if ($row[’parent’]!=”) {
//  $node 的路径的最后一部分是$node父节点的名称
$path[] = $row[’parent’];
// 我们要添加这个节点的父节点的路径到现在这个路径
$path = array_merge(get_path($row[’parent’]), $path);
}
// 返回路径
return $path;
}
?>

这个函数现在返回了指定节点的路径。他把路径作为数组返回,这样我们可以使用print_r(get_path('Cherry')); 来显示,其结果是:

Array
(
[0] => Food
[1] => Fruit
[2] => Red
)

不足

正如我们所见,这确实是一个很好的方法。他很容易理解,同时代码也很简单。但是邻接列表模型的缺点在哪里呢?在大多数编程语言中,他运行很慢,效率很差。这主要是“递归”造成的。我们每次查询节点都要访问数据库。

每次数据库查询都要花费一些时间,这让函数处理庞大的树时会十分慢。

造成这个函数不是太快的第二个原因可能是你使用的语言。不像Lisp这类语言,大多数语言不是针对递归函数设计的。对于每个节点,函数都要调用他自己,产生新的实例。这样,对于一个4层的树,你可能同时要运行4个函数副本。对于每个函数都要占用一块内存并且需要一定的时间初始化,这样处理大树时递归就很慢了。

改进前序遍历树

现在,让我们看另一种存储树的方法。递归可能会很慢,所以我们就尽量不使用递归函数。我们也想尽量减少数据库查询的次数。最好是每次只需要查询一次。

我们先把树按照水平方式摆开。从根节点开始(“Food”),然后他的左边写上1。然后按照树的顺序(从上到下)给“Fruit”的左边写上2。这样,你沿着树的边界走啊走(这就是“遍历”),然后同时在每个节点的左边和右边写上数字。最后,我们回到了根节点“Food”在右边写上18。下面是标上了数字的树,同时把遍历的顺序用箭头标出来了。

1105_numbering

我们称这些数字为左值和右值(如,“Food”的左值是1,右值是18)。正如你所见,这些数字按时了每个节点之间的关系。因为“Red”有3和6两个值,所以,它是有拥有1-18值的“Food”节点的后续。同样的,我们可以推断所有左值大于2并且右值小于11的节点,都是有2-11的“Food”节点的后续。这样,树的结构就通过左值和右值储存下来了。这种数遍整棵树算节点的方法叫做“改进前序遍历树”算法。

在继续前,我们先看看我们的表格里的这些值:

1105_table2

注意单词“left”和“right”在SQL中有特殊的含义。因此,我们只能用“lft”和“rgt”来表示这两个列。(译注——其实Mysql中可以用“`”来表示,如“`left`”,MSSQL中可以用“[]”括出,如“[left]”,这样就不会和关键词冲突了。)同样注意这里我们已经不需要“parent”列了。我们只需要使用lft和rgt就可以存储树的结构。

获取树

如果你要通过左值和右值来显示这个树的话,你要首先标识出你要获取的那些节点。例如,如果你想获得“Fruit”子树,你要选择那些左值在2到11的节点。用SQL语句表达:

SELECT * FROM tree WHERE lft BETWEEN 2 AND 11;

这个会返回:

1105_table3

好吧,现在整个树都在一个查询中了。现在就要像前面的递归函数那样显示这个树,我们要加入一个ORDER BY子句在这个查询中。如果你从表中添加和删除行,你的表可能就顺序不对了,我们因此需要按照他们的左值来进行排序。

SELECT * FROM tree WHERE lft BETWEEN 2 AND 11 ORDER BY lft ASC;

就只剩下缩进的问题了。

要显示树状结构,子节点应该比他们的父节点稍微缩进一些。我们可以通过保存一个右值的一个栈。每次你从一个节点的子节点开始时,你把这个节点的右值添加到栈中。你也知道子节点的右值都比父节点的右值小,这样通过比较当前节点和栈中的前一个节点的右值,你可以判断你是不是在显示这个父节点的子节点。当你显示完这个节点,你就要把他的右值从栈中删除。要获得当前节点的层数,只要数一下栈中的元素。

<?php
function display_tree($root) {
// 获得$root节点的左边和右边的值
$result = mysql_query('SELECT lft, rgt FROM tree '.
'WHERE title="'.$root.'";');
$row = mysql_fetch_array($result);
// 以一个空的$right栈开始
$right = array();
// 现在,获得$root节点的所有后序
$result = mysql_query('SELECT title, lft, rgt FROM tree '.
'WHERE lft BETWEEN '.$row['lft'].' AND '.
$row['rgt'].' ORDER BY lft ASC;');
// 显示每一行
while ($row = mysql_fetch_array($result)) {
// 检查栈里面有没有元素
if (count($right)>0) {
// 检查我们是否需要从栈中删除一个节点
while ($right[count($right)-1]<$row['rgt']) {
array_pop($right);
}
}
// 显示缩进的节点标题
echo str_repeat('  ',count($right)).$row['title']."\n";
// 把这个节点添加到栈中
$right[] = $row['rgt'];
}
}
?>

如果运行这段代码,你可以获得和上一部分讨论的递归函数一样的结果。而这个函数可能会更快一点:他不采用递归而且只是用了两个查询

节点的路径

有了新的算法,我们还要另找一种新的方法来获得指定节点的路径。这样,我们就需要这个节点的祖先的一个列表。

由于新的表结构,这不需要花太多功夫。你可以看一下,例如,4-5的“Cherry”节点,你会发现祖先的左值都小于4,同时右值都大于5。这样,我们就可以使用下面这个查询:

SELECT title FROM tree WHERE lft < 4 AND rgt > 5 ORDER BY lft ASC;

注意,就像前面的查询一样,我们必须使用一个ORDER BY子句来对节点排序。这个查询将返回:

+-------+
| title |
+-------+
| Food  |
| Fruit |
| Red   |
+-------+

我们现在只要把各行连起来,就可以得到“Cherry”的路径了。

有多少个后续节点?How Many Descendants

如果你给我一个节点的左值和右值,我就可以告诉你他有多少个后续节点,只要利用一点点数学知识。

因为每个后续节点依次会对这个节点的右值增加2,所以后续节点的数量可以这样计算:

descendants = (right – left - 1) / 2

利用这个简单的公式,我可以立刻告诉你2-11的“Fruit”节点有4个后续节点,8-9的“Banana”节点只是1个子节点,而不是父节点。

自动化树遍历

现在你对这个表做一些事情,我们应该学习如何自动的建立表了。这是一个不错的练习,首先用一个小的树,我们也需要一个脚本来帮我们完成对节点的计数。

让我们先写一个脚本用来把一个邻接列表转换成前序遍历树表格。

<?php
function rebuild_tree($parent, $left) {
// 这个节点的右值是左值加1
$right = $left+1;
// 获得这个节点的所有子节点
$result = mysql_query('SELECT title FROM tree '.
'WHERE parent="'.$parent.'";');
while ($row = mysql_fetch_array($result)) {
// 对当前节点的每个子节点递归执行这个函数
// $right 是当前的右值,它会被rebuild_tree函数增加
$right = rebuild_tree($row['title'], $right);
}
// 我们得到了左值,同时现在我们已经处理这个节点我们知道右值的子节点
mysql_query('UPDATE tree SET lft='.$left.', rgt='.
$right.' WHERE title="'.$parent.'";');
// 返回该节点的右值+1
return $right+1;
}
?>

这是一个递归函数。你要从rebuild_tree('Food',1); 开始,这个函数就会获取所有的“Food”节点的子节点。

如果没有子节点,他就直接设置它的左值和右值。左值已经给出了,1,右值则是左值加1。如果有子节点,函数重复并且返回最后一个右值。这个右值用来作为“Food”的右值。

递归让这个函数有点复杂难于理解。然而,这个函数确实得到了同样的结果。他沿着树走,添加每一个他看见的节点。你运行了这个函数之后,你会发现左值和右值和预期的是一样的(一个快速检验的方法:根节点的右值应该是节点数量的两倍)。

添加一个节点

我们如何给这棵树添加一个节点?有两种方式:在表中保留“parent”列并且重新运行rebuild_tree()
函数——一个很简单但却不是很优雅的函数;或者你可以更新所有新节点右边的节点的左值和右值。

第一个想法比较简单。你使用邻接列表方法来更新,同时使用改进前序遍历树来查询。如果你想添加一个新的节点,你只需要把节点插入表格,并且设置好parent列。然后,你只需要重新运行rebuild_tree() 函数。这做起来很简单,但是对大的树效率不高。

第二种添加和删除节点的方法是更新新节点右边的所有节点。让我们看一下例子。我们要添加一种新的水果——“Strawberry”,作为“Red”的最后一个子节点。首先,我们要腾出一个空间。“Red”的右值要从6变成8,7-10的“Yellow”节点要变成9-12,如此类推。更新“Red”节点意味着我们要把所有左值和右值大于5的节点加上2。

我们用一下查询:

UPDATE tree SET rgt=rgt+2 WHERE rgt>5;
UPDATE tree SET lft=lft+2 WHERE lft>5;

现在我们可以添加一个新的节点“Strawberry”来填补这个新的空间。这个节点左值为6右值为7。

INSERT INTO tree SET lft=6, rgt=7, title='Strawberry';

如果我们运行display_tree() 函数,我们将发现我们新的“Strawberry”节点已经成功地插入了树中:

Food
Fruit
Red
Cherry
Strawberry
Yellow
Banana
Meat
Beef
Pork

缺点

首先,改进前序遍历树算法看上去很难理解。它当然没有邻接列表方法简单。然而,一旦你习惯了左值和右值这两个属性,他就会变得清晰起来,你可以用这个技术来完成临街列表能完成的所有事情,同时改进前序遍历树算法更快。当然,更新树需要很多查询,要慢一点,但是取得节点却可以只用一个查询。

总结

你现在已经对两种在数据库存储树方式熟悉了吧。虽然在我这儿改进前序遍历树算法性能更好,但是也许在你特殊的情况下邻接列表方法可能表现更好一些。这个就留给你自己决定了

最后一点:就像我已经说得我部推荐你使用节点的标题来引用这个节点。你应该遵循数据库标准化的基本规则。我没有使用数字标识是因为用了之后例子就比较难读。

进一步阅读

数据库指导 Joe Celko写的更多关于SQL数据库中的树的问题:
http://searchdatabase.techtarget.com/tip/1,289483,sid13_gci537290,00.html [6]

另外两种处理层次数据的方法:
http://www.evolt.org/article/Four_ways_to_work_with_hierarchical_data/17/4047/index.html [7]

Xindice, “本地XML数据库”:
http://xml.apache.org/xindice/ [8]

递归的一个解释:
http://www.strath.ac.uk/IT/Docs/Ccourse/subsection3_9_5.html [9]

posted @ 2008-06-27 15:55 存鹰之心于高远,取鹰之志而凌云,习鹰之性以涉险,融鹰之神在山巅. 阅读(301) | 评论 (0)编辑 收藏

仅列出标题
共6页: 上一页 1 2 3 4 5 6 下一页