Запрос выполняется медленнее после создания индексов и используется вычисление dbms_stats
У меня есть таблица с 1,5 миллионами строк. Я запускаю запрос, который извлекает записи, не имеющие повторяющихся значений в столбце. Я наблюдаю поведение, при котором после создания индексов производительность запроса ухудшается. Я также использовал dbms_stats со 100% процентной оценкой (режим вычисления)
для сбора статистики, чтобы CBO оракула 11g принимала более обоснованное решение для плана запроса, но оно не улучшает время выполнения запроса.
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
имеет 0,4 миллиона уникальных значений.
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
Когда я впервые запустил запрос после этого, ему потребовалось время прокрутки 21. В то время как последующие прогоны купили его до 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
Я ожидал, что время будет ниже, чем когда таблица не была проиндексирована. Почему индексированная версия таблицы занимает больше времени для получения результатов, чем не индексированная версия? Если я не ошибаюсь, это ТАБЛИЦА ДОСТУПА ПО ИНДЕКСУ 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 секунд).
Моя цель - максимально сократить время выполнения запроса. Но после добавления индексов или после вычисления статистики время запроса просто увеличивалось. Почему это происходит и как я могу улучшить, даже сохраняя индексы?
Ответы
Ответ 1
Прежде всего, позвольте мне процитировать Tom Kyte:
просто продолжайте говорить себе снова и снова
"полные сканы не злые, индексы не очень хороши"
"полное сканирование не является злом, индексы не хороши"
"полное сканирование не является злом, индексы не хороши"
"полное сканирование не является злом, индексы не хороши"
"полное сканирование не является злом, индексы не хороши"
"полное сканирование не является злом, индексы не хороши"
Индексы будут не всегда улучшать производительность, это не волшебная серебряная пуля (как будто такая вещь когда-либо существовала)
Теперь вы спрашиваете , почему требуется больше времени с вашим индексом. Ответ довольно прост:
- с полным сканированием таблицы: 46518 соответствует
- с вашим индексом: 660054 соответствует
Другими словами: Oracle выполняет больше операций чтения с вашим индексом, чем с полным сканированием таблицы. Это происходит потому, что:
- FULL TABLE SCAN reads - это массовые операции (много блоков одновременно) и, следовательно, эффективный способ чтения большого количества данных.
- иногда, когда вы читаете из индекса, вы заканчиваете чтение одного и того же блока данных более одного раза.
В связи с тем, что оптимизатор решил использовать этот явно неэффективный индекс, это связано с тем, что даже при esimate_percent=100
и полных гистограммах (которые вы собрали в столбце MEASURE_0
) некоторое распределение данных по-прежнему не может быть надежно выраженный простым анализом оптимизатора. В частности, анализатор не понимает зависимость между столбцами и кросс-таблицами. Это приводит к неправильным оценкам, которые приводят к плохим выборам плана.
Изменить: кажется, что рабочая гипотеза CBO вообще не работает для этого самосоединения (ваш последний запрос ожидает 31 миллион строк, тогда как только 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, где все блоки сначала копируются с диска, а затем могут быть прочитаны несколько раз (при первом запросе блока всегда выполняется физическое чтение). Затем некоторые системы также имеют независимый системный кеш, который будет быстрее возвращать данные, если он был недавно прочитан.
Для дальнейшего чтения:
Ответ 2
Как работает этот код?
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;