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表條目多),批發模式的企業客戶量較小;食品飲料行業的物料號通常很少,而機械行業的物料號則每每多而繁雜;零售業的訂單量很大、時間性很強,因此最有效的查詢條件每每就是組織編碼+日期,部分行業則可能訂單量小但價值高。只有充分考慮到企業的業務特色,才能更有效地提升報表性能。性能優化