inner join 性能研究

 

http://saptree.blog.163.com/blog/static/21318513820121127103050278/数据库

 

大部分ABAPer都是从SAP报表及打印开始学起的,你们也都认为写个SAP报表程序是最简单不过的事了。
可是实际状况真的如此吗?写报表时除了保证数据的准确性,您可曾考虑过报表的性能问题吗?
因为报表程序是被最多SAP用户所访问的,因此性能差的报表极可能会引来大量的抱怨和质疑,大大下降用户满意度。
最近作了较多性能优化方面的工做,很有感触,在此进行概括总结,但愿对你们有所帮助,也欢迎你们讨论。
1, 关于表链接语句(INNER JOIN, LEFT JOIN…)
写报表的时候,表与表之间的关联是不可避免的。一般而言,表链接语句要掌握的原则有:
(1) 将最有效的查询条件所对应的表放在第一位。换言之,让查询第一个表后所获得的结果集就尽量小。
好比有一张报表叫作订单状态统计表,可能查完VBAK、VBAP后还查询LIPS、VTTP等,界面上的查询条件不少。不过据了解得知,该报表主要是查看昨天建立或前几天建立的订单。那么最有效的限制条件天然是订单建立日期,以及销售组织了,而表链接的主表宜选用VBAK。
(2) 肯定了表链接的次序后,应考虑将查询条件尽可能限制在靠前的表里。好比选择屏幕上有个物料号的查询条件,而咱们知道订单VBAP和交货单LIPS均有物料号,那就应该视状况而定,若是表链接中VBAP更早出现,那么WHERE子句中就使用vbap~matnr IN s_matnr,反之就是lips~matnr IN s_matnr。
(3) 两个表之间进行链接的时候,应考虑关键字段或索引字段的做用。好比查询VTTP和LIPS时,关联关系是vttp~vbeln = lips~vbeln。那么vttp在前,lips在后,就会比较快,由于根据vttp的vbeln查询lips时,vbeln是lips的关键字段,速度较快。而反过来若是lips在前,那根据lips~vbeln查询vttp会慢一些,除非vbeln是vttp的索引字段。
2, 先构建RANGE再执行SQL语句
有时咱们所能用的查询条件不是很理想,好比查询LIKP却必须用公司代码,而非销售组织。
此时普通作法是用LIKP与TVKO根据VKORG进行表联接,从而限制TVKO~BUKRS的值。
还有一种有效的办法是,经过TVKO查询到当期公司代码所对应的所有销售组织,从而组建一个RANGE出来,再根据此RANGE查询LIKP。固然要注意RANGE的行项目有上限的,在ECC6中大概2万行将致使ABAP DUMP。
提示:DATA r_vkorg TYPE RANGE OF likp-vkorg.
SIGN = ‘I’, OPTION = ‘EQ’, LOW = ‘XXXX’ 便可往r_vkorg中放入多个单值。
3, For All Entries与Select Single的比较
就我的而言,笔者不是很喜欢For All Entries语句,由于它的缺点多于优势。不少人都会说,为何呀,For All Entries不是比Select Single快么?事实到底怎样呢,让咱们作个比较。
假设咱们有个内表表明销售订单的行项目,该内表有10万行。此时咱们要根据LIPS的VGBEL和VGPOS,查询这些订单行项目对应的交货单行项目。
若是用SELECT SINGLE的话写法很简单:
LOOP AT it_vbap INTO wa_vbap.
SELECT SINGLE vbeln posnr
FROM lips INTO (wa_vbap-vbeln_vl, wa_vbap-posnr_vl)
WHERE vgbel = wa_vbap-vbeln AND
vgpos = wa_vbap-posnr AND
vgtyp = ‘C’.
MODIFY it_vbap FROM wa_vbap.
ENDLOOP.
若是用For All Entries则写法分2步:
SELECT vbeln posnr
FROM lips INTO TABLE it_lips
For All Entries IN it_vbap
WHERE vgbel = it_vbap-vbeln AND
vgpos = it_vbap-posnr AND
vgtyp = ‘C’. “此交货单是根据订单建立的
LOOP AT it_vbap INTO wa_vbap.
READ TABLE it_lips INTO wa_lips WITH KEY vgbel = wa_vbap-vbeln
vgpos = wa_vbap-vgpos.
IF sy-subrc = 0.
wa_vbap-vbeln_vl = wa_lips-vbeln.
wa_vbap-posnr_vl = wa_lips-posnr.
MODIFY it_vbap FROM wa_vbap.
ENDIF.
ENDLOOP.
对于SELECT SINGLE而言,因为LIPS有个VGB的SAP自带索引,每次查询都挺快,即使循环10万次,速度虽然快不了但也没什么大的危害。
对于For All Entries的第一步,的确比SELECT SINGLE快些,原本10万次的SELECT SINGLE,变成了1万次的查询(若是BASIS设置了参数为10),每次查询10个订单行项目。可是第二步就很慢了, 暂估it_lips为8万行,则对于it_vbap的10万个行项目,有8万个行项目执行READ TABLE语句平均须要4万次才能搜索到it_lips的目标行,另有2万个行项目将读遍it_lips而后才发现没有对应的目标行。因此咱们一共须要搜索48亿次,太慢了!
在此针对For All Entries的使用提出几点意见:
(1)若是是根据某数据量大的内表用For All Entries读取数据量小的配置表,好比TVAK/T006等,那不如把For All Entries直接去掉,把表里的几十条数据所有取出。
(2)使用For All Entries时,SELECT语句后面的字段必须包含所查表关键字段。好比上面的vbeln/posnr就是lips的关键字段。若是不含关键字段,好比SELECT lfimg FROM lips For All Entries ***,那么当LIPS中两个条目关键字段不一样而lfimg相同时,会被SAP自动过滤掉一条。
(3)上面关于性能问题,应该利用BINARY SEARCH、SORTED TABLE或者HASHED TABLE来解决。详见下面第四节。
4, 关于BINARY SEARCH/SORTED TABLE/HASHED TABLE的使用
BINARY SEARCH即二分法查找,在保证内表按查询字段以升序排列的时候,能够采用二分法查找。二分法查找的速度很快,最大查询次数为log2n。以上面的例子来讲,若是it_lips事先按vgbel和vgpos排好序,则每次查找最多不超过17次。则对于it_vbap的10万个行项目,仅100多万次就能够搞定了!
当使用READ TABLE语法时,若是查询字段跟SORTED TABLE的排序开始字段能匹配上,则SAP将自动采用二分法查找。好比it_lips是SORTED TABLE且以vgbel和vgpos排序,则当read table以vgbel进行查找时,系统会自动采用二分法。但若是read table以vgpos和其余字段进行查找,因为vgpos并不是SORTED TABLE的第一排序字段,系统将采用直线查找,速度会慢不少。总之,SORTED TABLE的排序字段次序也很关键。
针对STANDARD TABLE排序后能够进行二分法查找,使用SORTED TABLE也可进行二分法查找,那么两者有什么区别呢?简单来讲,因为SORTED TABLE自始至终都保持排序,若是须要对内部进行频繁的插入、删除操做,则不推荐使用SORTED TABLE,性能会不好。而另外一方面,若是咱们要用的是相似LOOP AT it_lips WHERE vgbel = ** 的语法(而非READ TABLE)时,对于STANDARD TABLE就没法采用二分法查找,或者说会很麻烦。而对于SORTED TABLE,系统会自动采用二分法优化查找过程。
至于HASHED TABLE,笔者用得也不太多。查看SAP的标准代码,貌似用得也没不少。理论上HASHED TABLE能够比SORTED TABLE更快些,但须要耗用更大的存储空间。当某程序采用了二分法查找以后,若是效果还不是很理想,建议能够用HASHED TABLE试试。
5, 将循环内的重复性工做进行缓冲
有时咱们的内表数据量很大,但又不得不在每次循环的时候,都进行相似的一些操做,好比调用函数FI_PERIOD_DETERMINE获取某日期对应的会计年度和期间,调用函数MD_CONVERT_MATERIAL_UNIT进行单位转换,等等。每一个函数的调用背后都要执行一系列的读表以及运算工做,程序的效率明显降低了。因此咱们得想出有效的办法。
好比针对FI_PERIOD_DETERMINE的调用,能够改用循环前对函数G_PERIODS_OF_YEAR_GET的调用。根据公司代码BUKRS读取T001-PERIV,而后调用G_PERIODS_OF_YEAR_GET获取某会计年度每一个会计期间对应的起始日期和结束日期。这样在循环内部,只要根据上面的结果便可算出某日期对应的会计年度和期间了。
又好比针对MD_CONVERT_MATERIAL_UNIT的调用。相信对于it_vbap的10万个行项目,物料号重复的有不少。因此能够先汇总物料号,而后一次性读取表MARM以存储换算关系。有了MARM的换算关系,循环中大量的单位换算就能够本身算了,若是没法换算的再考虑调用函数MD_CONVERT_MATERIAL_UNIT。(函数MD_CONVERT_MATERIAL_UNIT除了读取MARM的换算关系,还会考虑同一维度单位间的换算关系好比G和KG的关系,因此其功能更强大。)
6, 关于字段的加强以及TABLE INDEX的建立
这里提到字段的加强,主要是性能方面相关的。假设咱们须要基于系统全部的billing document作个动做,好比将其导出到金税系统。至少有两种方案:第一是新建一个表,专门记录已经导出到金税的开票凭证;第二是在系统标准的开票凭证抬头表vbrk中新增一个字段,记录是否已导出到金税。
用户在处理业务的时候确定会反复查询“未导出到金税”的全部开票凭证。那么第一种方案下,咱们须要先查询VBRK表(好比获得1万条记录),而后针对自建表的记录(好比获得9800条记录)作个减法,最后获得200条记录的结果集。而第二种方案就快多了,查询VBRK的时候判断新字段=“未导出”便可。
随着业务的持续,VBRK表的条目将愈来愈多,而“未导出”的条目则会维持在一个较为平稳的数字上,为了有效区分历史数据和现用数据,可添加TABLE INDEX,提升报表的查询速度。不少SAP标准表都自带了一些索引,这些索引大都比较实用。
建立索引须要注意如下几点:
(1)索引会占用额外的数据库空间,还会下降插入/修改的速度(虽然可提升查询速度),因此须要考虑实用性,确定不是越多越好。若是表中已有相似的索引,则不推荐新建。而对于容量大的、被多个程序访问的表加索引就更要谨慎了,好比VBFA、MSEG、FAGLFLEXA、LIPS、VBAP、EDIDC、STXH等等。
(2)建立索引时应注意字段的前后次序,MANDT是必须的并且都要放在第一位。字段的前后次序取决于实际业务须要。另外索引的字段不宜太多,字段越多占用的数据库空间就越多,对于插入/修改的影响也更大。
7, 选用一些替表明/替代字段(VBFA, SHP_IDX_)
曾作过一些相似于“未拣配交货单”、“未发货过帐交货单”的报表,刚开始用的是LIKP、VBUK等表,速度并不理想。后来调试了标准程序VL06O,发现其用的表是SHP_IDX_PICK、SHP_IDX_GDSI等。原来系统在建立交货单的时候,也会更新这些临时表。当拣配完成,该条目就从SHP_IDX_PICK中删除。因此表SHP_IDX_PICK的条目数始终很少,查询速度很快。
一样的,当咱们在多个表中进行查询时,可能不一样的限制条件都能达到一样的结果集,但效率差别就很大,因此选用有效的字段很是关键。好比须要查询某销售组织某天已发货的销售订单,咱们能够将VBAP与LIPS联查。此时查询条件VBAK-LIFSK=SPACE或VBAP-ABGRU=SPACE并不影响查询结果,但它们能够在第一时间就排除大量无关的订单,对于性能的提高帮助很大。
8,考虑企业的特色及数据量情况
作优化不是简单的技术活,既要考虑报表的实际需求,也要考虑企业的业务情况。好比采用零售模式的企业客户量很大(KNA1表条目多),批发模式的企业客户量较小;食品饮料行业的物料号通常很少,而机械行业的物料号则每每多而繁杂;零售业的订单量很大、时间性很强,因此最有效的查询条件每每就是组织编码+日期,部分行业则可能订单量小但价值高。只有充分考虑到企业的业务特色,才能更有效地提升报表性能。性能优化

相关文章
相关标签/搜索