2011-08-02 35 views
8

我有一個簡單的查詢,該查詢選擇由其他索引列過濾的列之一排序的前200行。混亂就是爲什麼在PL/SQL Developer中的查詢計劃表明,該指數是用來當我選擇的所有行,如:選擇頂部行時使用了錯誤的索引

SELECT * FROM 
(
SELECT * 
FROM cr_proposalsearch ps 
WHERE UPPER(ps.customerpostcode) like 'MK3%' 
ORDER BY ps.ProposalNumber DESC 
) 
WHERE ROWNUM <= 200 

計劃表明,它採用指數CR_PROPOSALSEARCH_I1,這是兩列的索引:PROPOSALNUMBER & UPPER(CUSTOMERNAME),這需要0.985s執行: query with ROWNUM

如果我擺脫ROWNUM條件,該計劃是什麼,我希望它在0.343s執行: query without ROWNUM

index XIF25CR_PROPOSALSEARCH is on CR_PROPOSALSEARCH (UPPER(CUSTOMERPOSTCODE));

怎麼來的?

編輯:我收集了關於cr_proposalsearch表的統計信息,現在兩個查詢計劃都顯示它們使用XIF25CR_PROPOSALSEARCH索引。

+0

兩個不同的查詢,兩個不同的結果集,爲什麼不應該有兩個不同的解釋計劃? – APC

回答

8

包括ROWNUM會改變優化器關於哪個更有效的路徑的計算。

當你做這樣的top-n查詢時,並不一定意味着Oracle會得到所有的行,完全排序它們,然後返回最上面的那個。執行計劃中的COUNT STOPKEY操作表示Oracle只會執行底層操作,直到找到要求的行數爲止。

優化程序計算出完整查詢將獲取並排序77K行。如果它將這個計劃用於top-n查詢,它將不得不做大量的那些行才能找到前200個(它不一定要完全排序它們,因爲它不會關心確切的順序的行通過頂部;但它必須查看所有這些行)。

top-n查詢的計劃使用其他索引來避免必須進行排序。它按順序考慮每一行,檢查它是否與謂詞匹配,如果是,則返回它。當它返回200行時,就完成了。它的計算表明,這對於獲得少量行將更有效率。 (當然,這可能是不對的;你沒有說過這些查詢的相對性能是什麼。)

如果優化器在您要求所有行時選擇此計劃,那麼它必須以降序讀取整個索引,並通過ROWID從表中獲取每行,以便檢查謂詞。這會導致很多額外的I/O並檢查許多不會返回的行。所以在這種情況下,它決定使用customerpostcode上的索引更高效。

如果您逐漸增加要從top-n查詢返回的行數,您可能會在計劃從第一個切換到第二個時發現一個臨界點。僅僅從這兩個計劃的成本來看,我想這可能是大約1200行。

+0

天才,門檻是1166,你怎麼知道這個?我添加了其他索引的時間和細節。如果您按照@Kevin Burton的回答告訴查詢使用索引,則時間總是在0.3s左右 – Tsar

+0

請注意,對於我來說最大的rownum數始終爲200.這意味着XIF25CR_PROPOSALSEARCH永遠不會被使用?有我可以在這裏使用的另一個優化選項嗎? – Tsar

+0

我猜想第一個計劃的成本會線性增長。 6 x 951 = 5,706,非常接近第二個計劃的5512成本。所以我估計臨界點將接近6 x 200 = 1200。 –

1

您似乎沒有完美匹配的索引。索引CR_PROPOSALSEARCH_I1可用於按屬性PROPOSALNUMBER的降序檢索行。可能選擇它是因爲Oracle可以避免檢索所有匹配的行,根據ORDER BY子句對它們進行排序,然後放棄除第一行以外的所有行。

如果沒有ROWNUM條件,Oracle使用XIF25CR_PROPOSALSEARCH索引(您沒有提供任何關於它的細節),因爲它可能對WHERE子句有選擇性。但之後需要對結果進行排序。這可能是基於假設您將檢索所有行的更有效的計劃。

由於任何索引都是一種權衡(一種更適合排序,另一種更適合應用WHERE子句),因此諸如ROWNUM等細節決定Oracle選擇哪種執行計劃。

4

如果您確定您的統計信息是最新的,並且該指數有足夠的選擇性,你可以告訴Oracle使用索引

SELECT * 
FROM (SELECT /*+ index(ps XIF25CR_PROPOSALSEARCH) */ * 
     FROM  cr_proposalsearch ps 
     WHERE UPPER (ps.customerpostcode) LIKE 'MK3%' 
     ORDER BY ps.proposalnumber DESC) 
WHERE ROWNUM <= 200 

(我只推薦這種方法作爲最後的手段)

如果我TKPROF查詢第一個這樣做我會看到它實際上是有多少工作做,

如:索引範圍掃描的成本可能是遙遠

忘了提.... 您應該檢查實際的基數:

SELECT count(*) FROM cr_proposalsearch ps WHERE UPPER(ps.customerpostcode) like 'MK3%' 

,然後把它比作在查詢計劃的基數。

1

這種情況:

WHERE UPPER(ps.customerpostcode) like 'MK3%' 

是不連續的,那就是你不能保持一個有序的範圍了。

因此,有兩種方法來執行這個查詢:

  1. 訂購編號,然後對代碼進行篩選。
  2. 過濾代碼然後按編號順序。

方法1能夠對用戶號碼,讓你線性執行時間索引(頂部100行將被選擇比頂部200更快2倍,條件是數字和代碼不相關)。

方法2能夠使用範圍掃描對代碼進行粗略過濾(範圍條件類似於code >= 'MK3' AND code < 'MK4'),但是,由於編號順序無法保存在組合索引中,因此它需要排序。

排序時間取決於您選擇的頂行數量,但與方法1不同,此依賴性不是線性的(您始終至少需要一次範圍掃描)。

然而,方法2中的過濾條件對於RANGE SCAN具有足夠的選擇性,隨後的排序比整個表的FULL SCAN更有效。

這意味着存在一個臨界點:此條件:ROWNUM <= X存在的X一個值,從而超過此值時該方法2變得更加有效。

更新:

如果你一直在尋找至少3第一符號,你可以創建一個這樣的指標:

SUBSTRING(UPPER(customerpostcode), 1, 3), proposalnumber 

,並在此查詢使用它:

SELECT * 
FROM (
     SELECT * 
     FROM cr_proposalsearch ps 
     WHERE SUBSTRING(UPPER(customerpostcode, 1, 3)) = SUBSTRING(UPPER(:searchquery), 1, 3) 
       AND UPPER(ps.customerpostcode) LIKE UPPER(:searchquery) || '%' 
     ORDER BY 
       proposalNumber DESC 
     ) 
WHERE rownum <= 200 

這樣,編號順序將分開保存,每組代碼先共享3字母會給你更密集的索引掃描。