2011-08-08 200 views
3

我有一個怪物遺留查詢是在我的程序的核心, 查詢需要太多的時間,是什麼讓它跑得更快的最佳方式? 我用的Oracle 11g優化SQL查詢

SELECT * 
    FROM  (SELECT COUNT(*) AS countme, 
         string_value  , 
         name    , 
         property_id   , 
         category_id 
       FROM (SELECT DISTINCT a.string_value, 
             a.name  , 
             a.property_id , 
             b.product_id , 
             a.category_id 
         FROM    filter_criterias a 
             JOIN product_properties b 
             ON    (
                      a.property_id = b.property_id 
                 AND 
                      (
                          (
                              isnumber(b.value)  IS NOT NULL 
                          AND    isnumber(a.range_bottom) IS NOT NULL 
                          AND    isnumber(a.range_top) IS NOT NULL 
                          AND 
                              (
                                  a.range_bottom >a.range_top 
                              AND    b.value  >= a.range_bottom 
                              OR    a.range_bottom<=a.range_top 
                              AND    b.value  >= a.range_bottom 
                              AND    b.value  <=a.range_top 
                              ) 
                          ) 
                     ) 
                 ) 
             JOIN PRODUCT_CATEGORY prc 
             ON    (
                      prc.sku   = b.product_id 
                 AND    prc.category_id = a.category_id 
                 ) 
             JOIN PRODUCT pr 
             ON    (
                      b.product_id = pr.SKU 
                 AND    pr.visible = '1' 
                 ) 
         ) 
       GROUP BY (string_value, name, property_id,category_id) 

       UNION 

       SELECT COUNT(*) AS countme, 
         string_value  , 
         name    , 
         property_id  , 
         category_id 
       FROM (SELECT DISTINCT a.string_value, 
             a.name  , 
             a.property_id , 
             b.product_id , 
             a.category_id 
         FROM    filter_criterias a 
             JOIN product_properties b 
             ON    (
                      a.property_id = b.property_id 
                 AND 
                      (
                          (
                              a.name= b.value 
                          ) 
                     ) 
                 ) 
             JOIN PRODUCT_CATEGORY prc 
             ON    (
                      prc.sku   = b.product_id 
                 AND    prc.category_id = a.category_id 
                 ) 
             JOIN PRODUCT pr 
             ON    (
                      b.product_id = pr.SKU 
                 AND    pr.visible = '1' 
                 ) 
         ) 
       GROUP BY (string_value, name, property_id,category_id) 
      ) 
    ORDER BY 5,4,3,2 

這說明計劃

"Optimizer" "Cost" "Cardinality" "Bytes" "Partition Start" "Partition Stop" "Partition Id" "ACCESS PREDICATES" "FILTER PREDICATES" 
"SELECT STATEMENT" "ALL_ROWS"  "1298" "2"   "542" "" "" "" "" "" 
"SORT(ORDER BY)" ""    "1298" "2"   "542" "" "" "" "" "" 
"VIEW"    ""    "1297" "2"   "542" "" "" "" "" "" 
"SORT(UNIQUE)"  ""    "1297" "2"   "74" "" "" "" "" "" 
"UNION-ALL" ""  ""    "" "" ""   "" "" "" "" 
"HASH(GROUP BY)" ""    "661" "1"   "37" "" "" "" "" "" 
"VIEW"    ""    "659" "1"   "37" "" "" "" "" "" 
"HASH(UNIQUE)"  ""    "659" "1"   "95" "" "" "" "" "" 
"NESTED LOOPS"  ""    "" "" ""   "" "" "" "" "" 
"NESTED LOOPS"  ""    "658" "1"   "95" "" "" "" "" "" 
"HASH JOIN"   ""    "493" "1"   "81" "" "" "" ""B"."PRODUCT_ID"=TO_NUMBER("PRC"."SKU") AND "A"."CATEGORY_ID"=SYS_OP_C2C("PRC"."CATEGORY_ID")" "" 
"HASH JOIN"   ""    "369" "2"   "128" "" "" "" ""B"."PROPERTY_ID"=TO_NUMBER("A"."PROPERTY_ID")" ""A"."RANGE_BOTTOM">"A"."RANGE_TOP" AND "A"."RANGE_BOTTOM"<=TO_NUMBER("B"."VALUE") OR "A"."RANGE_BOTTOM"<="A"."RANGE_TOP" AND "A"."RANGE_BOTTOM"<=TO_NUMBER("B"."VALUE") AND "A"."RANGE_TOP">=TO_NUMBER("B"."VALUE")" 
"TABLE ACCESS(FULL) BNET.B_FILTER_CRITERIAS" "ANALYZED" "36" "28" "1148" "" "" "" "" ""ISNUMBER"(TO_CHAR("A"."RANGE_BOTTOM")) IS NOT NULL AND "ISNUMBER"(TO_CHAR("A"."RANGE_TOP")) IS NOT NULL" 
"TABLE ACCESS(FULL) BNET.B_PRODUCT_PROPERTIES" "ANALYZED" "332" "12566" "289018" "" "" "" "" ""ISNUMBER"("B"."VALUE") IS NOT NULL" 
"TABLE ACCESS(FULL) BNET.WLCS_PRODUCT_CATEGORY" "ANALYZED" "124" "129762" "2205954" "" "" "" "" "" 
"INDEX(RANGE SCAN) BNET.WLCS_PROD_VISIBLE_IDX" "ANALYZED" "12" "6208" "" "" "" "" ""PR"."VISIBLE"='1'" "" 
"TABLE ACCESS(BY INDEX ROWID) BNET.WLCS_PRODUCT" "ANALYZED" "164" "1" "14" "" "" "" "" ""B"."PRODUCT_ID"=TO_NUMBER("PR"."SKU")" 
"HASH(GROUP BY)" ""    "637" "1"   "37" "" "" "" "" "" 
"VIEW"    ""    "635" "1"   "37" "" "" "" "" "" 
"HASH(UNIQUE)"  ""    "635" "1"   "91" "" "" "" "" "" 
"HASH JOIN"   ""    "634" "1"   "91" "" "" "" ""B"."PRODUCT_ID"=TO_NUMBER("PRC"."SKU") AND "A"."CATEGORY_ID"=SYS_OP_C2C("PRC"."CATEGORY_ID")" "" 
"NESTED LOOPS"  ""    ""  "" "" "" "" "" "" "" 
"NESTED LOOPS"  ""    "509" "1"   "74" "" "" "" "" "" 
"HASH JOIN"   ""    "345" "1"   "60" "" "" "" ""B"."PROPERTY_ID"=TO_NUMBER("A"."PROPERTY_ID") AND "A"."NAME"="B"."VALUE"" "" 
"TABLE ACCESS(FULL) BNET.B_FILTER_CRITERIAS" "ANALYZED" "35" "11257" "416509" "" "" "" "" "" 
"TABLE ACCESS(FULL) BNET.B_PRODUCT_PROPERTIES" "ANALYZED" "309" "251319" "5780337" "" "" "" "" "" 
"INDEX(RANGE SCAN) BNET.WLCS_PROD_VISIBLE_IDX" "ANALYZED" "12" "6208" "" "" "" "" ""PR"."VISIBLE"='1'" "" 
"TABLE ACCESS(BY INDEX ROWID) BNET.WLCS_PRODUCT" "ANALYZED" "164" "1" "14" "" "" "" "" ""B"."PRODUCT_ID"=TO_NUMBER("PR"."SKU")" 
"TABLE ACCESS(FULL) BNET.WLCS_PRODUCT_CATEGORY" "ANALYZED" "124" "129762" "2205954" "" "" "" "" "" 
+1

不可能告訴,因爲我們不知道存在哪些索引。顯示解釋計劃可能是一個好的開始... –

+2

@Daniel - 即使沒有計劃,我可以看到由於ISNUMBER數值被存儲爲字符串。這是造成表掃描。這就像嘗試通過吃豆類和在顯眼的地方握住zippo來膨脹熱氣球一樣。 – MatBailie

+0

@Lasse V. Karlsen:我不認爲這個問題太局部。雖然有很多細節對其他人不重要,但將數字存儲爲字符串的核心問題適用於大量觀衆。 –

回答

5

一個潛在的海量問題的根源在於您必須使用ISNUMBER。

如果存儲數值爲文本,然後再使用諸如「X < = y」的操作,你實現了很多負面的東西:
- 字符串有它的使用
之前被解析到一個號碼 - 一個字符串的指數有可能不承擔任何resemblence到數字的指數
- 如果該指數是沒用的,你得表掃描,而不是索引搜索

我會強烈建議在尋找存儲值作爲實數,不作爲字符串。不必使用ISNUMBER,不必轉換每個值,因此實際上能夠使用索引的組合可以具有極高的性能優勢。

編輯

您剛纔添加的計劃包括了很多TABLE ACCESS(FULL)實例和幾個那些似乎與數值相關的存儲爲字符串。

0

一些指針:

  1. 請確保您有正確的索引創建了最新
  2. 確保沒有執行隱式類型轉換(pr.visible = '1'看起來像一個這樣的情況下)
1

通過執行計劃步驟和看到瓶頸所在。右上角,這裏有幾件事你可以看看:

  • 不要使用SELECT * - 選擇你需要的特定列。
  • 檢查連接,看看是否有什麼辦法可以讓他們更有效地
  • 儘可能
  • 使用SET NOCOUNT ON在查詢的頂部
  • 確保所有表都正確地導出表代替嵌套查詢索引

你絕對需要看看執行計劃,然後從那裏開始。

+1

Oracle中的SET NOCOUNT ON'? – MatBailie

2

我會通過使更多一點至少縮進可讀性啓動。如果您無法閱讀,則無法對其進行優化。您可以使用匹配表名稱別名更具可讀性,所以filter_criterias成爲fc而不是a。在下面的查詢中,我剛剛修正了一些輪廓,並刪除了多餘的括號。

SELECT 
    * 
FROM 
( 
    SELECT 
     COUNT(*) AS countme, 
     string_value, 
     name, 
     property_id, 
     category_id 
    FROM 
    (
     SELECT DISTINCT 
      a.string_value, 
      a.name, 
      a.property_id, 
      b.product_id, 
      a.category_id 
     FROM 
      filter_criterias a 
      INNER JOIN product_properties b 
       ON a.property_id = b.property_id 
       AND isnumber(b.value) IS NOT NULL 
       AND isnumber(a.range_bottom) IS NOT NULL 
       AND isnumber(a.range_top) IS NOT NULL 
       AND ( 
        a.range_bottom > a.range_top 
        AND b.value >= a.range_bottom 
        OR a.range_bottom <= a.range_top 
        AND b.value >= a.range_bottom 
        AND b.value <=a.range_top 
       ) 
      INNER JOIN PRODUCT_CATEGORY prc 
       ON prc.sku = b.product_id 
       AND prc.category_id = a.category_id 
      INNER JOIN PRODUCT pr 
       ON b.product_id = pr.SKU 
       AND pr.visible = '1' 
    ) 
GROUP BY 
    string_value, 
    name, 
    property_id, 
    category_id 

UNION 

SELECT 
    COUNT(*) AS countme, 
    string_value, 
    name, 
    property_id, 
    category_id 
FROM 
    (
    SELECT DISTINCT 
     a.string_value, 
     a.name  , 
     a.property_id , 
     b.product_id , 
     a.category_id 
    FROM 
     filter_criterias a 
     INNER JOIN product_properties b 
      ON a.property_id = b.property_id 
      AND a.name = b.value 
     INNER JOIN PRODUCT_CATEGORY prc 
      ON prc.sku = b.product_id 
      AND prc.category_id = a.category_id 
     INNER JOIN PRODUCT pr 
      ON b.product_id = pr.SKU 
      AND pr.visible = '1' 
    ) 
GROUP BY 
    string_value, 
    name, 
    property_id, 
    category_id 
ORDER BY 5,4,3,2 

做完之後,您會注意到它包含到由UNION分隔的查詢中。如果這些查詢包含不同的行,則可以使用UNION ALL。只是UNION,將對結果執行另一個DISTINCT,這是較慢的。

此外,除了product_properties b連接中的單個條件(通過將每個子查詢放入WinMerge或類似工具中檢查),這兩個子查詢幾乎相同。所以,也許你可以跳過工會,並將兩個條件結合在一個OR中,儘管你必須記住OR會減慢連接!

當您查看查詢的解釋計劃時,會出現這些類型的問題。查看它以查看哪些連接會給您帶來問題總是很好的。有時它只是一個被遺忘的索引。但重要的是要知道,某些操作會減慢查詢速度,例如在聯接中使用OR(您會這樣做),在不需要時使用DISTINCT並在可以使用UNION ALL的地方使用UNION。

1

你應該看看解釋計劃中的第一件事是基數(估計的行數)。返回1行的最佳計劃通常與返回10億行的最佳計劃大不相同。如果甲骨文的估計顯着錯誤,您​​需要嘗試弄清楚爲什麼這是錯誤的,以及您可以對此做些什麼。

我同意@Dems ISNUMBER可能是您的問題的原因,但由於不同的原因。 Oracle無法準確猜測有多少行將由具有自定義函數的謂詞過濾掉。雖然您可能知道有99.9%的行會通過該過濾器,但Oracle假定只有5%的數據會通過。這導致基數非常低,導致低效的嵌套循環而不是散列連接。

您可以通過在ISNUMBER函數上創建擴展統計信息來爲優化器提供更多有用的信息。這是假設你是在Oracle 11g中,且ISNUMBER是確定的:

select dbms_stats.create_extended_stats(null,'product_properties','(isnumber(value))') from dual; 
select dbms_stats.create_extended_stats(null,'filter_criterias','(isnumber(range_bottom))') from dual; 
select dbms_stats.create_extended_stats(null,'filter_criterias','(isnumber(range_top))') from dual; 

--Must re-gather table stats for the extended stats to work 
begin 
    dbms_stats.gather_Table_stats(user, 'product_properties', no_invalidate => false); 
    dbms_stats.gather_Table_stats(user, 'filter_criterias', no_invalidate => false); 
end; 
/

但是,你的第二個查詢不使用ISNUMBER和仍然爲1的估計基數是最新的表和索引的統計數據?檢查select last_analyzed, table_name from user_tables;。或者,甲骨文可能無法得到正確的估計。像/ * + no_use_nl(a b prc pr)* /這樣的提示可能會有所幫助。

此外,它看起來像你試圖在SQL中實現短路邏輯,但這並不總是奏效。 Oracle不一定從頭到尾處理謂詞,並且您可能會發現有一天您的查詢在計劃更改時失敗。