![]() |
Использование нескольких индексов для выполнения запросаИсточник: ln
В этом выпуске мы рассмотрим некоторые особенности использования индексов в Oracle. Максимальное количество индексов для запросаКогда в запросе есть два и более условия с проверкой на равенство, можно использовать несколько индексов. Oracle будет "объединять" ( сливать , merge) индексы во время выполнения, возвращая строки, которые найдены по обоим индексам. Например, при выполнении запроса: SELECT ename FROM emp WHERE deptno=20 AND job='manager' может быть выполнено слияние следующих индексов:
А как реально сервер осуществляет слияние индексов? Что лучше: конкатенированный индекс по столбцам job и deptno или отдельные индексы по столбцам job и deptno, которые могут быть объединены? Правда ли, что Oracle использует для запроса не более 5 индексов? Сколько максимум индексов по таблице можно создать? Ответ Тома КайтаИндексы объединяются именно так, как вы и подумали. Условие deptno=20 будет генерировать один список идентификаторов строк-кандидатов, а условие job='manager' - другой. Эти списки будут объединяться, и фактически выбраны из таблицы будут только строки, входящие в оба списка. А вот ответ на вопрос, что лучше (как всегда) зависит от многих обстоятельств. Бывают случаи, благоприятные для обоих вариантов. В рассмотренном выше примере, конкатенированный индекс по deptno, job, скорее всего, будет лучше (работы меньше). Однако, если в таблице EMP есть 50 столбцов, а в условии могут упоминаться любые ДВА из них? Вы хотите создавать индекс для каждого сочетания (я - нет)? При работе с индексами на основе битовых карт (bitmap indexes), использовать конкатенированный индекс почти никогда нет смысла. Практически всегда индексируются отдельные столбцы. Что касается ограничения "5", - мы будем использовать столько индексов, сколько нужно. Например: ops$tkyte@ORA817.US.ORACLE.COM> create table t ( a int, b int, c int, d int, e int, f int, g int, h int, i int ); Table created. ops$tkyte@ORA817.US.ORACLE.COM> create bitmap index t_a on t(a); Index created. ops$tkyte@ORA817.US.ORACLE.COM> create bitmap index t_b on t(b); Index created. ops$tkyte@ORA817.US.ORACLE.COM> create bitmap index t_c on t(c); Index created. ops$tkyte@ORA817.US.ORACLE.COM> create bitmap index t_d on t(d); Index created. ops$tkyte@ORA817.US.ORACLE.COM> create bitmap index t_e on t(e); Index created. ops$tkyte@ORA817.US.ORACLE.COM> create bitmap index t_f on t(f); Index created. ops$tkyte@ORA817.US.ORACLE.COM> create bitmap index t_g on t(g); Index created. ops$tkyte@ORA817.US.ORACLE.COM> create bitmap index t_h on t(h); Index created. ops$tkyte@ORA817.US.ORACLE.COM> create bitmap index t_i on t(i); Index created. ops$tkyte@ORA817.US.ORACLE.COM> exec dbms_stats.set_table_stats( user, 'T', numrows=>10000000, numblks => 10000000 ); PL/SQL procedure successfully completed. ops$tkyte@ORA817.US.ORACLE.COM> set autotrace on explain ops$tkyte@ORA817.US.ORACLE.COM> select count(*) 2 from t 3 where a = 1 4 and b = 2 5 and c = 3 6 and d = 4 7 and e = 5 8 and f = 6 9 and g = 7 10 and h = 8 11 and i = 9 12 / COUNT(*) ---------- 0 Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=10 Card=1 Bytes=100) 1 0 SORT (AGGREGATE) 2 1 BITMAP CONVERSION (COUNT) 3 2 BITMAP AND 4 3 BITMAP INDEX (SINGLE VALUE) OF 'T_A' 5 3 BITMAP INDEX (SINGLE VALUE) OF 'T_B' 6 3 BITMAP INDEX (SINGLE VALUE) OF 'T_C' 7 3 BITMAP INDEX (SINGLE VALUE) OF 'T_D' 8 3 BITMAP INDEX (SINGLE VALUE) OF 'T_E' 9 3 BITMAP INDEX (SINGLE VALUE) OF 'T_F' 10 3 BITMAP INDEX (SINGLE VALUE) OF 'T_G' 11 3 BITMAP INDEX (SINGLE VALUE) OF 'T_H' 12 3 BITMAP INDEX (SINGLE VALUE) OF 'T_I' Даже если используются обычные индексы, можно будет их соединить: ops$tkyte@ORA817.US.ORACLE.COM> select /*+ index_join( t ) */ count(*) 2 from t 3 where a = 1 4 and b = 2 5 and c = 3 6 and d = 4 7 and e = 5 8 and f = 6 9 and g = 7 10 and h = 8 11 and i = 9 12 / COUNT(*) ---------- 0 Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=32 Card=1 Bytes=100) 1 0 SORT (AGGREGATE) 2 1 VIEW OF 'index$_join$_001' (Cost=32 Card=1 Bytes=100) 3 2 HASH JOIN 4 3 HASH JOIN 5 4 HASH JOIN 6 5 HASH JOIN 7 6 HASH JOIN 8 7 HASH JOIN 9 8 HASH JOIN 10 9 HASH JOIN 11 10 INDEX (RANGE SCAN) OF 'T_A' (NON-UNIQUE) (Cost=11 Card=1 Bytes=100) 12 10 INDEX (RANGE SCAN) OF 'T_B' (NON-UNIQUE) (Cost=11 Card=1 Bytes=100) 13 9 INDEX (RANGE SCAN) OF 'T_C' (NON-UNIQUE) (Cost=11 Card=1 Bytes=100) 14 8 INDEX (RANGE SCAN) OF 'T_D' (NON-UNIQUE) (Cost=11 Card=1 Bytes=100) 15 7 INDEX (RANGE SCAN) OF 'T_E' (NON-UNIQUE) (Cost=11 Card=1 Bytes=100) 16 6 INDEX (RANGE SCAN) OF 'T_F' (NON-UNIQUE) (Cost=11 Card=1 Bytes=100) 17 5 INDEX (RANGE SCAN) OF 'T_G' (NON-UNIQUE) (Cost=11 Card=1 Bytes=100) 18 4 INDEX (RANGE SCAN) OF 'T_H' (NON-UNIQUE) (Cost=11 Card=1 Bytes=100) 19 3 INDEX (RANGE SCAN) OF 'T_I' (NON-UNIQUE) (Cost=11 Card=1 Bytes=100) Использование нескольких индексов...Я тут борюсь с использованием нескольких индексов для Index Join... У меня есть два индекса на основе b-дерева по таблице, и я хочу выбирать данные путем соединения этих двух индексов, а не полным просмотром таблицы. Эти два индекса содержат все столбцы, которые выбираются в запросе. Я пытался использовать подсказку /*+ INDEX_JOIN(TAB1 IND1 IND2) */, но она не работает. Не могли бы вы объяснить, как использовать Index Join для индексов на основе B-деревьев? Какие параметры инициализации надо установить, чтобы эта возможность использовалась? Ответ Тома КайтаПримеры - всегда нужны примеры того, что именно пытались делать, чтобы объяснить, почему что-то произошло или не произошло... Причин может быть множество. Среди наиболее вероятных - следующие:
ops$tkyte@ORA817DEV.US.ORACLE.COM> @desc big_table Datatypes for Table big_table Data Data Column Name Type Length Nullable ------------------------------ -------------------- ----------- -------- OWNER VARCHAR2 30 not null OBJECT_NAME VARCHAR2 30 not null SUBOBJECT_NAME VARCHAR2 30 null OBJECT_ID NUMBER not null DATA_OBJECT_ID NUMBER null OBJECT_TYPE VARCHAR2 18 null CREATED DATE 7 not null LAST_DDL_TIME DATE 7 not null TIMESTAMP VARCHAR2 19 null STATUS VARCHAR2 7 null TEMPORARY VARCHAR2 1 null GENERATED VARCHAR2 1 null SECONDARY VARCHAR2 1 null Indexes on big_table Index Is Name Unique COLUMNS ------------------------------ ------ -------------------------------- BIG_TABLE_IDX1 No OBJECT_NAME BIG_TABLE_IDX2 No OBJECT_ID Для этой таблицы из 1000000 строк я запросто добиваюсь соединения индексов: ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ index_join( big_table big_table_idx1 big_table_idx2 ) */ object_name, object_id 2 from big_table 3 where object_name like 'ABCDEF%' 4 and object_id between 1000 and 1500 5 / no rows selected Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=83 Card=138 Bytes=4140) 1 0 VIEW OF 'index$_join$_001' (Cost=83 Card=138 Bytes=4140) 2 1 HASH JOIN 3 2 INDEX (RANGE SCAN) OF 'BIG_TABLE_IDX2' (NON-UNIQUE) (Cost=81 Card=138 Bytes=4140) 4 2 INDEX (RANGE SCAN) OF 'BIG_TABLE_IDX1' (NON-UNIQUE) (Cost=81 Card=138 Bytes=4140) Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 73 consistent gets 0 physical reads 0 redo size 272 bytes sent via SQL*Net to client 319 bytes received via SQL*Net from client 1 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 0 rows processed Мне пришлось задать подсказку, поскольку это ОШИБОЧНЫЙ план: ops$tkyte@ORA817DEV.US.ORACLE.COM> select object_name, object_id 2 from big_table 3 where object_name like 'ABCDEF%' 4 and object_id between 1000 and 1500 5 / no rows selected Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=431 Card=138 Bytes=4140) 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'BIG_TABLE' (Cost=431 Card=138 Bytes=4140) 2 1 INDEX (RANGE SCAN) OF 'BIG_TABLE_IDX1' (NON-UNIQUE) (Cost=52 Card=138) Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 3 consistent gets 0 physical reads 0 redo size 272 bytes sent via SQL*Net to client 319 bytes received via SQL*Net from client 1 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 0 rows processed Никаких параметров инициализации задавать не нужно. Подсказка index_join приводит к использованию только одного из индексов...У меня есть запрос с подсказкой index_join: SELECT /*+index_join(ho idx1 idx2)*/ sum( nvl(mw,0) ) FROM ho WHERE fko = :b4 AND fks = :b3 AND hdate >= :b2 AND hdate < :b1 Получаемый в результате план выполнения на oracle 9.2.0.3.0 (AIX - 64BIT) показывает, что выполняется index_join, но используется только один из указанных индексов, и соединяется он с неким третим индексом. Проблема в том, что этот третий индекс не секционирован, поэтому запрос работает дольше. / Rows Row Source Operation /--------- --------------------------------------------------- / 345 SORT AGGREGATE (cr=6409065 pr=6730699 pw=340953 time=7952.32) / 252288 .FILTER (cr=6409065 pr=6730699 pw=340953 time=7951.67) / 252288 ..VIEW (cr=6409065 pr=6730699 pw=340953 time=7951.20) / 252288 ...HASH JOIN (cr=6409065 pr=6730699 pw=340953 time=7950.17) /154842493 ....PARTITION RANGE ITERATOR PARTITION: KEY KEY (cr=264615 pr=264615 pw=0 time=534.27) /154842493 .....INDEX RANGE SCAN idx2 PARTITION: KEY KEY (object id 42026 ) (cr=264615 pr=264615 pw=0 time=316.62) / 5228609 ....INDEX FAST FULL SCAN idx3 (object id 36664 ) (cr=6144450 pr=6125131 pw=0 time=6704.00) Меня интересует, почему Oracle выбрал только один из двух индексов, указанных в подсказке index_join? Вот определения таблицы, представления и индекса: CREATE TABLE ho_t (hid NUMBER(9,0) NOT NULL, hdate DATE NOT NULL, fko NUMBER(9,0) NOT NULL, fks NUMBER(9,0) NOT NULL, df NUMBER(1,0) NOT NULL, mw NUMBER(13,3) --... еще 25 столбцов ) PARTITION BY RANGE (hdate) ( PARTITION p_2_2 VALUES LESS THAN (TO_DATE(' 2000-07-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')) --... еще 15 секций ) / CREATE OR REPLACE VIEW ho ( hid, hdate, fko, fks, df, mw --.. еще 25 столбцов ) AS select hid, hdate, fko, fks, df, mw --... еще 25 столбцов from ho_t / CREATE UNIQUE INDEX idx1 ON ho_t ( hdate ASC, fks ASC, df ASC, fko ASC ) LOCAL ( PARTITION p_2_2 --... еще 15 секций ) / CREATE INDEX idx2 ON ho_t ( hdate ASC, fko ASC, mw ) LOCAL ( PARTITION p_2_2 --... еще 15 секций ) / CREATE INDEX idx3 ON ho_t ( fko ASC, fks ASC ) / ALTER TABLE ho_t ADD CONSTRAINT pk_ho_t PRIMARY KEY (hid) USING INDEX / ALTER TABLE ho_t ADD CONSTRAINT fk_h FOREIGN KEY (fko) REFERENCES R.s (oi) ON DELETE SET NULL / Ответ Тома КайтаПомогите мне "поломать" следующий пример: drop table ho_t; CREATE TABLE ho_t (hid NUMBER(9,0) NOT NULL, hdate DATE NOT NULL, fko NUMBER(9,0) NOT NULL, fks NUMBER(9,0) NOT NULL, df NUMBER(1,0) NOT NULL, mw NUMBER(13,3) ) partition by range(hdate) ( partition p1 values less than (TO_DATE(' 2000-07-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')), partition p2 values less than (TO_DATE(' 2001-07-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')) ) / create or replace view ho as select * from ho_t / CREATE UNIQUE INDEX idx1 ON ho_t ( hdate ASC, fks ASC, df ASC, fko ASC ) local / CREATE INDEX idx2 ON ho_t ( hdate ASC, fko ASC, mw ) local / CREATE INDEX idx3 ON ho_t ( fko ASC, fks ASC ) / variable b1 varchar2(25); variable b2 varchar2(25); variable b3 varchar2(25); variable b4 varchar2(25); set linesize 121 delete from plan_table; explain plan for SELECT /*+index_join(ho idx1 idx2)*/ sum( nvl(mw,0) ) FROM ho WHERE fko = to_number(:b4) AND fks = to_number(:b3) AND hdate >= to_date(:b2) AND hdate < to_date(:b1) / select * from table(dbms_xplan.display); Комментарий читателя от 6 мая 2004 годаЧтобы "поломать" ваш сценарий, мне пришлось бы скопировать данные из исходной таблицы (более 11 миллионов строк) и посмотреть, будет ли ваша таблица вести себя так же, как и исходная. Я надеялся, что вы уже сталкивались с этой проблемой, например, с недокументированными ограничениями подсказки index_join, вот почему я и послал определения таблицы, индексов и представления... Ответ Тома КайтаНет, используйте пакет dbms_stats для установки статистической информации для таблицы и т.п., чтобы "мой" план стал "вашим". Если соединение индексов можно использовать, оно должно было использоваться. Должно быть нечто, что сделает этот тестовый пример "более походим на реальный", чтобы получить ваш результат. |