2012-09-24 99 views
24

我有一個表MYTABLE,它的日期列爲SDATE,它是表的主鍵,並且具有唯一的索引。從表中選擇最小值和最大值比預期的要慢

當我運行此查詢:

SELECT MIN(SDATE) FROM MYTABLE 

它給回答瞬間。同樣的情況適用於:

SELECT MAX(SDATE) FROM MYTABLE 

但是,如果我查詢兩者一起:

SELECT MIN(SDATE), MAX(SDATE) FROM MYTABLE 

它需要更多的時間來執行。我分析了這些計劃,發現在查詢最小或最大值時,它使用INDEX FULL SCAN(MIN/MAX),但是當兩者同時被查詢時,它會進行FULL TABLE SCAN。

爲什麼?

測試數據:

版本11g

create table MYTABLE 
(
    SDATE DATE not null, 
    CELL VARCHAR2(10), 
    data NUMBER 
) 
tablespace CHIPS 
    pctfree 10 
    pctused 40 
    initrans 1 
    maxtrans 255 
    storage 
    (
    initial 64K 
    minextents 1 
    maxextents unlimited 
); 

alter table MYTABLE 
    add constraint PK_SDATE primary key (SDATE) 
    using index 
    tablespace SYSTEM 
    pctfree 10 
    initrans 2 
    maxtrans 255 
    storage 
    (
    initial 64K 
    minextents 1 
    maxextents unlimited 
); 

負載表:

declare 
    i integer; 
begin 
    for i in 0 .. 100000 loop 
    insert into MYTABLE(sdate, cell, data) 
    values(sysdate - i/24, 'T' || i, i);  
    commit; 
    end loop; 
end; 

收集相關統計數據:

begin 
    dbms_stats.gather_table_stats(tabname => 'MYTABLE', ownname => 'SYS'); 
end; 

計劃1:

enter image description here

計劃2:

enter image description here

+0

表中有多少行?統計數據有多新鮮? – APC

+0

我的桌子有近100000行,數據很新;您可以通過創建一個只有一列或兩列的簡單表格輕鬆地重新生成問題,並親自查看結果。 – RGO

+0

查詢的費用是多少?你可以發佈計劃嗎?我認爲這個指數非常分散。 –

回答

11

的索引全掃描只能訪問索引的一側。當你在做

SELECT MIN(SDATE), MAX(SDATE) FROM MYTABLE 

你正在請求訪問雙方。因此,如果您想同時使用最小和最大列值,索引全面掃描不可行。

更詳細的分析你可以找到here

+0

您擁有的鏈接對此行爲沒有提供任何解釋。它沒有回答爲什麼索引不能(或不)被用來找到MIN和MAX。 –

+3

+1提供正確的答案,並鏈接到理查德·富特關於此主題的優秀博客文章。 –

+0

@ypercube ...關於如果它沒有解釋爲什麼索引全面掃描不適用於該查詢,您認爲在我給出的那個鏈接中所說的是什麼?和tnx Rob van Wijk – avi

2

我不得不說,我沒有看到相同的行爲在11.2

如果我建立一個測試情況下,跟隨和更新從10k到1m行響應文森特的評論

set linesize 130 
set pagesize 0 
create table mytable (sdate date); 

Table created. 

insert into mytable 
select sysdate - level 
    from dual 
connect by level <= 1000000; 
commit; 

1000000 rows created. 


Commit complete. 

alter table mytable add constraint pk_mytable primary key (sdate) using index; 

Table altered. 

begin 
dbms_stats.gather_table_stats(user, 'MYTABLE' 
          , estimate_percent => 100 
          , cascade => true 
           ); 
end; 
/

PL/SQL procedure successfully completed. 

然後,在執行您的疑問,我得到幾乎相同尋找解釋計劃(注意不同類型的索引全掃描的)

explain plan for select min(sdate) from mytable; 

Explained. 

select * from table(dbms_xplan.display); 
Plan hash value: 3877058912 

----------------------------------------------------------------------------------------- 
| Id | Operation   | Name | Rows | Bytes | Cost (%CPU)| Time | 
----------------------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT  |  |  1 |  8 |  1 (0)| 00:00:01 | 
| 1 | SORT AGGREGATE  |  |  1 |  8 |  |  | 
| 2 | INDEX FULL SCAN (MIN/MAX)| PK_MYTABLE |  1 |  8 |  1 (0)| 00:00:01 | 
----------------------------------------------------------------------------------------- 

9 rows selected. 

explain plan for select min(sdate), max(sdate) from mytable; 

Explained. 

select * from table(dbms_xplan.display); 
Plan hash value: 3812733167 

------------------------------------------------------------------------------- 
| Id | Operation | Name  | Rows | Bytes | Cost (%CPU)| Time  | 
------------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT |  |  1 |  8 | 252 (0)| 00:00:04 | 
| 1 | SORT AGGREGATE |  |  1 |  8 |  |   | 
| 2 | INDEX FULL SCAN| PK_MYTABLE | 1000K| 7812K| 252 (0)| 00:00:04 | 
------------------------------------------------------------------------------- 

9 rows selected. 

從我的前面的回答引用:

的查詢不使用索引的兩個最常見原因是:

  1. 執行全表掃描更快。
  2. 糟糕的統計數字。

要不是有你沒有的問題張貼我直接的答案是,你還沒有收集在此表中統計的東西,你還沒有足夠高的估計%的收集他們或已使用analyze,不是幫助基於成本的優化器,不像dbms_stats.gather_table_stats

要從文件引用上analyze

對於大多數統計數據的收集,使用DBMS_STATS包, 它可以讓你收集並行統計,收集全球 統計分區對象,和微調您的統計 以其他方式收集。有關DBMS_STATS程序包的更多信息,請參見Oracle數據庫PL/SQL程序包和 類型參考。

使用Analyze語句(而不是DBMS_STATS)統計 收集不相關的基於成本的優化:

+0

我剛剛看到你評論說100k行,但重新做這個數量沒有區別。 – Ben

+0

我在我的文章中提供了數據。我自己也做了一次,並得到了與11.1相同的結果。 – RGO

+0

10k is puny :)嘗試使用1M行,您應該會看到差異 –

6

的解釋計劃不同:單一MINMAX會產生INDEX FULL SCAN (MIN/MAX),而當兩個都存在,你會得到一個INDEX FULL SCANFAST FULL INDEX SCAN

理解上的差異,我們必須尋找一個FULL INDEX SCAN的描述:

在全索引掃描,數據庫以便讀取整個索引。

換句話說,如果索引上的VARCHAR2字段,Oracle將取,將含有例如以字母「A」開始的所有條目,並將由塊中的所有讀取塊索引的第一塊按字母順序輸入,直到最後輸入(「A」到「Z」)。 Oracle可以用這種方式處理,因爲這些條目是在二叉樹索引中排序的。

當您在解釋計劃看INDEX FULL SCAN (MIN/MAX),這是使用的事實,因爲條目的排序,你可以在讀完後,第一個如果你只用MIN感興趣停止優化的結果。如果您只對MAX感興趣,Oracle可以使用相同的訪問路徑,但是這次從最後一個入口開始並從「Z」向後讀取到「A」。

截止目前,FULL INDEX SCAN只有一個方向(向前或向後),並且不能同時從兩端開始,這就是爲什麼當你要求min和max時,你會得到一個效率較低的訪問方法。如其他答案所示,如果查詢需要臨界效率,則可以通過搜索兩個不同查詢中的最小值和最大值來運行自己的優化。

+0

我的第一個想法是爲什麼要進行完整索引掃描?爲什麼不尋求?但是,因爲它在讀取第一個值之後停止,那麼這是有道理的,這只是一個步驟,而索引搜索將是多步走向b樹。感謝解釋的解釋。 – Davos

4

儘量不要在一個查詢中選擇索引的兩個邊緣, 訪問查詢以不同的方式是這樣的:在

select max_date, min_date 
from (select max(sdate) max_date from mytable), 
     (select min(sdate) min_date from mytable) 

將導致優化訪問索引在INDEX_FULL_SCAN(MIN/MAX)嵌套循環(在我們的例子中,兩次)。

enter image description here

+0

我會提出相同的解決方案,但你的答案不會令人驚訝,但是如何讓sql引擎不夠智能來自動解決這個問題,大聲笑 – benjaminz

相關問題