Проверка выполнения и оптимизации SQL запросов в реальном времени

THE HOLY BIBLE - King James Version - БИБЛИЯ в Синодальном переводе
"Нас Атакуют!" Изобличи козни лукавого, запрети диаволу

Проверка выполнения и оптимизации SQL запросов в реальном времени

Оптимизатор SQL запросов Oracle достиг небывалого уровня сложности. Количество встроенных решений для предопределённых "частных случаев" иногда делает планы выполнения непредсказуемыми. Задача анализа и оптимизации SQL усложняется широким распространением параллельного выполнения и кластеров, "переписыванием" запросов "на лету", динамическим сбором статистики. В этих условиях традиционные методы анализа производительности и выполнения SQL оказываются недостаточными.

Предлагаемая читателю заметка рассказывает о новом средстве проверки выполнения и оптимизации SQL запросов в реальном времени - Active SQL Monitor Report.

Прежде чем мы продолжим, я хотел бы привести строки из Евангелия:



.................. == От Иоанна святое благовествование == .....................
=== Глава 15, Стих 12 ===
12 Сия есть заповедь Моя, да любите друг друга, как Я возлюбил вас.
13 Нет больше той любви, как если кто положит душу свою за друзей своих.
14 Вы друзья Мои, если исполняете то, что Я заповедую вам.
15 Я уже не называю вас рабами, ибо раб не знает, что делает господин его;
 но Я назвал вас друзьями, потому что сказал вам все, что слышал от Отца
 Моего.
16 Не вы Меня избрали, а Я вас избрал и поставил вас, чтобы вы шли и
 приносили плод, и чтобы плод ваш пребывал, дабы, чего ни попросите от Отца
 во имя Мое, Он дал вам.
17 Сие заповедаю вам, да любите друг друга.
18 Если мир вас ненавидит, знайте, что Меня прежде вас возненавидел.
19 Если бы вы были от мира, то мир любил бы свое; а как вы не от мира, но Я
 избрал вас от мира, потому ненавидит вас мир.
20 Помните слово, которое Я сказал вам: раб не больше господина своего. Если
 Меня гнали, будут гнать и вас; если Мое слово соблюдали, будут соблюдать и
 ваше.
21 Но все то сделают вам за имя Мое, потому что не знают Пославшего Меня.
22 Если бы Я не пришел и не говорил им, то не имели бы греха; а теперь не
 имеют извинения во грехе своем.

Лично для вас благая весть - Единородный Сын Божий Иисус Христос любит вас, Он взошёл на крест за ваши грехи, был распят и на третий день воскрес, сел одесную Бога и открыл нам дорогу в Царствие Небесное.

Господь говорит с каждым из нас - о христианской любви, о дружбе и преданности, исполнении Его заповедей. Именно так к каждому из нас приходит свобода, а с ней и ненависть греховного мира к нам. Но Иисус подбадривает нас, объясняя настоящую причину наших мирских неудач, расстройств и неприятностей. И в это же время милостиво даёт нам возможность спасения через познание Бога. Ухватимся же всеми силами за имя Его, несмотря на гонения и ненависть мира. И будем помнить слово, которое Он сказал нам.

Покайтесь, примите Иисуса как вашего Спасителя, ибо наступают последние времена и время близко - стоит Судья у ворот.

Пожалуйста, в своих каждодневных трудах, какими бы занятыми вы себе ни казались - находите время для Бога, Его заповедей и Библии.

На главной странице этого сайта вы найдете программу для чтения Библии в командной строке - буду очень рад если программа окажется полезной. Пожалуйста, читайте Библию, на экране или в печатном виде - вы будете искренне удивлены как много там сказано лично про вас и ваши обстоятельства.


Вернёмся к нашим техническим деталям.

Прежде всего мы проанализируем два варианта простого SQL запроса и найдём причину чрезмерного количества чтений блоков данных. Затем, используя Real Time SQL Monitoring, мы проверим наши выводы и посмотрим как можно избежать излишнего выполнения "проблемных" запросов во время анализа.

Данные для запроса

Создадим таблицу, содержащую абстрактные данные о продажах чего-либо.

.............. == Послание к Галатам святого апостола Павла == .................
=== Глава 5, Стих 15 ===
15 Если же друг друга  угрызаете  и  съедаете,  берегитесь,  чтобы  вы  не  были
истреблены друг другом.

(b+/b-, c+/c-, +/-, *) >
[ora@reatdb01 ~]$ sqlplus

SQL*Plus: Release 11.2.0.2.0 Production

Copyright (c) 1982, 2010, Oracle.  All rights reserved.

Enter user-name: test/test
SQL>

create sequence t1_pk_sq;

create table sales (
SALEID, CLIENT, PRODUCTID,
constraint PK_SALES_ID primary key (SALEID)
)
as select t1_pk_sq.nextval SALEID, d.*
from (
select owner CLIENT, HEADER_BLOCK PRODUCTID
from dba_segments
) d;

exec dbms_stats.gather_table_stats('TEST','SALES');

23:49:03 SQL> desc sales
Name      Null?   Type
--------------------------------------- -------- ------------------------------
SALEID      NOT NULL NUMBER(38)
CLIENT        VARCHAR2(30)
PRODUCTID       NUMBER

Проверим, действительно ли значения нашего primary key монотонно возрастают:

23:49:17 SQL> select * from sales
23:49:31   2  where rownum <10;

SALEID CLIENT      PRODUCTID
---------- ------------------------------ ----------
1 SYS      288
2 SYS      224
3 SYS      264
4 SYS      232
5 SYS      248
6 SYS      520
7 SYS      240
8 SYS      560
9 SYS      568

9 rows selected.

Elapsed: 00:00:00.00

Теперь посмотрим, как близко находятся ("упакованы") записи внутри блоков нашего индекса, отсортированного по столбцу SALEID:

1  select INDEX_NAME, INDEX_TYPE, UNIQUENESS, BLEVEL, LEAF_BLOCKS,
2  DISTINCT_KEYS, CLUSTERING_FACTOR, STATUS, SEGMENT_CREATED
3* from user_indexes
23:53:18 SQL> /

INDEX_NAME  INDEX_TYPE       UNIQUENES    BLEVEL LEAF_BLOCKS DISTINCT_KEYS CLUSTERING_FACTOR STATUS   SEG
----------- ---------------- --------- ---------- ----------- ------------- ----------------- -------- ---
PK_SALES_ID NORMAL      UNIQUE  1    12        6127     17 VALID    YES

1 row selected.

Elapsed: 00:00:00.04
23:53:19 SQL>


23:53:57 SQL> select CONSTRAINT_NAME, CONSTRAINT_TYPE, STATUS, INDEX_NAME
23:54:16   2  from user_constraints;

CONSTRAINT_NAME         C STATUS   INDEX_NAME
------------------------------ - -------- --------------------
PK_SALES_ID         P ENABLED  PK_SALES_ID

1 row selected.

Elapsed: 00:00:00.75
23:54:22 SQL>

23:55:07 SQL> l
1* select * from user_ind_columns
23:55:20 SQL> /

INDEX_NAME  TABLE_NAME    COLUMN_NAME COLUMN_POSITION COLUMN_LENGTH   CHAR_LENGTH DESC
----------- ----------    ----------- --------------- -------------   ----------- ----
PK_SALES_ID SALES        SALEID             1            22         0 ASC

1 row selected.

Elapsed: 00:00:00.04
23:55:21 SQL>

Близость значений CLUSTERING_FACTOR и LEAF_BLOCKS говорит нам о хорошей "упаковке" записей в индексе. Это сделает индекс PK_SALES_ID более "привлекательным" для оптимизатора запросов, что теоретически должно гарантировать нам самый быстрый доступ к данным таблицы.

Проверим это предположение на практике. Для простого теста мы хотим определить количество проданных продуктов в каждой покупке. Исходя из определения primary key для нашей таблицы очевидно, что это значение всегда будет равным единице. Последующий запрос необходимо выполнить как минимум дважды, для полного устранения обрашений к диску.

1  select SALEID, count(*)
2  from sales
3* group by saleid
23:57:25 SQL> /

..... output ....

6127 rows selected.

Elapsed: 00:00:00.12

Execution Plan
----------------------------------------------------------
Plan hash value: 1330191699

---------------------------------------------------------------------------------------------
| Id  | Operation        | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |      |  6127 | 24508 |   6  (17)| 00:00:01 |
|   1 |  HASH GROUP BY        |      |  6127 | 24508 |   6  (17)| 00:00:01 |
|   2 |   INDEX STORAGE FAST FULL SCAN| PK_SALES_ID |  6127 | 24508 |   5   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------


Statistics
----------------------------------------------------------
1  recursive calls
0  db block gets
17  consistent gets
0  physical reads
0  redo size
130285  bytes sent via SQL*Net to client
5011  bytes received via SQL*Net from client
410  SQL*Net roundtrips to/from client
0  sorts (memory)
0  sorts (disk)
6127  rows processed

00:02:06 SQL>

Получен ожидаемый результат - обращений к таблице не было вообще, все необходимые данные были получены из самого индекса. Изменим запрос так, чтобы использование индекса не было таким привлекательным.

"Хороший" SQL запрос

В этот раз нам надо узнать сколько наименований продуктов купил каждый клиент.

........................... == Притчи Соломона == ..............................
=== Глава 23, Стих 4 ===
4 Не  заботься  о  том,  чтобы  нажить  богатство;  оставь  такие  мысли   твои.

(b+/b-, c+/c-, +/-, *) >
[ora@reatdb01 ~]$ sqlplus

SQL*Plus: Release 11.2.0.2.0 Production

Copyright (c) 1982, 2010, Oracle.  All rights reserved.

Enter user-name: test/test

00:06:46 SQL> l
1   select client, count(distinct PRODUCTID)
2   from sales
3* group by client
00:06:47 SQL> /

CLIENT          COUNT(DISTINCTPRODUCTID)
------------------------------ ------------------------
MDSYS          566
CTXSYS           53
OLAPSYS          141
HR           25
OUTLN            9
SYSTEM          396
EXFSYS           58
APEX_030200         467
SCOTT            5
ORDSYS            7
SYSMAN          579
OE           70
SH          179
PM           41
DBSNMP            3
XDB          865
ORDDATA          160
IX           28
SYS         2333
WMSYS           56

20 rows selected.

Elapsed: 00:00:00.01

Execution Plan
----------------------------------------------------------
Plan hash value: 3472529357

------------------------------------------------------------------------------------------
| Id  | Operation       | Name  | Rows  | Bytes | Cost (%CPU)| Time  |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |   |    20 |   380 |     8  (13)| 00:00:01 |
|   1 |  HASH GROUP BY       |   |    20 |   380 |     8  (13)| 00:00:01 | уменьшение числа строк
|   2 |   VIEW        | VM_NWVW_1 |  6127 |   113K|     8  (13)| 00:00:01 |
|   3 |    HASH GROUP BY      |   |  6127 | 67397 |     8  (13)| 00:00:01 |
|   4 |     TABLE ACCESS STORAGE FULL| SALES  |  6127 | 67397 |     7   (0)| 00:00:01 |
------------------------------------------------------------------------------------------


Statistics
----------------------------------------------------------
1  recursive calls
0  db block gets
19  consistent gets -- Таблица уже в buffer cache (после сбора статистики и первого запроса)
0  physical reads
0  redo size
1104  bytes sent via SQL*Net to client
534  bytes received via SQL*Net from client
3  SQL*Net roundtrips to/from client
0  sorts (memory)
0  sorts (disk)
20  rows processed

00:06:49 SQL>

В этот раз результат немного неожиданный - индекс не был использован вообще, поскольку полное чтение всей таблицы требует приблизительно такого же количества чтений блоков из буфера (19 буферов за 7 операций чтения), как и сканирование индекса (17 буферов за 5 операций.).

Также надо заметить что при пустом buffer cache платформа Exadata позволяет операции "TABLE ACCESS STORAGE FULL" использовать в одной сессии два разных способа чтения данных в buffer cache (а не PGA) - "cell single block physical read" для доступа к заголовку сегмента и "cell multiblock physical read" для прочтения "за один раз" всех оставшихся блоков. Если же buffer cache оказывается не пустым, как в этом примере "хорошего" запроса - сессия прочитает все необходимые блоки из памяти SGA (а не PGA), опять же несмотря на способ доступа к таблице "TABLE ACCESS STORAGE FULL".

Возможно ли что Оракл выбрал неоптимальный способ чтения данных и нам надо использовать индекс?

"Плохой" SQL запрос

Заставим Оракл использовать индекс и оценим результат.

....................... == Деяния святых апостолов == ..........................
=== Глава 20, Стих 24 ===
24 Но я ни на что не взираю и не дорожу  своею  жизнью,  только  бы  с  радостью
совершить поприще мое  и  служение,  которое  я  принял  от  Господа  Иисуса,
проповедать Евангелие благодати Божией.

(b+/b-, c+/c-, +/-, *) >
[ora@reatdb01 ~]$ sqlplus

SQL*Plus: Release 11.2.0.2.0 Production

Copyright (c) 1982, 2010, Oracle.  All rights reserved.

Enter user-name: test/test

SQL> l

1   select /*+ INDEX (sales, PK_SALES_ID) */ client, count(distinct PRODUCTID)
2   from sales
3* group by client
00:13:08 SQL> /

CLIENT          COUNT(DISTINCTPRODUCTID)
------------------------------ ------------------------
MDSYS          566
CTXSYS           53
OLAPSYS          141
HR           25
OUTLN            9
SYSTEM          396
EXFSYS           58
APEX_030200         467
SCOTT            5
ORDSYS            7
SYSMAN          579
OE           70
SH          179
PM           41
DBSNMP            3
XDB          865
ORDDATA          160
IX           28
SYS         2333
WMSYS           56

20 rows selected.

Elapsed: 00:00:00.02

Execution Plan
----------------------------------------------------------
Plan hash value: 3227008845

----------------------------------------------------------------------------------------------
| Id  | Operation         | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |      |   20 |  380 |   31   (4)| 00:00:01 | Cost вырос в 3 раза
|   1 |  HASH GROUP BY         |      |   20 |  380 |   31   (4)| 00:00:01 | CPU меньше в 3 раза
|   2 |   VIEW          | VM_NWVW_1   | 6127 |  113K|   31   (4)| 00:00:01 |
|   3 |    HASH GROUP BY        |      | 6127 | 67397 |   31   (4)| 00:00:01 |
|   4 |     TABLE ACCESS BY INDEX ROWID| SALES      | 6127 | 67397 |   30   (0)| 00:00:01 | Очевидно, что индекс
|   5 |      INDEX FULL SCAN        | PK_SALES_ID | 6127 |      |   13   (0)| 00:00:01 | тут не нужен.
----------------------------------------------------------------------------------------------


Statistics
----------------------------------------------------------
1  recursive calls
0  db block gets
30  consistent gets -- Логический IO возрос всего на 1/3 из-за хорошей "плотности" индекса
0  physical reads
0  redo size
1104  bytes sent via SQL*Net to client
534  bytes received via SQL*Net from client
3  SQL*Net roundtrips to/from client
0  sorts (memory)
0  sorts (disk)
20  rows processed

00:13:11 SQL>

В этом случае raw trace файл показывает, что "cr" равен "cost" для обеих строк плана "INDEX FULL SCAN" и "TABLE ACCESS BY INDEX ROWID" - наша сессия читает по одному блоку за раз. Просто для полного прочтения таблицы через индекс нам понадобится один блок для "корня" индекса, 12 блоков для "листьев" индекса и 17 прочтений блоков (смотрите CLUSTERING_FACTOR в начале заметки) для доступа к строкам самой таблицы - итого 30 consistent gets - буферов, прочитанных из buffer cache, равным по размеру 8192 байт каждый.

И чем выше значение CLUSTERING_FACTOR для используемого индекса - тем большее количество обращений к диску или buffer cache будет необходимо для прочтения всей таблицы. В нашем примере индекс PK_SALES_ID имеет почти идеальную структуру и очень низкий CLUSTERING_FACTOR, в реальной ситуации полное прочтение таблицы по индексу потребует значительно больше ресурсов чем простой full table scan.

Также заметьте, что при таком запросе не используется специфический для Exadata "INDEX STORAGE FULL SCAN".

Становится понятным, что показанный выше способ анализа производительности SQL запроса весьма трудоёмок и потребует многократного выполнения запроса, очищения buffer cache, трассировки сессий и прочих специфических приёмов, недопустимых на "живой" системе.

Active SQL Monitor Report

Основное назначение этого отчёта - периодически оценивать степень выплонения длительного SQL запроса. То есть этот отчёт может показывать нам сколько процентов работы уже выполнено параллельными сессиями, и сколько ещё осталось. В дополнение, Active SQL Monitor Report собирает для нас всю использованную выше статистику для уже выполненных SQL запросов длинной более 5 секунд (и для всех параллельных запросов). Это позволяет нам увидеть всю необходимую информацию в одном месте, без блуждания по raw trace файлам - и почти полностью исключает необходимость перезапусков "медленных" запросов. Генерировать этот отчёт надо как можно быстрее - желательно сразу же после завершения "плохого" запроса.

Итак, освежим нашу память - в чём проблема? Предположим, что один из разработчиков "по-старинке" уверен что индекс надо использовать всегда. Наш специалист берёт "хороший" запрос и делает из него "плохой" запрос (см. выше). После попадания этого кода в "живую" систему наши пользователи начинают жаловаться на резкое замедление работы приложения.

Как администраторы баз данных, мы должны найти причину деградации производительности как можно скорее. Мы быстренько генерируем AWR report за последние 15-20 минут и обнаруживаем наш "плохой" запрос на месте лидера по потреблению ресурсов системы. У нас нет времени на подробное "разжёвывание" запроса, как было сделано выше. Нам надо просто как можно быстрее понять, на что же "плохой" запрос тратит время и IO ресурсы. Выплоняем следующий шаг:

.................. == От Марка святое благовествование == ......................
=== Глава 4, Стих 24 ===
23 Если      кто       имеет       уши       слышать,       да           слышит!
24 И сказал им: замечайте, что  слышите:  какою  мерою  мерите,  такою  отмерено
будет вам и прибавлено будет вам, слушающим.

(b+/b-, c+/c-, +/-, *) >
[ora@reatdb01 ~]$ sqlplus

SQL*Plus: Release 11.2.0.2.0 Production

Copyright (c) 1982, 2010, Oracle.  All rights reserved.

Enter user-name: test/test

SQL> l

1  select * from gv$sql_monitor
2* where sysdate-SQL_EXEC_START<1/24/4 -- последние 15 минут
00:35:53 SQL> /

no rows selected

Elapsed: 00:00:01.19
00:35:55 SQL>

Запрос выполняется слишком быстро и не был зарегистрирован. Заставить Оракл "обратить внимание" на нужный SQL можно вручную. Мы делаем это в нашем примере исключительно в демонстрационных целях - в действительности ваш "проблемный" отчёт наверняка появится в V$SQL_MONITOR - и вы сможете его там найти, если пользователи пожалуются достаточно быстро. Таким образом, вам не понадобится выполнять "плохой" запрос снова. Ну а мы вставим в код необходимый "hint" и запустим заново оба SQL - "хороший" и "плохой".

-- "хороший" запрос
select /*+ MONITOR */ client, count(distinct PRODUCTID)
from sales
group by client
/

CLIENT          COUNT(DISTINCTPRODUCTID)
------------------------------ ------------------------
MDSYS          566
CTXSYS           53
OLAPSYS          141
HR           25
OUTLN            9
SYSTEM          396
EXFSYS           58
APEX_030200         467
SCOTT            5
ORDSYS            7
SYSMAN          579
OE           70
SH          179
PM           41
DBSNMP            3
XDB          865
ORDDATA          160
IX           28
SYS         2333
WMSYS           56

20 rows selected.

Elapsed: 00:00:00.01

Execution Plan
----------------------------------------------------------
Plan hash value: 3472529357

------------------------------------------------------------------------------------------
| Id  | Operation       | Name  | Rows  | Bytes | Cost (%CPU)| Time  |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |   |    20 |   380 |     8  (13)| 00:00:01 |
|   1 |  HASH GROUP BY       |   |    20 |   380 |     8  (13)| 00:00:01 |
|   2 |   VIEW        | VM_NWVW_1 |  6127 |   113K|     8  (13)| 00:00:01 |
|   3 |    HASH GROUP BY      |   |  6127 | 67397 |     8  (13)| 00:00:01 |
|   4 |     TABLE ACCESS STORAGE FULL| SALES  |  6127 | 67397 |     7   (0)| 00:00:01 |
------------------------------------------------------------------------------------------


Statistics
----------------------------------------------------------
1  recursive calls
0  db block gets
19  consistent gets
0  physical reads
0  redo size
1104  bytes sent via SQL*Net to client
534  bytes received via SQL*Net from client
3  SQL*Net roundtrips to/from client
0  sorts (memory)
0  sorts (disk)
20  rows processed

00:42:21 SQL>

1  select inst_id, sql_id, username, sql_exec_id, SQL_TEXT
2  from gv$sql_monitor
3* where sysdate-SQL_EXEC_START<1/24/4
00:43:40 SQL> /

INST_ID  SQL_ID  USERNAME        SQL_EXEC_ID
---------- -------------------- ------------------------------ -----------------
SQL_TEXT
--------------------------------------------------------------------------------
1 27q9ut623h9rf TEST           16777216
EXPLAIN PLAN SET STATEMENT_ID='PLUS20024' FOR select /*+ MONITOR */ client,
count(distinct PRODUCTID)
from sales
group by client

1 f1hmr6pcwzf5u TEST           16777216 -- наш "хороший" запрос
select /*+ MONITOR */ client, count(distinct PRODUCTID)
from sales
group by client


2 rows selected.

Elapsed: 00:00:00.02
00:43:41 SQL>

Главное, что нам надо записать - SQL_ID и SQL_EXEC_ID, эти два параметра однозначно определяют о каком SQL запросе мы говорим. Найдём соответствующие значения для "плохого" отчёта с индексом.

-- теперь "плохой" запрос
00:46:20 SQL> get /tmp/2
1  select /*+ MONITOR INDEX (sales, PK_SALES_ID) */ client, count(distinct PRODUCTID)
2  from sales
3* group by client
00:46:23 SQL> /

CLIENT          COUNT(DISTINCTPRODUCTID)
------------------------------ ------------------------
MDSYS          566
CTXSYS           53
OLAPSYS          141
HR           25
OUTLN            9
SYSTEM          396
EXFSYS           58
APEX_030200         467
SCOTT            5
ORDSYS            7
SYSMAN          579
OE           70
SH          179
PM           41
DBSNMP            3
XDB          865
ORDDATA          160
IX           28
SYS         2333
WMSYS           56

20 rows selected.

Elapsed: 00:00:00.02

Execution Plan
----------------------------------------------------------
Plan hash value: 3227008845

----------------------------------------------------------------------------------------------
| Id  | Operation         | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |      |   20 |  380 |   31   (4)| 00:00:01 |
|   1 |  HASH GROUP BY         |      |   20 |  380 |   31   (4)| 00:00:01 |
|   2 |   VIEW          | VM_NWVW_1   | 6127 |  113K|   31   (4)| 00:00:01 |
|   3 |    HASH GROUP BY        |      | 6127 | 67397 |   31   (4)| 00:00:01 |
|   4 |     TABLE ACCESS BY INDEX ROWID| SALES      | 6127 | 67397 |   30   (0)| 00:00:01 |
|   5 |      INDEX FULL SCAN        | PK_SALES_ID | 6127 |      |   13   (0)| 00:00:01 |
----------------------------------------------------------------------------------------------


Statistics
----------------------------------------------------------
1  recursive calls
0  db block gets
30  consistent gets
0  physical reads
0  redo size
1104  bytes sent via SQL*Net to client
534  bytes received via SQL*Net from client
3  SQL*Net roundtrips to/from client
0  sorts (memory)
0  sorts (disk)
20  rows processed

00:46:23 SQL>

1  select inst_id, sql_id, username, sql_exec_id, SQL_TEXT
2  from gv$sql_monitor
3  where sysdate-SQL_EXEC_START<1/24/4
4* and sql_text like '%INDEX%'
00:48:55 SQL> /

INST_ID  SQL_ID  USERNAME        SQL_EXEC_ID
---------- -------------------- ------------------------------ -----------------
SQL_TEXT
--------------------------------------------------------------------------------
1 c4s7v8uzmdt38 TEST           16777216
EXPLAIN PLAN SET STATEMENT_ID='PLUS20024' FOR
select /*+ MONITOR INDEX (sales, PK_SALES_ID) */ client,
count(distinct PRODUCTID)
from sales
group by client

1 bm6q2rmzfxjrb TEST           16777216 -- "плохой" запрос
select /*+ MONITOR INDEX (sales, PK_SALES_ID) */ client, count(distinct PRODUCTID)
from sales
group by client


2 rows selected.

Elapsed: 00:00:00.01
00:48:56 SQL>

Итак, все необходимые данные имеются - сгенерируем Active SQL Monitor Report для обоих случаев. Необходимо правильно настроить параметры вывода SQL*Plus и потом вручную отредактировать HTML файлы. Не пропустите самую первую строку "set ..." - без неё ваш SQL Monitor Report будет нечитаемым.

-- Теперь проанализируем уже запущенные ранее запросы:

00:50:31 SQL> set lines 30000 trims on long 999999 longchunksize 9999 pages 0

00:56:16 SQL> spool /tmp/good.html
00:56:39 SQL> select dbms_sqltune.report_sql_monitor(
sql_id=>'f1hmr6pcwzf5u', sql_exec_id=>16777216, type=>'active') from dual;

.... Огромное количество HTML строк ....

1 row selected.

Elapsed: 00:00:58.74
00:59:15 SQL> 00:59:15 SQL> spool off
00:59:20 SQL>

00:59:20 SQL> spool /tmp/bad.html
00:59:50 SQL> select dbms_sqltune.report_sql_monitor(
sql_id=>'bm6q2rmzfxjrb', SQL_EXEC_ID=>16777216, type=>'active') from dual;

.... Огромное количество HTML строк ....

1 row selected.

Elapsed: 00:00:02.36
01:01:00 SQL> spool off

Отредактируем оба spool файла так, чтобы в них не было лишних строк внизу и вверху и загрузим их к себе на компьютер. Используйте веб броузер, подключённый к интернету и с полностью включенными Java Script и Plugins. Я всегда пользуюсь Opera.

Открыв файл "bad.html", на странице "Details / Plan" мы сможем увидеть точное распределение 30-ти "consistent gets" между операциями доступа к таблице по индексу - 13 чтений буферов (в нашем случае и блоков) индекса и 17 чтений таблицы. "Откопать" эту информацию иным способом достаточно сложно и долго. Кроме того, время исполнения запроса указано с точностью до микросекунды. Сравните данные со вторым отчётом "good.html".

Теперь поэкспериментируйте с другими запросами посложнее и обратите внимание на очень полезную страницу "Activity".

Я уверен что "Active SQL Monitor Report" послужит вам отличным подспорьем в работе.

Спасибо что зашли,

Будьте благословенны!
Денис