中国开发网: 论坛: 程序员情感CBD: 贴子 700470
haitao
创新性应用 和 行业借鉴经验-牛新庄
http://blog.csdn.net/best_dba/archive/2006/07/21/952255.aspx

创新性应用-牛新庄
新一篇: 项目经验-齐红胤 | 旧一篇: 项目经验-牛新庄
创新性应用:

1, 在DB2 V7中,为了实现一个频繁访问的小表在内存中不被交换出来,为该表创建一个单独的表空间和缓冲池,每隔一段时间执行一次select count(*) from tabname保证该表一直在内存中。
2, 版本中才出现识别列(identify column),在DB2 V5版本中我通过使用触发器和用户自定义函数实现了识别列的功能。DB2 V6
3, 版本中才实现表空间容器自动扩展,在DB2 V8之前我们通过监控表空间的使用并且使用操作系统shell编程来实现在表空间使用超过80%(用户可自定义)阀值时自动扩展表空间。DB2 V8.2
4, 在DB2 V7.1中,事件监视器的输出结果是文本,有的时候文本很大(几百兆),查看非常不方便,为此我们根据输出文件的结构自定义了表,并把结果load到该表中,从而使用SQL来进行详细的分析。这个功能直到DB2 V8才实现。
5, 中没有truncate命令,为此我们是使用db2 load from /dev/null(或空文件)来间接代替truncate命令。DB2
6, 在DB2数据库rownumber() over()函数出来之前,在之前的DB2版本中,使用递归函数实现类似rownumber() over()函数功能。
7, 的数据库日志是不开放的,用户无法查看日志的内容,自己调用内部API函数,用C编写读取数据库日志的程序。DB2
8, 命令无法指定目录,自己调用操作系统shell编程和读取数据库系统表编写程序来代替DB2MOVE命令并实现指定目录功能。DB2MOVE


行业借鉴经验

1,在DB2数据库的设计中,尽量采用DMS表空间提高性能,分别把表中的索引(index),常规数据(data)和大对象(BLOB,CLOB,DBCLOB)分割存放在不同的DMS表空间中,并且把DMS表空间放在裸设备(raw device)上,这样大大提高了读写(I/O)的并行访问,优化了数据访问的速度;另外在缓冲池的设计上,OLTP系统建议创建了多个缓冲池,分别为索引表空间,数据表空间指定各自的缓冲池,这样可以使它们减少对一个大缓冲池的竞争,从而减少交换(swapping)操作,提高从缓冲池命中率(hit ratio),提高访问速度;OLAP系统建议创建一个大的缓冲池。同时,如果DB2数据库中存在大量连接(join),分组(group by),排序(sort)操作,所以为了提高这些操作的速度,对系统临时表空间所在的文件系统预留了大量文件系统空间。
2,在DB2数据库中,数据库中的大对象(BLOB,CLOB,DBCLOB)数据类型的访问无法通过内存,所以大对象类型的存在直接对数据访问的性能产生影响。如果数据库中存在大量大对象类型,对系统的性能和I/O读写直接带来性能的低下,建议在满足业务需求的情况下,避免了无必要的加大大对象的长度,考虑把有些大对象用varchar代替。
3,在使用数据库的时,锁是控制并发的,每个不同的数据库的锁的实现机制是不一样的,Oracle通过具有意向锁的多粒度封锁机制进行并发控制,保证数据的一致性。其DML锁(数据锁)分为两个层次(粒度):即表级和行级。通常的DML操作在表级获得的只是意向锁(RS或RX),其真正的封锁粒度还是在行级;DB2也是通过具有意向锁的多粒度封锁机制进行并发控制,保证数据的一致性。其DML锁(数据锁)分为两个层次(粒度):即表级和行级。通常的DML操作在表级获得的只是意向锁(IS,SIX或IX),其真正的封锁粒度也是在行级;另外,在Oracle数据库中,单纯地读数据(SELECT)并不加锁,这些都提高了系统的并发程度,Oracle强调的是能够"读"到数据,并且能够快速的进行数据读取。而DB2的锁强调的是"读一致性",进行读数据(SELECT)时会根据不同的隔离级别(RR,RS,CS)而分别加S,IS,IS锁,只有在使用UR隔离级别时才不加锁。从而保证不同应用程序和用户读取的数据是一致的; 在支持高并发度的同时,DB2和Oracle对锁的操纵机制有所不同:Oracle利用意向锁及数据块头部上加锁标志位等设计技巧,减小了Oracle维护行级锁的开销,使其在数据库并发控制方面有着一定的优势。而DB2中对每个锁会在锁的内存(locklist)中申请分配一定字节的内存空间,具体是X锁64字节内存,S锁32字节内存(注:DB2 V8之前是X锁72字节内存而S锁36字节内存);Oracle数据库中不存在锁升级,而DB2数据库中当数据库表中行级锁的使用超过locklist*maxlocks会发生锁升级;在Oracle中当一个session对表进行insert,update,delete时候,另外一个session仍然可以从Orace回滚段或者还原表空间中读取该表的前映象(before image); 而在DB2中当一个session对表进行insert,update,delete时候,另外一个session仍然在读取该表数据时候会处于lock wait状态,除非使用UR隔离级别可以读取第一个session的未提交的值;所以Oracle同一时刻不同的session有读不一致的现象,而DB2在同一时刻所有的session都是"读一致"的。在Oracle数据库中不存在锁升级现象,而DB2数据库存在锁升级现象,为此,建议在DB2数据库中采用以下建议:
l 1. 正确调整locklist,maxlocks,dlchktime和locktimeout等和锁有关的数据库配置参数(locktimeout最好不要等于-1)。如果锁内存不足会报SQL0912错误而影响并发。
l 2.写出高效而简洁的SQL语句(非常重要)。
l 3.在业务逻辑处理完后尽可能快速commit释放锁。
l 4.对引起锁等待(SQL0911返回码68)和死锁(SQL0911返回码2)的SQL语句创建最合理的索引(非常重要,尽量创建复合索引和包含索引)。
l 5.使用 altER TABLE 语句的 LOCKSIZE 参数控制如何在持久基础上对某个特定表进行锁定。检查syscat.tables中locksize字段,尽量在符合业务逻辑的情况下,每个表中该字段为"R"(行级锁)。
l 6.根据业务逻辑使用正确的隔离级别(RR,RS,CS和UR)。
l 7.当执行大量更新时,更新之前,在整个事务期间锁定整个表(使用 SQL LOCK TABLE 语句)。这只使用了一把锁从而防止其它事务进行这些更新,但是对于其他用户它的确减少了数据并发性。
4,在数据库隔离级别的使用上,建议大家详细了解每种数据库的隔离级别,DB2,Oracle,Sybase和Informix数据库的隔离级别是不一样的,在很多客户应用中,我看到应用开发人员直接把informix的隔离级别照搬到DB2数据库中。要结合不同的数据库产品,在保证业务逻辑允许的情况下,使用合理的隔离级别(例如:DB2 UR隔离级别)来最大程度上提高数据库的并发。
5,对于数据库中出现的大量死锁,锁升级和锁等待出现,为了解决上述问题,首先,需要加大相关数据库中有关锁的参数(locklist,maxlocks,locktimeout和dlchktime)等数据库配置参数;其次,为了让锁的快速释放不至于引起交易阻塞,就需要我们在表上创建合理的索引。所以,造成引起锁等待的应用程序和SQL语句,对这样应用和SQL进行合理的构建索引。
6,应用开发中的很多SQL语句运行效率低下,这些SQL语句在写法和谓词使用存在很多问题,例如:大量使用select *,select count(*),使用not in,not exist,使用函数等,对于这些问题,需要对开发人员仔细利用解释工具相信分析SQL语句的执行计划,写出高效的SQL。
7,对数据库创建最合理的索引(太多,影响insert速度,浪费存储;太少,不能显著提高查询速度),尽量的多创建复合索引和包含(include)索引。应用开发人员往往凭借自己对业务的理解在表上建立了很多索引,结果是这些索引在SQL执行期间根本没有使用,这些冗余索引的存在直接导致了空间存储的浪费和对插入(insert)操作的影响;所以,正确的做法是我们应该正确的分析该表的读写情况,分析表中SQL语句执行的频率,对每一条SQL语句做解释(explain)从而来评估是否使用创建的索引并为该表创建最合理的索引。一般情况下,最好在OLTP的表上建超过8个索引。
8,合理的调整操作系统交换空间(设置大交换空间的只是可能会带来磁盘空间的浪费,但是可以带来性能的提高),内核参数(AIX vmtune,schedtune等);调整数据库配置参数;合理的设置应用,中间件等相关和数据库的接口配置,调整应用性能;
9,数据库前期的合理的架构设计(物理设计和逻辑设计)是整个项目成败的关键,合理的架构设计为整个项目稳定可靠高效运行打下了良好的基础,同时也起到了事半功倍的作用。
10, 合理的调整系统的物理资源,对数据库的配置参数做合理的调整,保证系统物理资源(CPU,I/O,内存和网络)和逻辑资源(裸设备,文件系统等)合理的分布和应用。一般综合考虑建议数据放在裸设备上,数据库日志放在文件系统上。
11, 在java应用开发中,注意对内存进行垃圾回收,虽然java本身有垃圾回收机制,但是有些调用java不会自动进行垃圾回收的。
12, 在进行海量数据移动的时候,建议使用数据库中最合适的技术(例如: ORACLE SQL*LOADER,DB2 LOAD等)来提高数据移动速度。
13, 对频繁更新操作的表,建议在业务不太繁忙期间对数据库表进行“碎片”整理和统计信息分析更新。这一点在DB2数据库中尤其重要。
14, DB2数据库中没有类似Oracle的truncate函数,直接delete大表的操作时候容易产生大量日志,锁并耗时长,建议在应用中使用load replace相关的API函数来实现delete全表的操作。
15, 信息系统前期的选型和架构设计是非常关键的,这是一个方向性的问题,成功的选型和架构设计是成功的一半,例如梅州农信在做综合业务系统时候选用了java平台做前台开发,而java应用开发不太适合银行业务系统后台核心帐务处理,这直接导致了运行效率的低下和对资源的消耗,如果前期他们有一个很好的高级数据库架构设计工程师也许可以避免这些问题的出现。由于前期的架构设计和选型不当,直接导致了后续的工作只能是“打补丁”的工作,这些导致了后续项目的被动性以及和其它银行系统和新上线系统的兼容性。
16, 应用编程人员需要对业务需求有详细的了解,应该充分做好前期的业务需求分析,例如在云南社保金卡工程项目中经常出现很多由于前期业务需求没有做好而导致后期重新“返工”现象,这直接影响了项目的按时上线。所以,编程人员和数据库工程师需要对企业的业务需求有更详尽的了解和分析。
17, 应该为企业信息系统制定合理的安全策略以消除潜在的安全隐患。
18, 需要为企业的信息系统根据企业自身需求制定一个合理的备份恢复和灾备策略。
19, 现在企业信息架构往往是一个大型的复杂系统。在这个系统中从上至下包括以下几个层次:应用程序、数据库、主机系统(操作系统)、网络和存储系统。在发生系统的性能问题时,性能问题的定位和调优就很复杂。这就需要我们不断的拓宽知识面,需要对企业信息架构有全局的了解,因为现在企业信息架构非常复杂,往往涉及到的不只是数据库,还有操作系统,中间件,应用,存储等,所以需要对信息架构全局有很全面的了解,这就需要不断的学习和更新新的技术。例如我们在做江苏电力公司负控系统上线期间,因为时间紧迫(一个地市只有一晚上将近10小时时间),在这么短的时间内,很难保证很顺利的上线,如果出现问题,一定要快速的定位是在什么层面(操作系统,数据库,中间件,应用,存储等)出现的问题然后快速解决之,所以必须具备全局的对信息架构的驾驭能力。
20, 国内的大部分应用开发商没有用到数据库内部的很多技术,这往往导致应用运行性能低下,在应用中检查资源消耗最大语句的逻辑设计。建立合适的分区索引,将排名靠前语句的表数据与索引分别存储。提高资源消耗靠前语句的并行度。
21, 要善于借助于第三方的监控软件(如:StorWatch Expert和Precise软件),这些软件的运用可以快速的定位性能瓶颈从而更快速的作出性能调整。
22, 在应用开发期间,对不同的数据库版本,不同的操作系统版本和不同的中间件版本做充分的测试,确保之间的兼容性。我们曾经在北京工行碰到CICS 5.0版本无法在Solaris的高版本中稳定的运行。
23, 和相关业务部门的沟通和协调非常关键,很多时候我们需要他们配合才能完成相关工作,所以交流和沟通是非常关键的。国内很多垄断部门的员工往往非常牛,这就需要我们耐心的,不厌其烦的去和他们做好沟通和协调。同时在进行问题诊断时,也需要协调好应用开发商,系统集成商和系统维护商之间的关系。
24, 企业在使用相关软件的时候,最好不要用最新的版本而要用相对稳定的版本。例如我们在做江苏电力公司负控系统时,发现在部分地市,由于使用了最新的DB2版本,发现上线后有很多问题,后来经过问题诊断和咨询IBM,发现是DB2新版本bug的问题。
25, 在AIX系统上,建议调整AIX的缺省内核参数,调整文件内存和计算内存的页面替换算法所关联的vmtune所对应的maxperm,minperm和maxclient参数。
26, 在AIX系统上,不要在业务高峰期间做compress,tar和大文件ftp的操作。
27, 在做性能调整的时候,注意系统(操作系统,数据库,中间件,应用)性能调整需要循序渐进,逐渐增加调整值,不可一蹴而就。
28, 调整相关配置参数时,需要对该配置参数有深刻的理解,当然这需要不断的补充理论知识。
29, 在性能调整时,要做好系统备份和数据库备份,要胆大心细,小心谨慎。
30, 在数据库配置调整时,一定要有最坏的打算,做好失败冗余和恢复方案。
31, 不可完全相信所谓的经验法则,那只是提供一种参考,我就是看了书上说的把缓冲池调整为系统内存的70%这条经验法则才间接导致海南美兰机场离港系统停机的。
32, 在系统运行高峰期间,如果能够忍受,尽量保持系统的平衡稳定运行,否则可以尝试先在备机或测试机上调整。
33, 要把在书上看到的理论和在实践中碰到的实际问题结合起来。
34, 要不断的认真总结经验,积累,失败的经验是一种宝贵的财富,它对我后期从事性能调整产生了很深远的影响,所以有时候要好好的总结失败并从中汲取经验教训。
35, 在列的长度变化不是特别显著时,建议从char代替varchar数据类型。
36, 如果一个表上有大量并发,尽量不要在该表上创建触发器。

应用难点技巧:
1, 在应用中,用户经常问到如何在一个表创建最合理的索引?为此采用以下方法步骤:
1) 首先评估最该表操作的SQL语句,这个应用开发人员或建表人员应该知道,如果不知道可以通过DB2监控工具获取。
2) 评估在该表上每一条SQL语句的执行频率(例如:有的SQL一天执行8次,有的一天执行6700次)。
3) 评估在该表的读写操作比例。
4) 针对以上创建索引,如果有些索引之间重复的字段比较多,考虑进行索引的合并;并把合并后的索引用EXPLAIN解释每条SQL是否使用该索引。
按照这种方式创建索引,可以保证创建索引的合理性和避免创建索引的冗余性,这种技巧应用在交通银行大集中项目和中信银行绩效考核系统中。
2, 客户需要知道一个表最后修改(insert,update,delete)操作的时间?
对于这种业务需求,我们可以利用DB2的触发器来实现,每次对该表的操作,都被加上一个时间戳插入到另外一张表中,从而获取最后修改的时间,不过这种方式对性能有所影响。
3, 用户希望知道是谁每天最后修改了一个特定的表?
对于这种技术,我们使用DB2 Audit机制,并且把Audit产生的日志格式化成del文件,我们根据del文件格式创建表,把del文件导入到数据库表中,可以用SQL获取每天最后修改表的人。
4, 数据库管理员希望在执行”db2 list tablespaces show detail“命令时实现类似”df –k“的输出结果,为此需要调用DB2 API函数和读取系统数据字典编程实现,程序源代码如下:
#include "XTGetTPInf.h"
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <time.h>
#include <sql.h>
#include <sqlenv.h>
#include <sqlda.h>
#include <sqlca.h>
#include <string.h>
#include <ctype.h>
#include <sqlcodes.h>
struct sqlca sqlca;
EXEC SQL BEGIN DECLARE SECTION;
char sInputTimeStamp[27];
short sInputTimeStamp_ind;
char DBName[20];
char DBUserName[20];
char DBUserPass[20];
EXEC SQL END DECLARE SECTION;
char RootPath[100];
int changedcnt=0;
int alltime=0;
void main( int argc, char *argv[] )
{ /*参数小于4,提示输入密码*/
if (argc!= 6){
printf( "\nUSAGE: TRANLOG dbalias userid passwd %d\n\n",argc );
}
/*读入数据库名、用户名和密码 */
if(argc==6){
strcpy(DBName, argv[3]);
strcpy(DBUserName, argv[4]);
strcpy(DBUserPass, argv[5]);
}
EXEC SQL CONNECT TO :DBName USER :DBUserName USING :DBUserPass;

if(argc!=6 && argc!=7) {
printf("\nUsage1: ChangeSeparator InputFileName OutputFileName RootPath(d:/datacollect)\n");
printf("Usage2: ChangeSeparator DateType(Day|Month) RootPath(d:/datacollect)\n\n");
exit(-1);
}
if(argc==6)
{
strcpy(RootPath,argv[2]);
changeprocess_A(argv[1]);
}
else
{
strcpy(RootPath,argv[3]);
changeprocess_B(argv[1],argv[2]);
}
}
changeprocess_A(char *datetypedef)
{
int i=0;

for(i=0;i<MAXFILECOUNT;i++)
{
if(strcmp(datetypedef,ChangeStruct[i].DateTypeDef)==0)
changeonefile(ChangeStruct[i].InFileName,ChangeStruct[i].OutFileName);
}
printf("\n\n... [%s]: Have Changed %d files.\n",datetypedef,changedcnt);
printf("\n...All Spend Time: %ds\n\n",alltime);
return changedcnt;
}

changeprocess_B(char *infile,char *outfile)
{
changeonefile(infile,outfile);
return 1;
}

changeonefile(char *infile,char *outfile)
{


FILE *fi;
FILE *fo;
char ifile[200],ofile[200];
unsigned char readbuff[1024];
unsigned char writebuff[1024];
int row=0,i=0,ln=0,j=-1,k=-1;
int EOF_flag=0;
int readchar=0;
int COLBegin=0;
int CNBegin=0,CNEnd=0;
int iColCount=0;
time_t ltime1,ltime2;

char str[2];
char str2[2];
char tmpstr[26];
char corigin='=';
char corigin2=' ';
char cfinal=' ';
char creturn='\n';
char csearch;
int numwritten;

tmpstr[0]=0x0;
sInputTimeStamp[0]=0x0;
sInputTimeStamp_ind=0;

if((row=chkfilename(infile))==-1) {
printf("\nThis File %s Can't be Transfered, Register It First!\n",infile);
return(-3);
}
if(RootPath[strlen(RootPath)]=='/')
RootPath[strlen(RootPath)]=0x0;
sprintf(ifile,"%s%s%s",RootPath,INPUTFILEDIR,infile);
sprintf(ofile,"%s%s%s",RootPath,OUTPUTFILEDIR,outfile);

if ((fi=fopen(ifile,"r"))==(FILE *)NULL)
{
printf("\nCan not open file %s for read\n",ifile);
return(-2);
}

if((fo=fopen(ofile,"w"))==(FILE *)NULL)
{
printf("\nError! Can't open file %s for write\n",ofile);
fclose(fi);
return(-4);
}

printf("\nChange struct row:%d\n",row+1);

time( &ltime1 );
printf( "%s --> %s , \nThe start time is %s\n", ifile,ofile,ctime( &ltime1 ) );
EXEC SQL select distinct current timestamp into :sInputTimeStamp:sInputTimeStamp_ind from SYSIBM.SYSTABLESPACES;
if(sInputTimeStamp_ind) sInputTimeStamp[0]=0x0;
sprintf(tmpstr,sInputTimeStamp,26);
/*fwrite(tmpstr,sizeof(char),strlen(tmpstr),fo);
str2[0]=cfinal;
str2[1]='\0';
fwrite(str2,sizeof(char),strlen(str2),fo);*/
while(1)
{
start1: if(fgets(str,2,fi)==NULL) break;
else
{
csearch=str[0];

if(csearch==creturn)
{
ln++;
iColCount=0;
if(ln>=3)
{
if(ln%17==8||ln%17==9)
{
/*除去空行*/
}
else if(ln%17==3)
{
/*除去空行,同时在每条记录开头增加一个字段*/
if(fgets(str2,2,fi)==NULL) break;
else
{
if(str2[0]==creturn)
{
str[0]=str[0];
goto start1;
}
else
{
fwrite(tmpstr,sizeof(char),strlen(tmpstr),fo);
str2[0]=cfinal;
fwrite(str2,sizeof(char),strlen(str2),fo);
}


}

}
else if(ln%17==2)
{
/*在每一块的最后一行加换行符*/
fwrite(str,sizeof(char),strlen(str),fo);

}
else
{
/*其余的都作为正常的字段来处理*/
str[0]= cfinal;
fwrite(str,sizeof(char),strlen(str),fo);
}
}

}
/*else if(csearch==corigin)*/
else if(csearch=='=')
{
iColCount++;
}
else if (csearch==corigin2)
{
iColCount++;
}
else
{


if(ln>=3)
{
iColCount++;
/*去掉前面35列*/
if(iColCount>=36)
fwrite(str,sizeof(char),strlen(str),fo);
}
}
}
i++;
if(i%1000000==0) printf(".");
}

fclose(fi);
fclose(fo);
printf("\nRows Count:%d\n\n",ln);

if(ln==0)
unlink(ofile);
time( &ltime2 );
alltime+=(ltime2-ltime1);
printf( "The end time is %s\n", ctime( &ltime2 ) );
printf("Spend time= %lds\n\n",ltime2-ltime1);
printf("%s --> %s , Change OK!\n\n",ifile,ofile);
changedcnt++;
return 0;
}
int chkfilename(char *filename)
{
int i=0,ret=-1;
for(i=0;i<MAXFILECOUNT;i++)
{
if(strcmp(filename,ChangeStruct[i].InFileName)==0)
{
ret=i;
break;
}
}
return ret;
}
/*strprocess(readbuff,writebuff,collen)
unsigned char readbuff[1024];
unsigned char writebuff[1024];
int collen;
{
int i=0,j=0;
astopc(readbuff,writebuff,&collen);
for(i=0;i<strlen(writebuff);i++)
{
if(writebuff[i]<0x20) {
writebuff[i]=' ';
}
}
writebuff[i] = 0x0;
i=strlen(writebuff);
while( i-- && writebuff[i]==0x20);
writebuff[i+1] = 0x0;

return 0;
}*/
我的blog:http://szhaitao.blog.hexun.com & http://www.hoolee.com/user/haitao
--以上均为泛泛之谈--
不尽牛人滚滚来,无边硬伤纷纷现 人在江湖(出来的),哪能不挨刀(总归是要的)
网络对话,歧义纷生;你以为明白了对方的话,其实呢?

您所在的IP暂时不能使用低版本的QQ,请到:http://im.qq.com/下载安装最新版的QQ,感谢您对QQ的支持和使用

相关信息:


欢迎光临本社区,您还没有登录,不能发贴子。请在 这里登录