2010-05-05 3 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, пропускаемый сканирование является наиболее полезным, когда первый столбец индекса имеет небольшое количество уникальных значений. Дело в том, что первый индекс этого столбца имеет большое количество уникальных возможностей. Правильно ли я предположить, что сканирование пропусков - это неправильная вещь? Кроме того, какой вид сканирования должен это делать? Должен ли я сделать еще несколько намеков на этот запрос?

EDIT: Я должен также указать, что предложение where where использует столбцы в IDX_MBR_IDENTFN и не содержит столбцов, отличных от того, что находится в этом индексе. Насколько я могу судить, я не пропускаю никаких столбцов.

EDIT 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 может использовать индекс, но для того, чтобы найти правильные строки , он должен читать каждую строку индекса (id 1 до 4) и проверить status.


Update: Следующий индекс может улучшить производительность немного дальше, но вы должны попробовать, если это действительно помогает:

mbr_identfn(identfd_type, identfd_number, entity_active, mbr_idn) 

Это может даже помочь избежать намека.

+0

Дело в том, что 'IDX_MBR_IDENTFN' содержит * все * столбцы в предложении where запроса. И предложение where содержит все столбцы в 'IDX_MBR_IDENTFN'. Так что я ничего не пропускаю ... –

+0

@ Джейсон: Я думаю, что по крайней мере один столбец используется в соединении, и оптимизатор решает сначала запросить эту таблицу, поэтому он должен найти все строки, в которых остальная часть условия выполнены. Также убедитесь, что ваша статистика обновлена: 'dbms_stats.gather_table_stats' (http://psoug.org/reference/dbms_stats.html) –

+0

Ну, я смог ускорить запрос. Мы определенно хотим сначала запросить 'mbr_identfn', поэтому я добавил подсказку:'/* + LEADING (mbr_identfn, mbr) */' –

1

Это поможет, если вы определили, какие столбцы в индексах (PK_CLAIMANT и IDX_MBR_IDENTFN), и в каком порядке.

Я подозреваю, что это проблема с типом данных. Если, например, mbr_identfn.identfd_type является ведущим столбцом индекса и является числовым, но ваш: identfd_type_1 является символьной переменной (или наоборот), он становится непригодным. Однако, если существует несколько типов, то индекс можно использовать с пропуском сканирования.

Вы также указываете предикат «mbr.mbr_idn = mbr_identfn.mbr_idn» как в предложении where, так и в условии соединения.

+0

'PK_CLAIMANT' является' mbr_idn' (на 'mbr') и' IDX_MBR_IDENTFN' является 'mbr_idn',' identfd_type', 'identfd_number' и' entity_active' (на mbr_identfn в этом порядке). Также спасибо за ловушку mbr_idn! –

6

Я бы сфокусировал свой фокус от сканирования пропусков.

Фрагмент tkprof показывает, что ваш первый приоритет должен заключаться в уменьшении количества раз, когда вы выдаете это утверждение. В настоящее время вы выполняете это заявление 9936 раз. И каждое исполнение занимает всего 405/9936 секунд. Довольно быстро. Но нет, если вы выполните его 9936 раз.

Так что это утверждение почти наверняка находится внутри конструкции цикла. На каждой итерации вы предоставляете другой набор входных параметров (: identfd_type_1,: identfd_number_1,: entity_active_1,: ROWNUM_1,: ora_rn_1). Перепишите эту конструкцию цикла, чтобы этот оператор выполнялся один раз для всего набора, и ваша проблема с производительностью, вероятно, будет из прошлого. Если нет, отправьте новый вывод tkprof.

С уважением, Роб.

+0

Я согласен, что это будет лучшей стратегией, но легче сказать, чем сделать по целому ряду архитектурных причин. :-) –

+0

Я никогда не говорил, что это легко :-).Надеюсь, что «архитектурные причины» перевешивают огромную потерю производительности, но я сомневаюсь в этом ... –

+0

+1: Если уменьшить количество запросов - это вариант, обязательно сделайте это. –

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 в качестве ведущих столбцов. Это позволит выполнить диапазон или уникальное сканирование вместо сканирования пропусков.