2012-06-27 31 views
10

我有一個包含150萬行的表。我運行一個查詢,獲取列中具有非重複值的記錄。我正在觀察創建索引後查詢性能下降的行爲。我還使用dbms_stats和100%估計百分比(計算模式) 來收集統計信息,以便Oracle 11g CBO對查詢計劃做出更明智的決定,但它不會改進查詢執行時間。在創建索引並使用dbms_stats計算後,查詢執行速度較慢

SQL> desc tab3; 
Name     Null? Type 
---------------------------------------------- 
COL1       NUMBER(38) 
COL2       VARCHAR2(100) 
COL3       VARCHAR2(36) 
COL4       VARCHAR2(36) 
COL5       VARCHAR2(4000) 
COL6       VARCHAR2(4000) 
MEASURE_0      VARCHAR2(4000) 
MEASURE_1      VARCHAR2(4000) 
MEASURE_2      VARCHAR2(4000) 
MEASURE_3      VARCHAR2(4000) 
MEASURE_4      VARCHAR2(4000) 
MEASURE_5      VARCHAR2(4000) 
MEASURE_6      VARCHAR2(4000) 
MEASURE_7      VARCHAR2(4000) 
MEASURE_8      VARCHAR2(4000) 
MEASURE_9      VARCHAR2(4000) 

measure_0具有40萬個唯一值。

SQL> select count(*) from (select measure_0 from tab3 group by measure_0 having count(*) = 1) abc; 

    COUNT(*) 
---------- 
    403664 

以下是帶執行計劃的查詢,請注意表中沒有索引。

SQL> set autotrace traceonly; 

SQL> SELECT * FROM (
    2  SELECT 
    3    (ROWNUM -1) AS COL1, 
    4    ft.COL1   AS OLD_COL1, 
    5    ft.COL2, 
    6    ft.COL3, 
    7    ft.COL4, 
    8    ft.COL5, 
    9    ft.COL6, 
10    ft.MEASURE_0, 
11    ft.MEASURE_1, 
12    ft.MEASURE_2, 
13    ft.MEASURE_3, 
14    ft.MEASURE_4, 
15    ft.MEASURE_5, 
16    ft.MEASURE_6, 
17    ft.MEASURE_7, 
18    ft.MEASURE_8, 
19    ft.MEASURE_9 
20  FROM tab3 ft 
21  WHERE MEASURE_0 IN 
22  (
23    SELECT MEASURE_0 
24    FROM tab3 
25    GROUP BY MEASURE_0 
26    HAVING COUNT(*) = 1 
27  ) 
28 ) ABC WHERE COL1 >= 0 AND COL1 <=449; 

450 rows selected. 

Elapsed: 00:00:01.90 

Execution Plan 
---------------------------------------------------------- 
Plan hash value: 3115757351 

------------------------------------------------------------------------------------ 
| Id | Operation    | Name  | Rows | Bytes | Cost (%CPU)| Time  | 
------------------------------------------------------------------------------------ 
| 0 | SELECT STATEMENT  |   | 1243 | 28M| 717K (1)| 02:23:29 | 
|* 1 | VIEW     |   | 1243 | 28M| 717K (1)| 02:23:29 | 
| 2 | COUNT     |   |  |  |   |   | 
|* 3 | HASH JOIN   |   | 1243 | 30M| 717K (1)| 02:23:29 | 
| 4 |  VIEW    | VW_NSO_1 | 1686K| 3219M| 6274 (2)| 00:01:16 | 
|* 5 |  FILTER    |   |  |  |   |   | 
| 6 |  HASH GROUP BY  |   |  1 | 3219M| 6274 (2)| 00:01:16 | 
| 7 |  TABLE ACCESS FULL| TAB3  | 1686K| 3219M| 6196 (1)| 00:01:15 | 
| 8 |  TABLE ACCESS FULL | TAB3  | 1686K| 37G| 6211 (1)| 00:01:15 | 
------------------------------------------------------------------------------------ 

Predicate Information (identified by operation id): 
--------------------------------------------------- 

    1 - filter("COL1">=0 AND "COL1"<=449) 
    3 - access("MEASURE_0"="MEASURE_0") 
    5 - filter(COUNT(*)=1) 

Note 
----- 
    - dynamic sampling used for this statement (level=2) 


Statistics 
---------------------------------------------------------- 
     354 recursive calls 
      0 db block gets 
     46518 consistent gets 
     45122 physical reads 
      0 redo size 
     43972 bytes sent via SQL*Net to client 
     715 bytes received via SQL*Net from client 
     31 SQL*Net roundtrips to/from client 
      0 sorts (memory) 
      0 sorts (disk) 
     450 rows processed 

查詢佔用1.90秒。如果我再次運行查詢,則需要1.66秒。爲什麼第一次運行需要更多時間?

爲了加快速度,我在查詢中使用的兩列創建索引。

SQL> create index ind_tab3_orgid on tab3(COL1); 

Index created. 

Elapsed: 00:00:01.68 
SQL> create index ind_tab3_msr_0 on tab3(measure_0); 

Index created. 

Elapsed: 00:00:01.83 

當我解僱查詢後這是第一次花了百日咳秒回來。後續運行將其購買到2.9秒。爲什麼甲骨文在第一輪中需要花費這麼多時間,是熱身還是什麼......讓我感到困惑!

這是計劃時需要2.9秒至

450 rows selected. 

Elapsed: 00:00:02.92 

Execution Plan 
---------------------------------------------------------- 
Plan hash value: 240271480 

------------------------------------------------------------------------------------------------- 
| Id | Operation      | Name   | Rows | Bytes | Cost (%CPU)| Time  | 
------------------------------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT    |    | 1243 | 28M| 711K (1)| 02:22:15 | 
|* 1 | VIEW       |    | 1243 | 28M| 711K (1)| 02:22:15 | 
| 2 | COUNT      |    |  |  |   |   | 
| 3 | NESTED LOOPS    |    |  |  |   |   | 
| 4 |  NESTED LOOPS    |    | 1243 | 30M| 711K (1)| 02:22:15 | 
| 5 |  VIEW      | VW_NSO_1  | 1686K| 3219M| 6274 (2)| 00:01:16 | 
|* 6 |  FILTER     |    |  |  |   |   | 
| 7 |  HASH GROUP BY   |    |  1 | 3219M| 6274 (2)| 00:01:16 | 
| 8 |   TABLE ACCESS FULL  | TAB3   | 1686K| 3219M| 6196 (1)| 00:01:15 | 
|* 9 |  INDEX RANGE SCAN   | IND_TAB3_MSR_0 | 1243 |  |  2 (0)| 00:00:01 | 
| 10 |  TABLE ACCESS BY INDEX ROWID| TAB3   | 1243 | 28M| 44 (0)| 00:00:01 | 
------------------------------------------------------------------------------------------------- 

Predicate Information (identified by operation id): 
--------------------------------------------------- 

    1 - filter("COL1">=0 AND "COL1"<=449) 
    6 - filter(COUNT(*)=1) 
    9 - access("MEASURE_0"="MEASURE_0") 

Note 
----- 
    - dynamic sampling used for this statement (level=2) 


Statistics 
---------------------------------------------------------- 
      0 recursive calls 
      0 db block gets 
    660054 consistent gets 
     22561 physical reads 
      0 redo size 
     44358 bytes sent via SQL*Net to client 
     715 bytes received via SQL*Net from client 
     31 SQL*Net roundtrips to/from client 
      0 sorts (memory) 
      0 sorts (disk) 
     450 rows processed 

我期待的時間比當桌子非索引更低。爲什麼表的索引版本比非索引版本需要更多時間來獲取結果?如果我沒有錯,那就是需要時間的TABLE ACCESS BY INDEX ROWID。我可以強制oracle使用TABLE ACCESS FULL嗎?

然後我收集表上的統計數據,以便CBO使用計算選項改進計劃。所以現在統計數據是準確的。

SQL> EXECUTE dbms_stats.gather_table_stats (ownname=>'EQUBE67DP', tabname=>'TAB3',estimate_percent=>null,cascade=>true); 

PL/SQL procedure successfully completed. 

Elapsed: 00:01:02.47 
SQL> set autotrace off; 
SQL> select COLUMN_NAME,NUM_DISTINCT,SAMPLE_SIZE,HISTOGRAM,LAST_ANALYZED from dba_tab_cols where table_name = 'TAB3' ; 

COLUMN_NAME     NUM_DISTINCT SAMPLE_SIZE HISTOGRAM  LAST_ANALYZED 
------------------------------ ------------ ----------- --------------- --------- 
COL1        1502257  1502257 NONE   27-JUN-12 
COL2          0    NONE   27-JUN-12 
COL3          1  1502257 NONE   27-JUN-12 
COL4          0    NONE   27-JUN-12 
COL5        1502257  1502257 NONE   27-JUN-12 
COL6        1502257  1502257 NONE   27-JUN-12 
MEASURE_0       405609  1502257 HEIGHT BALANCED 27-JUN-12 
MEASURE_1       128570  1502257 NONE   27-JUN-12 
MEASURE_2       1502257  1502257 NONE   27-JUN-12 
MEASURE_3       185657  1502257 NONE   27-JUN-12 
MEASURE_4        901  1502257 NONE   27-JUN-12 
MEASURE_5        17  1502257 NONE   27-JUN-12 
MEASURE_6        2202  1502257 NONE   27-JUN-12 
MEASURE_7        2193  1502257 NONE   27-JUN-12 
MEASURE_8        21  1502257 NONE   27-JUN-12 
MEASURE_9        27263  1502257 NONE   27-JUN-12 

我再次運行查詢

450 rows selected. 

Elapsed: 00:00:02.95 

Execution Plan 
---------------------------------------------------------- 
Plan hash value: 240271480 

------------------------------------------------------------------------------------------------- 
| Id | Operation      | Name   | Rows | Bytes | Cost (%CPU)| Time  | 
------------------------------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT    |    | 31M| 718G| 8046 (2)| 00:01:37 | 
|* 1 | VIEW       |    | 31M| 718G| 8046 (2)| 00:01:37 | 
| 2 | COUNT      |    |  |  |   |   | 
| 3 | NESTED LOOPS    |    |  |  |   |   | 
| 4 |  NESTED LOOPS    |    | 31M| 62G| 8046 (2)| 00:01:37 | 
| 5 |  VIEW      | VW_NSO_1  | 4057 | 7931K| 6263 (2)| 00:01:16 | 
|* 6 |  FILTER     |    |  |  |   |   | 
| 7 |  HASH GROUP BY   |    |  1 | 20285 | 6263 (2)| 00:01:16 | 
| 8 |   TABLE ACCESS FULL  | TAB3   | 1502K| 7335K| 6193 (1)| 00:01:15 | 
|* 9 |  INDEX RANGE SCAN   | IND_TAB3_MSR_0 |  4 |  |  2 (0)| 00:00:01 | 
| 10 |  TABLE ACCESS BY INDEX ROWID| TAB3   | 779K| 75M|  3 (0)| 00:00:01 | 
------------------------------------------------------------------------------------------------- 

Predicate Information (identified by operation id): 
--------------------------------------------------- 

    1 - filter("COL1">=0 AND "COL1"<=449) 
    6 - filter(COUNT(*)=1) 
    9 - access("MEASURE_0"="MEASURE_0") 


Statistics 
---------------------------------------------------------- 
      0 recursive calls 
      0 db block gets 
    660054 consistent gets 
     22561 physical reads 
      0 redo size 
     44358 bytes sent via SQL*Net to client 
     715 bytes received via SQL*Net from client 
     31 SQL*Net roundtrips to/from client 
      0 sorts (memory) 
      0 sorts (disk) 
     450 rows processed 

這一次的查詢中2.9秒回來(有時花了3.9秒太)。

我的目標是儘量減少查詢執行時間。但是在添加索引或計算統計後,查詢時間不斷增加。爲什麼會發生這種情況,即使保持索引,我又該如何改進?

+0

什麼是執行計劃**與您的指數? –

+2

爲什麼查詢首次運行需要更長的時間?首先,它是由autotrace報告的那些254次遞歸SQL調用。所有需要完成的工作來解析查詢的語法,語義(做引用的對象和名稱是否存在,你是否有權限),然後準備執行計劃的工作(哪些索引可用,估計成本各種可能的計劃)。在第一次執行時,有很多繁重的工作正在完成。 – spencer7593

+2

在第二次查詢時,第一次運行需要29秒,這可能是物理讀取...... Oracle正在從磁盤獲取塊並填充緩衝區緩存。 (autotrace會顯示這一點的總結,一個事件10046跟蹤會包含所有等待的詳細信息。) – spencer7593

回答

11

首先,請允許我引用Tom Kyte

只是不停地對自己說一遍又一遍地

「全掃描是不是邪惡的,指標都不好」
「全掃描是不是邪惡,索引不好「
」全掃描不邪惡,索引不好「
」全掃描不邪惡,索引不好「
」全掃描不是e VIL,指標都不好」
‘全掃描是不是邪惡的,指標都不好’

指數將並不總是提高性能,它們不是魔法銀彈(如如果這樣的事情曾經存在:)

現在你問爲什麼它需要更長的索引。答案很簡單:

  • 與全表掃描:一致獲取
  • 用食指:一致獲取

換句話說,甲骨文執行更多的讀取操作與您的索引相比,全表掃描。這是因爲:

  1. 全表掃描(一次多塊)讀取的批量操作,因此,當你從你最終讀取索引讀取讀取大量數據
  2. 有時的有效途徑完全相同的數據塊不止一次。

至於爲何優化器選擇使用這顯然非有效的索引,這是因爲即使esimate_percent=100和全直方圖(您已收集了關於MEASURE_0列),一些數據分佈仍不能可靠地表示通過優化器的簡單分析。特別是,分析儀不能很好地理解交叉列和交叉表的依賴關係。這導致錯誤的估計,導致計劃選擇不當。

編輯:看來CBO的工作假設是不工作的這種自聯接(你上次查詢預計3100萬行,而450只選擇!)。這是很令人費解的,因爲桌子只有1.5米的排。你使用的是什麼版本的Oracle?

我想你會發現,你可以刪除自聯接,從而提高與分析查詢性能:

SELECT * FROM (
    SELECT (ROWNUM -1) AS COL1, ABC.* 
    FROM (
     SELECT 
       ft.COL1 AS OLD_COL1, 
       [...], 
       COUNT(*) OVER (PARTITION BY MEASURE_O) nb_0 
     FROM tab3 ft 
    ) ABC 
    WHERE nb_0 = 1 
     AND ROWNUM - 1 <= 449 
    ) v 
WHERE COL1 >= 0; 

你還問,爲什麼第一次查詢運行需要更多的時間一般。這是因爲有工作緩存。在數據庫級別有SGA,其中所有塊首先從磁盤複製,然後可以多次讀取(第一次查詢塊總是物理讀取)。然後一些系統也有一個獨立的系統緩存,如果它最近被讀取,它將更快地返回數據。

如需進一步閱讀:

+0

謝謝,我肯定會記住Tom Kyte的記錄:)。我正在使用Oracle 11g。我讀了SQL配置文件,不幸的是我不認爲SQL配置文件可以解決我的問題。 SQL配置文件適用於謂詞不變的查詢,我在這裏提到的查詢會改變(最後一個過濾器'COL1> = 0 AND COL1 <= 449'可以是'COL1> = 400000 AND COL1 <= 400449' )。我試圖使用動態採樣提示,但我不確定我是否正確。我在'tab3'的第二個選項中加入了一個動態採樣級別3提示,但沒有任何效果。 – rirhs

+0

我試着運行你寫的查詢,大約需要14秒才能找回。 – rirhs

+0

(1)您可以使用綁定變量:)和(2)您是否嘗試過使用分析的查詢?無論如何,我不認爲在這種情況下索引的使用會有所幫助:您基本上在整個表中使用「MEASURE_0」過濾器進行分頁,該過濾器將選擇將近一半的行。當選擇性很強時,指數非常好,而當過濾器很弱時,指標非常好。 –

3

此代碼如何執行?

SELECT ROWNUM - 1  AS col1 
,  ft.col1   AS old_col1 
,  ft.col2 
,  ft.col3 
,  ft.col4 
,  ft.col5 
,  ft.col6 
,  ft.measure_0 
,  ft.measure_1 
,  ft.measure_2 
,  ft.measure_3 
,  ft.measure_4 
,  ft.measure_5 
,  ft.measure_6 
,  ft.measure_7 
,  ft.measure_8 
,  ft.measure_9 
FROM tab3 ft 
WHERE NOT EXISTS (SELECT NULL 
        FROM tab3 ft_prime 
        WHERE ft_prime.measure_0 = ft.measure_0 
        AND ft_prime.ROWID <> ft.ROWID) 
AND ROWNUM <= 450; 
+0

這個查詢執行得非常好,它在0.04秒內回來,但我在這裏提到的查詢會改變(最後一個過濾器'COL1> = 0 AND COL1 <= 449'可以是像'COL1> = 400000 AND COL1 <= 400449'的任何東西)。因此,我使用指定謂詞的select來包圍查詢,並在1.6秒內得到結果,這比以前的查詢所發生的情況要好得多。謝謝! – rirhs

+0

@shri:您可以讓內部查詢中的上限範圍過濾器'ROWNUM <= 450',這將讓Oracle知道最多需要450行。額外的過濾器'COL1> = 0'當然需要在外部查詢中。請參閱[關於分頁優化的討論](http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:127412348064)。 –