本期内容:
在数据库的很多应用场景下会出现需要通过多表联合查询返回结果集的情况,而大表间联表查询的执行效率高低往往是一个联机数据库的性能的决定因素。联表查询的连接方式主要有以下三种:嵌套循环(nestedloopjoin)、归并(mergejoin)、哈希(hashjoin)。其中嵌套循环是最为常见、也是最主要的连接方式。在本专题中将以sybaseASE15版本数据库为基础环境,对嵌套循环的连接原理以及因为统计值等原因优化器未能做出较优选择时手工进行调优的方法进行探讨。
案例描述:
首先我们来看一条出现在我们某套互联网相关系统数据库中的SQL语句:
该语句为用户登录后(TKF_CSTNO),查询指定一个或多个账号(FTI_FACCNO)下某些交易代码(TKF_BSNCOD)未提交交易(TKF_TASKSTT)的笔数。以下为该SQL的执行计划开销:
注:该树为后序遍历,VA为执行次序,r为返回行数,l为逻辑读个数,p为物理读个数。
注:本文中外部表、驱动表统一称为左表,内部表、被驱动表统一称为右表
?该语句有TASKID和CSTNO两个可选连接字段,由于左表和右表均有CSTNO字段(CSTNO=37***)作为筛选列,故优化器选择TASKID作为连接字段。T.TKF_CSTNO=F.FTI_CSTNOT.TKF_CSTNO=37***
语句中两表的CSTNO相同,且右表的CSTNO已经给出了等值查询的值,故查询优化器将该语句翻译为了:T.TKF_CSTNO=F.FTI_CSTNOT.TKF_CSTNO=37***F.FTI_CSTNO=37***
而从统计信息上,F表的FTI_CSTNO检索效率更高。故优化器第一步(VA=0)选择使用INDEX_FTASK(FTI_CSTNO),得到该客户号在F表中的记录数为条。第二步(VA=1),对F表已有结果集进行过滤(FTI_FACCNOIN(15-,15-)(F.FTI_SHOWISNULLOR(F.FTI_SHOWISNOTNULLANDF.FTI_SHOW!=E0--03))),仍为条。第三步(VA=2),即将F表的结果集代入到T表的连接字段TASKID所在的索引,由于PK_TASK索引层高为4,而F表的结果集高达行,故检索的开销约为*4,实际结果为个逻辑读。第四步(VA=3),将T表的结果集再进行条件筛选(TKF_TASKSTTIN(2,42)),结果为0行。最后返回count(*)的结果为0行。总开销接近个逻辑读。
案例分析:
那么优化器为什么会选择这样一个开销很大的查询计划呢?我们来分析一下。
通常来说,联表查询小结果集的等值连接会使用嵌套循环(NestedLoopJoin),即先从两表中选择一张表,将该表筛选出的结果集代入到另一张表的连接字段(该连接字段必须有索引便于检索),检索出第二张表中匹配的行,得到最终结果集。
执行总的逻辑读的次数由两部分组成:
1左表的筛选成本
2左表筛选后的结果集大小*右表连接字段的索引树高度(主要开销)
3两表连接后结果集的筛选成本
左表(驱动表)筛选后的结果集的大小决定了连接操作检索第二张表的循环次数,是决定联表查询效率的关键。
从上述的查询开销树中可以发现,F表作为左表并不理想,该表的筛选条件并未起到筛选的作用,而T表的筛选条件一下子将结果集筛选为0条,下面进行了验证。
F表的其他几项筛选条件FTI_SHOW、FTI_FACCNO没有起到任何作用,而T表的两个筛选条件TKF_TASKSTT、TKF_BSNCOD效果都非常好。通过对应用逻辑的分析,F表中的FACCNO为客户号下对应的账号(可以是多个),对于用户而言全选的可能性很大,而两张表的数据都保存天,相当于将该客户号下所有交易记录都取出来;而T表中的TKF_TASKSTT字段为交易状态位,’2’’42’为未提交交易状态,对于用户而言未提交交易的生命周期一般为1-3天内,数据量相对F表结果集会小很多。
影响查询优化器选择连接次序和连接方式的条件有很多,在绝大多数情况下优化器的选择是值得信赖的。而在某些特殊情况下,优化器可能无法选出最优方案。在这个例子中,由于T表没有建立(TKF_CSTNO,TKF_TASKSTT)的索引,故没有这两个组合键值的筛选率给予查询优化器作参考,且当使用IN操作符时会影响优化器的判断,这个时候可能需要我们人工进行干预。
优化方案:
可以有以下三种优化方案提供参考
1优化程序的SQL逻辑:
在这个问题场景中TASKID是唯一键,同一个TASKID在T表和F表中对应的CSTNO客户号是一致的。故当去除T.TKF_CSTNO=F.FTI_CSTNO这个条件时,对于应用来说语义并不发生变化,但是同时查询优化器则不能再将TKF_CSTNO=37***这个查询条件翻译成FTI_CSTNO=37***,这个时候T表就成了驱动表。以下是改写后的SQL语句的查询开销树:
VA=0,以T表作为驱动表,使用了IDX_TASK_2(TKF_CSTNO,TKF_BSNCOD)的索引,取出7行符合要求的记录。VA=1,使用TKF_TASKSTT状态位进行过滤,结果为0行。VA=4,由于驱动表结果集为0行,直接跳过嵌套循环,返回结果集为0。总共开销为13个逻辑读。(注:对于应用逻辑的优化需要与应用开发人员进行深度沟通,明确逻辑关系,这种优化方式不需要在系统层面做任何变化,计划仍旧由优化器自行选择。在很多情况下IO开销较大的联表查询不一定具备优化SQL逻辑的条件。)
2使用forceplan
通过打开forceplan开关可以控制SQL语句的多表连接次序,根据FROM后的表的先后关系来选择驱动表。从plancost的输出中可以看到优化器选择了A表作为驱动表,成功走上了A表的索引,整体的开销也下降到了15个逻辑读,但是连接方式使用了MergeJoin。(注:由于使用forceplan开关强行改变了优化器的执行计划,需要对应用行为模式深入了解,充分评估可能出现的例外情况的风险。)
?3强制索引控制
FTASK表的索引:
通过使用强制索引,也可以改变查询优化器对联表的表连接的先后次序。原语句使用了F表作为驱动表,并使用IDX_FTASK_CSTNO(使用CSTNO字段的等值查询)来检索驱动表的结果集。我们在这里将F表强制使用PK_FTASK索引。由于在WHERE子句中并没有在FTI_TASKID上出现合适的过滤条件(在该字段上有等值或范围过滤),而如果将F表作为驱动表,相当于进行了一次索引扫描,取出的结果集为全表,故优化器不会将F表作为驱动表。而FTI_TASKID是表连接字段,当T表为驱动表时,通过该字段的索引可以对F表的结果集进行检索。
查询优化器选择了A表作为驱动表,而在进行联表时,使用该索引与B表进行了嵌套循环。在这种情况下,使用强制索引既保证了优化器选择T表作为驱动表,又保证了两表连接时是使用嵌套循环方式。(注:由于使用强制索引也强行改变了优化器的执行计划,需要对应用行为模式深入了解,充分评估可能出现的例外情况的风险。)
开放系统支持部
ABCDC孔祥逸北京治疗白癜风中医医院什么药治疗白癜风