2010-05-05 76 views
1

下面是這是非常緩慢運行查詢的TKPROF輸出(警告:這是長:-)):爲什麼Oracle對此查詢使用跳過掃描?

SELECT mbr_comment_idn, mbr_crt_dt, mbr_data_source, mbr_dol_bl_rmo_ind, mbr_dxcg_ctl_member, mbr_employment_start_dt, mbr_employment_term_dt, mbr_entity_active, mbr_ethnicity_idn, mbr_general_health_status_code, mbr_hand_dominant_code, mbr_hgt_feet, mbr_hgt_inches, mbr_highest_edu_level, mbr_insd_addr_idn, mbr_insd_alt_id, mbr_insd_name, mbr_insd_ssn_tin, mbr_is_smoker, mbr_is_vip, mbr_lmbr_first_name, mbr_lmbr_last_name, mbr_marital_status_cd, mbr_mbr_birth_dt, mbr_mbr_death_dt, mbr_mbr_expired, mbr_mbr_first_name, mbr_mbr_gender_cd, mbr_mbr_idn, mbr_mbr_ins_type, mbr_mbr_isreadonly, mbr_mbr_last_name, mbr_mbr_middle_name, mbr_mbr_name, mbr_mbr_status_idn, mbr_mpi_id, mbr_preferred_am_pm, mbr_preferred_time, mbr_prv_innetwork, mbr_rep_addr_idn, mbr_rep_name, mbr_rp_mbr_id, mbr_same_mbr_ins, mbr_special_needs_cd, mbr_timezone, mbr_upd_dt, mbr_user_idn, mbr_wgt, mbr_work_status_idn 
FROM (SELECT /*+ FIRST_ROWS(1) */ mbr_comment_idn, mbr_crt_dt, mbr_data_source, mbr_dol_bl_rmo_ind, mbr_dxcg_ctl_member, mbr_employment_start_dt, mbr_employment_term_dt, mbr_entity_active, mbr_ethnicity_idn, mbr_general_health_status_code, mbr_hand_dominant_code, mbr_hgt_feet, mbr_hgt_inches, mbr_highest_edu_level, mbr_insd_addr_idn, mbr_insd_alt_id, mbr_insd_name, mbr_insd_ssn_tin, mbr_is_smoker, mbr_is_vip, mbr_lmbr_first_name, mbr_lmbr_last_name, mbr_marital_status_cd, mbr_mbr_birth_dt, mbr_mbr_death_dt, mbr_mbr_expired, mbr_mbr_first_name, mbr_mbr_gender_cd, mbr_mbr_idn, mbr_mbr_ins_type, mbr_mbr_isreadonly, mbr_mbr_last_name, mbr_mbr_middle_name, mbr_mbr_name, mbr_mbr_status_idn, mbr_mpi_id, mbr_preferred_am_pm, mbr_preferred_time, mbr_prv_innetwork, mbr_rep_addr_idn, mbr_rep_name, mbr_rp_mbr_id, mbr_same_mbr_ins, mbr_special_needs_cd, mbr_timezone, mbr_upd_dt, mbr_user_idn, mbr_wgt, mbr_work_status_idn, ROWNUM AS ora_rn 
FROM (SELECT mbr.comment_idn AS mbr_comment_idn, mbr.crt_dt AS mbr_crt_dt, mbr.data_source AS mbr_data_source, mbr.dol_bl_rmo_ind AS mbr_dol_bl_rmo_ind, mbr.dxcg_ctl_member AS mbr_dxcg_ctl_member, mbr.employment_start_dt AS mbr_employment_start_dt, mbr.employment_term_dt AS mbr_employment_term_dt, mbr.entity_active AS mbr_entity_active, mbr.ethnicity_idn AS mbr_ethnicity_idn, mbr.general_health_status_code AS mbr_general_health_status_code, mbr.hand_dominant_code AS mbr_hand_dominant_code, mbr.hgt_feet AS mbr_hgt_feet, mbr.hgt_inches AS mbr_hgt_inches, mbr.highest_edu_level AS mbr_highest_edu_level, mbr.insd_addr_idn AS mbr_insd_addr_idn, mbr.insd_alt_id AS mbr_insd_alt_id, mbr.insd_name AS mbr_insd_name, mbr.insd_ssn_tin AS mbr_insd_ssn_tin, mbr.is_smoker AS mbr_is_smoker, mbr.is_vip AS mbr_is_vip, mbr.lmbr_first_name AS mbr_lmbr_first_name, mbr.lmbr_last_name AS mbr_lmbr_last_name, mbr.marital_status_cd AS mbr_marital_status_cd, mbr.mbr_birth_dt AS mbr_mbr_birth_dt, mbr.mbr_death_dt AS mbr_mbr_death_dt, mbr.mbr_expired AS mbr_mbr_expired, mbr.mbr_first_name AS mbr_mbr_first_name, mbr.mbr_gender_cd AS mbr_mbr_gender_cd, mbr.mbr_idn AS mbr_mbr_idn, mbr.mbr_ins_type AS mbr_mbr_ins_type, mbr.mbr_isreadonly AS mbr_mbr_isreadonly, mbr.mbr_last_name AS mbr_mbr_last_name, mbr.mbr_middle_name AS mbr_mbr_middle_name, mbr.mbr_name AS mbr_mbr_name, mbr.mbr_status_idn AS mbr_mbr_status_idn, mbr.mpi_id AS mbr_mpi_id, mbr.preferred_am_pm AS mbr_preferred_am_pm, mbr.preferred_time AS mbr_preferred_time, mbr.prv_innetwork AS mbr_prv_innetwork, mbr.rep_addr_idn AS mbr_rep_addr_idn, mbr.rep_name AS mbr_rep_name, mbr.rp_mbr_id AS mbr_rp_mbr_id, mbr.same_mbr_ins AS mbr_same_mbr_ins, mbr.special_needs_cd AS mbr_special_needs_cd, mbr.timezone AS mbr_timezone, mbr.upd_dt AS mbr_upd_dt, mbr.user_idn AS mbr_user_idn, mbr.wgt AS mbr_wgt, mbr.work_status_idn AS mbr_work_status_idn 
FROM mbr JOIN mbr_identfn ON mbr.mbr_idn = mbr_identfn.mbr_idn 
WHERE mbr_identfn.mbr_idn = mbr.mbr_idn AND mbr_identfn.identfd_type = :identfd_type_1 AND mbr_identfn.identfd_number = :identfd_number_1 AND mbr_identfn.entity_active = :entity_active_1) 
WHERE ROWNUM <= :ROWNUM_1) 
WHERE ora_rn > :ora_rn_1 

call  count  cpu elapsed  disk  query current  rows 
------- ------ -------- ---------- ---------- ---------- ---------- ---------- 
Parse  9936  0.46  0.49   0   0   0   0 
Execute 9936  0.60  0.59   0   0   0   0 
Fetch  9936 329.87  404.00   0 136966922   0   0 
------- ------ -------- ---------- ---------- ---------- ---------- ---------- 
total 29808 330.94  405.09   0 136966922   0   0 

Misses in library cache during parse: 0 
Optimizer mode: FIRST_ROWS 
Parsing user id: 36 (JIVA_DEV) 

Rows  Row Source Operation 
------- --------------------------------------------------- 
     0 VIEW (cr=102 pr=0 pw=0 time=2180 us) 
     0 COUNT STOPKEY (cr=102 pr=0 pw=0 time=2163 us) 
     0 NESTED LOOPS (cr=102 pr=0 pw=0 time=2152 us) 
     0  INDEX SKIP SCAN IDX_MBR_IDENTFN (cr=102 pr=0 pw=0 time=2140 us)(object id 341053) 
     0  TABLE ACCESS BY INDEX ROWID MBR (cr=0 pr=0 pw=0 time=0 us) 
     0  INDEX UNIQUE SCAN PK_CLAIMANT (cr=0 pr=0 pw=0 time=0 us)(object id 334044) 


Rows  Execution Plan 
------- --------------------------------------------------- 
     0 SELECT STATEMENT MODE: HINT: FIRST_ROWS 
     0 VIEW 
     0 COUNT (STOPKEY) 
     0  NESTED LOOPS 
     0  INDEX MODE: ANALYZED (SKIP SCAN) OF 'IDX_MBR_IDENTFN' 
       (INDEX (UNIQUE)) 
     0  TABLE ACCESS MODE: ANALYZED (BY INDEX ROWID) OF 'MBR' 
       (TABLE) 
     0  INDEX MODE: ANALYZED (UNIQUE SCAN) OF 'PK_CLAIMANT' 
        (INDEX (UNIQUE)) 

******************************************************************************** 

根據我的Oracle的documentation of skip scans的閱讀,跳躍掃描是最有用的,當第一列的索引具有少量的唯一值。問題是這個專欄的第一個索引有大量的唯一標識符。那麼我是否認爲跳過掃描在這裏是錯誤的?另外,它應該做什麼樣的?我應該爲這個查詢做更多的提示嗎?

編輯:我還要指出的是,查詢的WHERE子句中使用的列IDX_MBR_IDENTFN,比什麼是在指數沒有其他列。所以據我所知,我沒有跳過任何專欄。

編輯2:我已經做了一些事情來加快這個查詢。首先,我刪除了分頁。事實證明,這個查詢只返回一行。其次,我添加了一個LEADING提示,以確保表格按照正確的順序被查詢。第三,我刪除了重複的mbr_idn謂詞。最後,我讓IDX_MBR_IDENTFN獨一無二。總之,這使得大幅度的性能提升(儘管它仍然是我跑的最昂貴的查詢):

SELECT /*+ LEADING (mbr_identfn, mbr) */ mbr.comment_idn AS mbr_comment_idn, mbr.crt_dt AS mbr_crt_dt, mbr.data_source AS mbr_data_source, mbr.dol_bl_rmo_ind AS mbr_dol_bl_rmo_ind, mbr.dxcg_ctl_member AS mbr_dxcg_ctl_member, mbr.employment_start_dt AS mbr_employment_start_dt, mbr.employment_term_dt AS mbr_employment_term_dt, mbr.entity_active AS mbr_entity_active, mbr.ethnicity_idn AS mbr_ethnicity_idn, mbr.general_health_status_code AS mbr_general_health_status_code, mbr.hand_dominant_code AS mbr_hand_dominant_code, mbr.hgt_feet AS mbr_hgt_feet, mbr.hgt_inches AS mbr_hgt_inches, mbr.highest_edu_level AS mbr_highest_edu_level, mbr.insd_addr_idn AS mbr_insd_addr_idn, mbr.insd_alt_id AS mbr_insd_alt_id, mbr.insd_name AS mbr_insd_name, mbr.insd_ssn_tin AS mbr_insd_ssn_tin, mbr.is_smoker AS mbr_is_smoker, mbr.is_vip AS mbr_is_vip, mbr.lmbr_first_name AS mbr_lmbr_first_name, mbr.lmbr_last_name AS mbr_lmbr_last_name, mbr.marital_status_cd AS mbr_marital_status_cd, mbr.mbr_birth_dt AS mbr_mbr_birth_dt, mbr.mbr_death_dt AS mbr_mbr_death_dt, mbr.mbr_expired AS mbr_mbr_expired, mbr.mbr_first_name AS mbr_mbr_first_name, mbr.mbr_gender_cd AS mbr_mbr_gender_cd, mbr.mbr_idn AS mbr_mbr_idn, mbr.mbr_ins_type AS mbr_mbr_ins_type, mbr.mbr_isreadonly AS mbr_mbr_isreadonly, mbr.mbr_last_name AS mbr_mbr_last_name, mbr.mbr_middle_name AS mbr_mbr_middle_name, mbr.mbr_name AS mbr_mbr_name, mbr.mbr_status_idn AS mbr_mbr_status_idn, mbr.mpi_id AS mbr_mpi_id, mbr.preferred_am_pm AS mbr_preferred_am_pm, mbr.preferred_time AS mbr_preferred_time, mbr.prv_innetwork AS mbr_prv_innetwork, mbr.rep_addr_idn AS mbr_rep_addr_idn, mbr.rep_name AS mbr_rep_name, mbr.rp_mbr_id AS mbr_rp_mbr_id, mbr.same_mbr_ins AS mbr_same_mbr_ins, mbr.special_needs_cd AS mbr_special_needs_cd, mbr.timezone AS mbr_timezone, mbr.upd_dt AS mbr_upd_dt, mbr.user_idn AS mbr_user_idn, mbr.wgt AS mbr_wgt, mbr.work_status_idn AS mbr_work_status_idn 
FROM mbr JOIN mbr_identfn ON mbr.mbr_idn = mbr_identfn.mbr_idn 
WHERE mbr_identfn.identfd_type = :identfd_type_1 AND mbr_identfn.identfd_number = :identfd_number_1 AND mbr_identfn.entity_active = :entity_active_1 

call  count  cpu elapsed  disk  query current  rows 
------- ------ -------- ---------- ---------- ---------- ---------- ---------- 
Parse 10102  0.45  0.42   0   0   0   0 
Execute 10102  0.44  0.52   0   0   0   0 
Fetch 10102  1.60  1.81   0  218121   0   0 
------- ------ -------- ---------- ---------- ---------- ---------- ---------- 
total 30306  2.50  2.75   0  218121   0   0 

Misses in library cache during parse: 0 
Optimizer mode: ALL_ROWS 
Parsing user id: 36 (JIVA_DEV) 

Rows  Row Source Operation 
------- --------------------------------------------------- 
     0 NESTED LOOPS (cr=3 pr=0 pw=0 time=96 us) 
     0 TABLE ACCESS BY INDEX ROWID MBR_IDENTFN (cr=3 pr=0 pw=0 time=88 us) 
     0 INDEX UNIQUE SCAN UK_CLM_IDFN (cr=3 pr=0 pw=0 time=77 us)(object id 334118) 
     0 TABLE ACCESS BY INDEX ROWID MBR (cr=0 pr=0 pw=0 time=0 us) 
     0 INDEX UNIQUE SCAN PK_CLAIMANT (cr=0 pr=0 pw=0 time=0 us)(object id 334044) 


Rows  Execution Plan 
------- --------------------------------------------------- 
     0 SELECT STATEMENT MODE: ALL_ROWS 
     0 NESTED LOOPS 
     0 TABLE ACCESS MODE: ANALYZED (BY INDEX ROWID) OF 
       'MBR_IDENTFN' (TABLE) 
     0  INDEX MODE: ANALYZED (UNIQUE SCAN) OF 'UK_CLM_IDFN' (INDEX 
       (UNIQUE)) 
     0 TABLE ACCESS MODE: ANALYZED (BY INDEX ROWID) OF 'MBR' (TABLE) 

     0  INDEX MODE: ANALYZED (UNIQUE SCAN) OF 'PK_CLAIMANT' (INDEX 
       (UNIQUE)) 

回答

5

索引跳躍式掃描方式,該指數的第一列被忽略。這會降低性能,因爲Oracle已經讀取了第一列的每一項,並檢查第二列(或第三列)是否是您搜索的列。這通常比全表掃描更快(取決於您的查詢),但比指數範圍掃描的慢。

嘗試在作爲IDX_MBR_IDENTFN的一部分的列上創建一個單獨的索引並將其用於查詢中。


例如,如果your_table看起來是這樣的:

id status 
1 0 
2 0 
3 0 
4 1 

和你有(id, status)一個複合索引,查詢Select * From your_table Where status = 1很可能使用索引,但爲了找到正確的行,它必須讀取索引的每一行(編號爲14)並檢查status


更新:以下指數可能有點進一步提高性能,但你必須嘗試,如果它確實有助於:

mbr_identfn(identfd_type, identfd_number, entity_active, mbr_idn) 

這甚至有助於避免提示。

+0

事情是'IDX_MBR_IDENTFN'包含查詢的where子句中的* all *列。 where子句包含'IDX_MBR_IDENTFN'中的所有列。所以我並沒有真正跳過任何東西...... – 2010-05-05 21:32:50

+0

@Jason:我想至少有一列用於連接,優化器決定先查詢該表,因此它必須找到所有行,其餘的條件得到滿足。還要確保你的統計數據是最新的:'dbms_stats.gather_table_stats'(http://psoug.org/reference/dbms_stats.html) – 2010-05-05 21:40:14

+0

好吧,我能夠加快查詢的速度。我們一定要首先查詢'mbr_identfn',所以我添加了一個提示:'/ * + LEADING(mbr_identfn,mbr)* /' – 2010-05-06 14:14:32

1

如果您確定了索引(PK_CLAIMANT和IDX_MBR_IDENTFN)中的列是什麼,以及按照什麼順序,這將有所幫助。

我懷疑這是一個datetype問題。例如,如果mbr_identfn.identfd_type是索引的前導列並且是數字,但是:identfd_type_1是字符變量(反之亦然),則它變得不可用。但是,如果類型很少,那麼索引可以用於跳過掃描。

您還可以在where子句和join子句中指定謂詞「mbr.mbr_idn = mbr_identfn.mbr_idn」。

+0

'PK_CLAIMANT'是'mbr_idn'(在'mbr'上),'IDX_MBR_IDENTFN'是'mbr_idn',''identfd_type'','identfd_number'和'entity_active'(在mbr_identfn上的順序)。同時感謝您關注mbr_idn問題! – 2010-05-06 13:43:47

6

我會把焦點從跳躍掃描中移開。

tkprof片段顯示您的首要任務應該是減少發出此語句的次數。目前您正在執行該語句9936次。每次執行只需405/9936秒。合理快速。但是,如果你執行它9936次,則不是。

所以這個聲明幾乎可以肯定的在一個循環結構中。在每次迭代中,您都提供了一個不同的輸入參數集(:identfd_type_1,:identfd_number_1,:entity_active_1,:ROWNUM_1,:ora_rn_1)。重寫這個循環結構來讓這個語句對整個集合執行一次,而你的性能問題可能是過去的事情。如果沒有,請發佈新的tkprof輸出。

Regards, Rob。

+0

我同意這將是一個更好的策略,但由於各種體系結構原因,說起來容易做起來難。 :-) – 2010-05-06 13:32:54

+0

我從來沒有說過這很容易:-)。我希望「架構上的原因」超過了巨大的性能損失,但我懷疑它...... – 2010-05-06 14:33:53

+0

+1:如果減少查詢數量是一個選項,一定要這樣做。 – 2010-05-06 15:00:10

1

爲了解釋跳躍掃描...這似乎是查詢的相關謂語部分:

WHERE mbr_identfn.mbr_idn = mbr.mbr_idn 
    AND mbr_identfn.identfd_type = :identfd_type_1 
    AND mbr_identfn.identfd_number = :identfd_number_1 
    AND mbr_identfn.entity_active = :entity_active_1 

如果執行與MBR_IDENTFN開始,那麼我們還沒有對MBR_IDN的值在索引中查找;這意味着我們無法進行獨特或範圍掃描。但是我們爲索引的其他三列提供了值(作爲綁定變量),因此我們可以執行跳過掃描。 Oracle選擇這樣做是爲了避免訪問基本表,這似乎是合理的。

MBR_IDENTFN的主鍵是什麼?單是MBR_IDN?

我想你應該在MBR_IDENTFN上有一個單獨的索引,IDENTFD_TYPE,IDENTFD_NUMBER和ENTITY_ACTIVE中的一些或全部作爲主要的列。這將允許執行範圍或唯一掃描而不是跳過掃描。

相關問題