1、所谓查询优化(Query Optimizer)就是尽可能地选择一个查询计划,使得该查询计划在查询执行时的总开销和总时间达到最小。 从总体而言,数据库查询优化分为物理层优化和逻辑层优化两大部分,涉及数据库的物理设计、体系结构设计、逻辑设计以及数据库管理系统设计等多个方面。数据库物理设计的目的是建立高效的存储结构和有效的物理存储布局,涉及文件组织形式、存储映射算法、存储媒介等方面,这些都是提高数据库查询效率的基础。而在逻辑设计方面,模式分解理论和规范化理论提供了坚实的理论基础和有效的手段,依据模式分解理论及规范化理论进行设计,可以避免一定的更新异常,并在很大程度上消除数据冗余,增进数据库的完整性,
2、从而提高数据库的可靠性及可维护性。 当前对查询优化所展开的研究主要包括两个方面:一是外部优化,即利用现有的查询优化器最大限度地挖掘计算机系统的软、硬件潜力,提高查询效率,它针对影响查询的多种因素,涵盖从系统分析、设计到实现的各个阶段,其中包括数据的存储与组织、SQL 语句的优化、前端开发工具的使用技巧及后台数据库的参数调整等;二是内部优化,即对查询优化器的工作原理及其设计方案进行研究。对于查询优化的处理通常表现为这样几个方面:数据库统计信息的管理、查询优化算法设计、SQL的查询重写、分布式查询优化及基于缓存技术的查询优化。2 查询优化器的设计与实现 对于给定的一组查询语句,数据库查询优化器必须
3、能够选择一个较好的查询指令序列去执行。查询优化通常要经历两个阶段,第一阶段只包括查询重写模块,第二阶段则包括逻辑关系的优化、执行操作的优化和操作间关系的优化3个层次。其中,查询重写主要通过对所给查询施加一些特定的变换(如等价谓词替换),从而产生比原来效率更高的查询;逻辑关系的优化则主要根据数据库关系表间的查询条件,选择究竟先对哪个表作连接,即通过条件类型及相关属性决定其关系代数运算的操作;执行操作的优化主要针对某一特定操作,研究使其达到最佳执行效果的算法;操作间关系的优化则主要针对不同的操作序列,利用计算机的并行性对执行序列进行重新组合,从而实现优化。 由于在查询重写阶段,优化器所做的工作是静
4、态的,它只依赖于查询本身的特点,即如果查询满足了某些特性或条件则会实施相应的重写操作,而根本不会考虑查询的执行代价。于是,不管重写后的查询效率是否高于原查询,新产生的查询都将被传至下一阶段处理;而逻辑关系的优化则主要表现为基本join算法的优化,即根据关系表及其选择条件,判断究竟先对哪张表作连接,并通过条件类型及相关属性决定关系代数运算的操作。因此,这里将主要介绍用于执行操作优化的分块连接算法及用于操作间关系优化的DataParallel优化器的设计与实现。2.1 执行操作的优化分块连接算法的实现与优化 作为当前最为流行的主存join 算法,分块连接算法实现了对hash表的随机访问模型,用于找
5、到数据库关系表内部的值。然而,现代计算机的cache容量是有限的,当所要计算的数据超过cache容量或计算所需hash表的大小超出cache容量时,就会涉及cache与内存之间的换入换出,从而增加时间开销。 比如,当两列(假设为A1、A2,A2 是小表列)作hash-join时,在A2上建hash,当A1、A2被载入内存时,会将部分A1先载入cache,接着对cache中A1的每条数据,在A2中进行hash值的比较,若hash值的跳跃比较大,则会频繁地从内存中加载hash值。此时,大量时间将消耗在内存到cache的换入换出上。因此,通常使用Partition-join算法将hash值相近的块放
6、在一起。 作为一种基于分块的hash-join算法,Partition-join算法的核心思想在于通过对计算机内存及CPU资源的使用进行优化,从而提高join操作的内存访问性能,相比于一般的join算法,其性能将有一定的提升。Partition-join算法的实现流程如图1所示:图1 Partition-join算法执行流程 由此流程图可以看出,首先应对进行连接的左、右两边关系表进行分块,分块的依据是分块所需的趟数和位数,此时左、右两边的分块均已位于内存中,接下来便可针对相应的分块(由hash值决定)进行具体的hash-join 操作。 这里,首先需要定义计算hash的宏,目的是减少函数调用,
7、提高速度: #define HASH(c) (c5) XOR (c11) XOR (c17) XOR c); 接着定义进行连接操作的元组结构,假设两个元组均包含两列,并且第一个元组的后一列与第二个元组的第一列作连接。 Typedef struct Int c1,c2; /两列 tuple; /内部元组结构(二元组) 下面给出分块操作算法的实现过程: radix_cluster(tuple *dst2D, tuple *dst_end2D /输出缓存,存储分块后结果 tuple *rel,rel_end, /*输入关系,关系结束指针*/ Int R,int D /*关系索引,位数*/ ) int
8、 M = (2D-1) R; for(tuple *cur=rel; curc2)&M; / int idx = HASH(cur- memcpy(dstidx, cur, sizeof(tuple); / *dstidx = *cur; if (+dstidxdst_endidx) REALLOC(dstidx,dst_endidx); /重新分配空间 partition-join(L, R, H) radix-cluster(L,H) /对左边列分块 radix-cluster(R,H) /对右边列分块 FOREACH cluster IN 1.H hash-join(Lc, Rc) /对
9、左、右两边相应的分块作hash-join操作 按照对关系数据块操作算法的不同,通常使用partition-join算法在匹配的块上执行hash-join操作,而利用radix-join算法在匹配的块上执行nested-loop-join操作。radix-join算法利用radix-cluster算法的分块功能,当分成块的数目很大时,分块操作能够使可能匹配的元组相互接近。对于所形成的分块,利用一个简单的嵌套循环操作就能够找到相匹配的元组。需要注意的是,应先决定进行连接的左、右两个关系的大小,大的关系做外层嵌套循环,小的则做内层嵌套循环,具体的连接由指针函数comparefcn进行处理。这里给出嵌
10、套循环连接(nested-loop)的算法实现: nested-loop(tuple *dst, tuple *end, /* start and end of result buffer*/ tuple *outer, tuple *outer end, tuple *inner, tuple * inner end; /* inner and outer relations */) for(tuple *outer_cur=outer; outer_cur outer_end; outer_cur+) for(tuple *inner_cur=inner; inner_cur c2,inn
11、er_cur-c2)=0) /*优化语句 if (outer_cur-vc = inner_cur-c2)*/ memcpy(&dst-c1,&outer cur-c1, sizeof(int); /*优化语句 dst-c1= outer cur-c1;*/ memcpy(&c2,&inner cur-c2 = inner cur- if (+dst_end) REALLOC(dst,end); 由此算法可以看出,一旦内存访问优化完成,partitioned hash-join的时间将主要花费在CPU上。该算法体现了3种主要的优化思想:将建hash表和遍历外部表的hash函数定义成宏,减少函数
12、调用时堆栈的开销;将memcpy函数优化成赋值语句,避免每次迭代都需花费的函数调用开销;将%运算转换成&运算,提高了执行效率。2.2 操作间关系的优化DataParallel优化器的设计与实现 DataParallel优化器主要用于维护全局工作队列,对SQL语句解析后的指令序列进行处理,依次判断它们之间是否存在依赖关系,从而选择可以多线程并行执行的指令,将其放入全局队列中,供多线程执行。为了判定各条指令之间的相互依赖关系,DataParallel 优化器先将所有可执行的指令封装在一个能够被监视的块中,代码开始执行前初始化多个线程,这些线程是根据CPU的核数确定的,各个线程通过队列调用所有可用指
13、令。 首先,定义DataParallel优化器所使用的数据结构,用它来封装指令块和参数块,并定义指令依赖关系的标识信息。DataParallel的结构如下: Typedef struct BlkPtr mb; /上下文信息即指令快信息 StkPtr stk; /参数块 int start, stop; /块的起止指令书 char *status; /指令是否能被阻塞 char *blocked; /阻塞标志,需初始化 int *foruse; /被使用的次数 int *reassign; /是否需重新赋值,哪条指令是赋值的 queue *done; /标识队列中指令是否处理完 *DataParallel, DataParallelRec; 每条指令的结构如下: bit token; /
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1