2009-08-13 122 views
2

我將在前言中指出我正在使用Oracle 10g企業版,並且我對Oracle相對較新。幫助優化Oracle查詢

我有一個表與下面的模式:

ID   integer (pk) -- unique index 
PERSON_ID integer (fk) -- b-tree index 
NAME_PART nvarchar  -- b-tree index 
NAME_PART_ID integer (fk) -- bitmap index 

PERSON_ID是一個人記錄的唯一ID的外鍵。 NAME_PART_ID是查找表的外鍵,具有諸如「名字」,「中間名」,「姓氏」等靜態值。表格的要點是分別存儲人名的各個部分。每個人的記錄至少有一個名字。當試圖拉出來的數據,我首先考慮使用連接,就像這樣:

select 
    first_name.person_id, 
    first_name.name_part, 
    middle_name.name_part, 
    last_name.name_part 
from 
    NAME_PARTS first_name 
left join 
    NAME_PARTS middle_name 
    on first_name.person_id = middle_name.person_id 
left join 
    NAME_PARTS last_name 
    on first_name.person_id = last_name.person_id 
where 
    first_name.name_part_id = 1 
    and middle_name.name_part_id = 2 
    and last_name.name_part_id = 3; 

但是表中有幾千萬的記錄,並沒有被用於NAME_PART_ID列的位圖索引。解釋計劃表明優化器正在使用全表掃描和散列連接來檢索數據。

有什麼建議嗎?

編輯:這種方式設計表的原因是因爲數據庫跨越幾種不同的文化,每個人都有不同的約定如何命名個人(例如在一些中東文化,個人通常有第一個名字,然後是他們父親的名字,然後是他父親的名字等)。很難創建一個具有多列的表格來解決所有文化差異。

+0

我會考慮一個不同的設計,其中所有名稱部分都是表中person_id是PK的列。 – 2009-08-13 16:24:21

+0

通過將每個部分放在它自己的表中,您可能會發現加速 - 由於您檢索數據的方式,您總是需要將一個表中的多行連接在一起。看起來很直觀,如果他們在自己的位置,找到每個部分會更容易,而不是讓他們混在一張桌子裏。否則,史蒂夫的答案可能是最好的。 – 2009-08-14 14:23:11

回答

6

既然你基本上做一個全表掃描(就像你的查詢從該表中提取所有數據,不包括不會有第一次被,中間或姓氏部分幾行),您可能要考慮編寫查詢,以便它只是返回一個稍微不同的格式的數據,如:

SELECT person_id 
     , name_part_id 
     , name_part 
    FROM NAME_PART 
    WHERE name_part_id IN (1, 2, 3) 
ORDER BY person_id 
     , name_part_id; 

當然,你會用3行而不是一個每個名稱結束,但它可能爲了讓你的客戶端代碼一起滾動,這些都是微不足道的。您還可以通過使用解碼,GROUP BY和max滾動3行成一個:

SELECT person_id 
     , max(decode(name_part_id, 1, name_part, null)) first 
     , max(decode(name_part_id, 2, name_part, null)) middle 
     , max(decode(name_part_id, 3, name_part, null)) last 
    FROM NAME_PART 
    WHERE name_part_id IN (1, 2, 3) 
GROUP BY person_id 
ORDER BY person_id; 

這將產生到原來的查詢結果相同。兩種版本都只會掃描一次表格(用一種排序方式),而不是處理3路聯接。如果您將表格設置爲person_id索引上的索引組織表格,則可以保存排序步驟。

我跑了一臺測試用56150人,這裏的結果的概要:

原始查詢:

Execution Plan 
---------------------------------------------------------- 

------------------------------------------------------------------------------ 
| Id | Operation   | Name  | Rows | Bytes |TempSpc| Cost (%CPU)| 
------------------------------------------------------------------------------ 
| 0 | SELECT STATEMENT |   | 113K| 11M|  | 1364 (2)| 
|* 1 | HASH JOIN   |   | 113K| 11M| 2528K| 1364 (2)| 
|* 2 | TABLE ACCESS FULL | NAME_PART | 56150 | 1864K|  | 229 (3)| 
|* 3 | HASH JOIN   |   | 79792 | 5298K| 2528K| 706 (2)| 
|* 4 | TABLE ACCESS FULL| NAME_PART | 56150 | 1864K|  | 229 (3)| 
|* 5 | TABLE ACCESS FULL| NAME_PART | 56150 | 1864K|  | 229 (3)| 
------------------------------------------------------------------------------ 

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

    1 - access("FIRST_NAME"."PERSON_ID"="LAST_NAME"."PERSON_ID") 
    2 - filter("LAST_NAME"."NAME_PART_ID"=3) 
    3 - access("FIRST_NAME"."PERSON_ID"="MIDDLE_NAME"."PERSON_ID") 
    4 - filter("FIRST_NAME"."NAME_PART_ID"=1) 
    5 - filter("MIDDLE_NAME"."NAME_PART_ID"=2) 

Statistics 
---------------------------------------------------------- 
      0 recursive calls 
      0 db block gets 
     6740 consistent gets 
      0 physical reads 
      0 redo size 
    5298174 bytes sent via SQL*Net to client 
     26435 bytes received via SQL*Net from client 
     3745 SQL*Net roundtrips to/from client 
      0 sorts (memory) 
      0 sorts (disk) 
     56150 rows processed 

我查詢#1(3行/人):

Execution Plan 
---------------------------------------------------------- 

----------------------------------------------------------------------------- 
| Id | Operation   | Name  | Rows | Bytes |TempSpc| Cost (%CPU)| 
----------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT |   | 168K| 5593K|  | 1776 (2)| 
| 1 | SORT ORDER BY  |   | 168K| 5593K| 14M| 1776 (2)| 
|* 2 | TABLE ACCESS FULL| NAME_PART | 168K| 5593K|  | 230 (3)| 
----------------------------------------------------------------------------- 

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

    2 - filter("NAME_PART_ID"=1 OR "NAME_PART_ID"=2 OR "NAME_PART_ID"=3) 

Statistics 
---------------------------------------------------------- 
      1 recursive calls 
      0 db block gets 
     1005 consistent gets 
      0 physical reads 
      0 redo size 
    3799794 bytes sent via SQL*Net to client 
     78837 bytes received via SQL*Net from client 
     11231 SQL*Net roundtrips to/from client 
      1 sorts (memory) 
      0 sorts (disk) 
    168450 rows processed 

我查詢#2(1行/人):

Execution Plan 
---------------------------------------------------------- 

----------------------------------------------------------------------------- 
| Id | Operation   | Name  | Rows | Bytes |TempSpc| Cost (%CPU)| 
----------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT |   | 56150 | 1864K|  | 1115 (3)| 
| 1 | SORT GROUP BY  |   | 56150 | 1864K| 9728K| 1115 (3)| 
|* 2 | TABLE ACCESS FULL| NAME_PART | 168K| 5593K|  | 230 (3)| 
----------------------------------------------------------------------------- 

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

    2 - filter("NAME_PART_ID"=1 OR "NAME_PART_ID"=2 OR "NAME_PART_ID"=3) 

Statistics 
---------------------------------------------------------- 
      1 recursive calls 
      0 db block gets 
     1005 consistent gets 
      0 physical reads 
      0 redo size 
    5298159 bytes sent via SQL*Net to client 
     26435 bytes received via SQL*Net from client 
     3745 SQL*Net roundtrips to/from client 
      1 sorts (memory) 
      0 sorts (disk) 
     56150 rows processed 

事實證明,你可以擠一點仍然較快;我試圖通過添加索引提示來強制使用person_id索引來避免排序。我好不容易纔收工另外10%,但它仍然看起來像它的排序:

SELECT /*+ index(name_part,NAME_PART_person_id) */ person_id 
     , max(decode(name_part_id, 1, name_part)) first 
     , max(decode(name_part_id, 2, name_part)) middle 
     , max(decode(name_part_id, 3, name_part)) last 
    FROM name_part 
    WHERE name_part_id IN (1, 2, 3) 
GROUP BY person_id 
ORDER BY person_id; 

Execution Plan 
---------------------------------------------------------- 

----------------------------------------------------------------------------------------------------- 
| Id | Operation      | Name     | Rows | Bytes |TempSpc| Cost (%CPU)| 
----------------------------------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT    |      | 56150 | 1864K|  | 3385 (1)| 
| 1 | SORT GROUP BY     |      | 56150 | 1864K| 9728K| 3385 (1)| 
| 2 | INLIST ITERATOR    |      |  |  |  |   | 
| 3 | TABLE ACCESS BY INDEX ROWID | NAME_PART    | 168K| 5593K|  | 2500 (1)| 
| 4 |  BITMAP CONVERSION TO ROWIDS|      |  |  |  |   | 
|* 5 |  BITMAP INDEX SINGLE VALUE | NAME_PART_NAME_PART_ID|  |  |  |   | 
----------------------------------------------------------------------------------------------------- 

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

    5 - access("NAME_PART_ID"=1 OR "NAME_PART_ID"=2 OR "NAME_PART_ID"=3) 

Statistics 
---------------------------------------------------------- 
      1 recursive calls 
      0 db block gets 
     971 consistent gets 
      0 physical reads 
      0 redo size 
    5298159 bytes sent via SQL*Net to client 
     26435 bytes received via SQL*Net from client 
     3745 SQL*Net roundtrips to/from client 
      1 sorts (memory) 
      0 sorts (disk) 
     56150 rows processed 

然而,上述計劃都是基於你是從整個表中選擇的假設。如果您約束基礎上爲person_id的結果(例如,55968和56000之間PERSON_ID),事實證明你與散列連接原來的查詢是最快的(27與106一致獲取了我指定的約束)。

在第三方面,如果上面的查詢用於填充使用光標滾動結果集的GUI(例如,您只會看到結果集的前N行 - 最初由此轉載添加一個「和行數」),我的版本的查詢再次變得快速 - 非常快(4個一致的獲取與417)。

故事的寓意在於,它確實取決於您如何訪問數據。對整個結果集運行良好的查詢在針對不同子集應用時可能會更糟糕。

+0

+1,最後一段如果沒有別的! – DCookie 2009-08-13 22:42:12

+0

+1並檢查綠色。感謝您的出色答案和見解! – 2009-08-14 03:37:08

5

由於您不以任何方式過濾您的表格,因此優化程序可能是正確的,HASH JOIN是加入未過濾表格的最佳方式。

在這種情況下,位圖索引對你無能爲力。

這對於在多個低基數列上製作ORAND非常有用,不適用於單列上的純過濾。

爲此,全表掃描幾乎總是更好。

請注意,這不是最好的設計。我寧願將列first_name,last_namemiddle_name添加到person中,在每個列上建立一個索引並使它們可爲NULL。

在這種情況下,您的表格與您設計中的表格相同,但沒有表格。

索引名稱和rowid與表格一樣好,並且在rowid上的聯接效率更高。

更新:

自己正在使用父親的名字作爲個人名稱的一部分的文化中的一員,我可以說,使用三個字段是足以滿足大多數情況下。

一個字段的姓氏,一個字段給定的名字和一個字段之間的所有內容(沒有進一步的專業化)是一個相當體面的方式來處理名稱。

只要依靠你的用戶。在現代世界中,幾乎每個人都知道如何使自己的名字適合這種模式。

例如:

Family name: Picasso 
Given name: Pablo 
Middle name: Diego José Francisco de Paula Juan Nepomuceno María de los Remedios Cipriano de la Santísima Trinidad Ruiz y 

P. S.你知道,親密的朋友只是叫他PABLO~1

+0

+1:第一句話幾乎說明了一切。 – DCookie 2009-08-13 16:47:25

+0

感謝您的建議。我想過重新設計表格模式,在我原來的問題中增加了更多細節來解釋爲什麼我沒有選擇這個模式。 – 2009-08-13 17:00:43

+1

如果可以的話,我會再給你一份+1。 :-D – DCookie 2009-08-13 17:57:30

-2

,如果你有幾百萬行的,並且有3個不同的值索引(爲「名」,「中間名」,「姓」),該指數將被忽略,因爲它更容易掃描比使用所有行該指數。一個索引需要有一個廣泛的值分佈以供任何使用。

+2

-1:對於位圖索引(name_part_id是),這不一定是正確的。 http://www.oracle.com/technology/pub/articles/sharma_indexes.html上有關於位圖與btree索引決策的相當不錯的白皮書。 – DCookie 2009-08-13 16:40:54

+0

對於此查詢或位圖似乎是正確的。位圖索引在此查詢中可以保存的任何魔術都沒有。 – 2009-08-14 13:35:54

+0

你答案的最後一句話是不真實的,這是我失望的原因。 – DCookie 2009-08-14 14:56:42

0

好吧,首先,在你的第二個左連接看起來你正在使用「first_token」和「last_token」而不是「first_name」和「last_name」。我猜這只是一個剪切和粘貼錯誤,但?

+0

你是對的,這是一個複製和過去的錯誤。模式創建者實際上將其命名爲token_1,token_2等,但我正在重命名以使問題更清晰。 – 2009-08-13 16:56:18

3

對於Quassnoi的答案+1,但讓我補充一點,儘管在這種情況下它不會有幫助(因爲您正在檢索這麼多記錄),但此表可能會很好地存儲在person_id上的散列簇中,以便記錄因爲一個人在同一個街區居住。用於檢索幾個比堆錶快得多的記錄。

+0

對這些記錄進行聚類會減慢'DML',但肯定會加速通過'id'檢索人名。不知道在'@ op'情況下是否可以減緩'DML',但是這個選項當然值得考慮。 +1 – Quassnoi 2009-08-13 19:35:51

+0

這確實是一個較慢的插入(雖然有bitma索引讓我不知道DML裏面發生了什麼),但我總是喜歡考慮通常我們修改日期的次數比我們閱讀它的次數少得多。由於索引或集羣等原因,DML開銷應該與更好的選擇性能相平衡。 – 2009-08-13 21:50:01