Sei sulla pagina 1di 16

Aula 2 - Brevíssima história da linguagem SQL, conceitos básicos da linguagem,tools de Análise built-in, Planos de

Execução – Introdução, Introdução ao CBO

Structured Query Language, ou Linguagem de Consulta Estruturada ou SQL, é uma linguagem de pesquisa
declarativa para bancos de dados relacionais. Com o advento dos primeiros produtos baseados no modelo de dados
relacional (no final dos anos 70, implementando a teoria proposta pelo pesquisador E. F. Codd alguns anos antes),
surgiu a necessidade de se desenvolver uma linguagem voltada especificamente para este uso. O nome original da
linguagem era SEQUEL, acrônimo para "Structured English Query Language" (Linguagem de Consulta Estruturada em
Inglês), vindo daí o fato de, até hoje, a sigla, em inglês, ser comumente pronunciada "síquel" ao invés de "és-kiú-él",
letra a letra. No entanto, em português, a pronúncia mais corrente é a letra a letra: "ésse-quê-éle".

Originalmente a linguagem só possuía instruções para pesquisa de dados, mas em pouco tempo foram introduzidos
comandos para inserção/deleção/alteração de dados (DML, Data Manipulation Language, tais como INSERT,
UPDATE, DELETE), para definição de estruturas como tabelas/índices/etc (DDL, Data Definition Language, tais como
CREATE, ALTER e DROP), para controle/autorização de acesso aos dados (DCL, Data Control Language, tais como
GRANT e REVOKE) e de controle de transação (DTL, Data Transaction Language, tais como ROLLBACK e COMMIT). O
comando original para pesquisa de dados (SELECT) é um caso á parte, pois originalmente foi incluído num grupo
único para ele, chamado DQL (Data Query Language), mas diversos fabricantes em breve deram ao comando a
capacidade de travar/lockar os registros que recuperou (via cláusula FOR UPDATE ou similar), o que implica
alteração e portanto incluiria o SELECT no grupo de DDLs.

Rapidamente , com a crescente aceitação da linguagem, houve esforços de padronização da linguagem,


originalmente pelo ANSI (American National Standards Institute) em 1986. O SQL foi revisto em 1992 e a esta versão
foi dado o nome de SQL-92. Foi revisto novamente em 1999 e 2003 para se tornar SQL:1999 (SQL3) e SQL:2003,
respectivamente. A idéia fundamental por trás dessa padronização era permitir que tipicamente a linguagem
pudesse ser migrada de plataforma para plataforma sem mudanças estruturais principais - o ponto crucial a ser
compreendido, porém, é que o SQL, embora padronizado pela ANSI, possui muitas variações e extensões produzidos
pelos diferentes fabricantes de sistemas gerenciadores de bases de dados : isso decorre da própria teoria relacional,
que admite vários caminhos para o fluxo de dados, e também do fato de que a padronização NÃO exigiu um método
de implementação comum aos diversos produtos – ela lista quesitos mínimos mas deixa a cargo e cada
implementador como os atender, e em que nível. Assim sendo, é IRREAL se esperar que um mesmo código SQL
possa ser portado para as diferentes plataformas SEM alteração alguma, e igualmente é IRRAZOÁVEL tentar se ater
as features mínimas da linguagem na esperança de maior interoperabilidade, já que tal como dito pode haver
diferenças naturais na implementação de mesmos comandos.

Desta forma, neste Treinamento vamos utilizar em nível pleno as facilidades e recursos no dialeto SQL do banco de
dados Oracle, visando à máxima performance e eficiência do código contra um banco Oracle.

A linguagem SQL é o único meio oficial de um cliente qualquer obter/alterar/remover informações de um banco de
dados (já que o aceso direto aos datafiles é proibido), assim qualquer que seja o cliente, em qualquer ambiente, o
mesmo deverá obter uma conexão (normalmente via rede), e por meio desta enviar um texto com o SQL desejado,
que será validado, executado e armazenado no banco Oracle, e os dados enviados de volta ao cliente. Até mesmo
outras linguagens que podem coexistir no database Oracle (como PL/SQL e Java) ao necessitarem
manipular/consultar dados, alterar estruturas do database, etc, Necessariamente executam comandos SQL, que
serão enviados ao SQL Engine (o componente especializado em interpretar SQL).

Assim que o banco de dados Oracle recebe um SQL, o método para o SQL ser processado é :

Passo 1: é checado se o SQL é válido, utiliza comandos válidos na sintaxe correta, se não retorna o erro ORA-xxx
correspondente
Passo 2: checada a semântica, ie, se os objetos que o SQL referencia existem, se o usuário que submeteu o SQL tem
acesso a eles, etc : se check falhou, exibir erro ORA-xx apropriado

Passo 3: no texto integral do SQL submetido é aplicada uma função interna de hash, reduzindo-o a um código
numérico de hash

Passo 4: o valor de hash é pesquisado na library cache

Passo 5: se o valor de hash não foi encontrado, é um hard parse, vá para passo 8

Passo 6: valor de hash foi encontrado, se o texto do SQL em análise difere do texto no cache, fazer hard parse, vá
para passo 8, caso contrário será necessária apenas uma breve verificação de status dos objetos referenciados no
SQL e no plano de execução existentes em cache, é o chamado SOFT PARSE

Passo 7: se SOFT PARSE foi bem sucedido, textos em cache e do SQL em análise são idênticos, se os objetos
referenciados no SQL em cache ainda são os mesmos e não houve alterações de ambiente, vá para passo 9 executar
o plano de acesso, caso contrário houve alteração de ambiente, forçar um hard parse indo para passo 8

Passo 8: hard parse, o texto do SQL vai para o cache, o otimizador (CBO – Cost Based Optimizer) entra em cena, o
SQL é analizado, diversos planos de execução são montados e avaliados, o melhor é escolhido (melhor aqui
entendido como aquele que na avaliação do CBO apresentará o menor Custo, ie, gastará menos CPUs, memória,
qtdade de blocos acessados, etc) – esta Avaliação é basicamente feita pelo CBO com informações coletadas
anteriormente, como quantidade de linhas, domínio dos valores presentes, etc . Os demais planos considerados são
descartados. Este passo é EXTREMAMENTE custoso, assim deve ser evitado, re-aproveitando-se SQLs sempre que
possível (ver BIND VARIABLES, abaixo)

Passo 9: executar o plano de acesso, e depois da execução manter o mesmo em cache, de modo que eventuais re-
execuções do exato mesmo texto deste SQL usem o mesmo plano.

DEMONSTRAÇÃO #1 :

Objetivo : exemplificar o mecanismo de parse e montagem de planos e demonstrar o efeito de cache e as estatísticas
de performance e monitoração dos SQLs built-in.

 Teremos duas sessões, uma executando SQLs e a outra efetuando a monitoração/demonstração


propriamente dita – a sessão executando ativará a coleta completa de estatísticas de SQL para que
possamos obter as informações de linhas no plano de execução :

HR:@orcl:SQL>alter session set statistics_level='ALL';

Session altered.

 Query que retorna apenas uma linha, veremos que o CBO optará por uso de índice :

HR:@orcl:SQL>select department_id, last_name, first_name, salary from employees where


DEPARTMENT_ID=10;

DEPARTMENT_ID LAST_NAME FIRST_NAME SALARY

------------- ------------------------- -------------------- ----------

10 Whalen Jennifer 4400

 Query idêntica mas com elemento de filtragem indeterminado mas possivelmente múltiplas linhas, o CBO irá optar por
um FULL TABLE SCAN , dado o tamanho relativamente pequeno da tabela :
HR:@orcl:SQL> select employee_id, first_name, last_name, salary from employees where DEPARTMENT_ID>0;

DEPARTMENT_ID LAST_NAME FIRST_NAME SALARY


------------- ------------------------- -------------------- ----------
90 King Steven 24000
90 Kochhar Neena 17000
90 De Haan Lex 17000
....
70 Baer Hermann 10000
110 Higgins Shelley 12000
110 Gietz William 8300

106 rows selected.

 Planos de Execução e detalhes dos SQLs (cache de SQLs) :

SYS:AS SYSDBA@orcl:SQL> select sql_id, sql_text, elapsed_time, buffer_gets, rows_processed from v$sql

where sql_text like 'select department_id, last_name, first_name, salary from employees where
DEPARTMENT_ID%';
SQL_ID

-------------

SQL_TEXT

--------------------------------------------------------------------------------------------------

ELAPSED_TIME BUFFER_GETS ROWS_PROCESSED

------------ ----------- --------------

fcgyhzfzwnkbn

select department_id, last_name, first_name, salary from employees where DEPARTMENT_ID>0

3299 26 106

99htt8hdqgwbv

select department_id, last_name, first_name, salary from employees where DEPARTMENT_ID=10

7377 17 1

=> Plano de Execução demonstrando Índice:

SYS:AS SYSDBA@orcl:SQL>select * from TABLE(DBMS_XPLAN.DISPLAY_CURSOR('99htt8hdqgwbv', null, 'ALLSTATS'));

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------------------
SQL_ID 99htt8hdqgwbv, child number 0
-------------------------------------
select department_id, last_name, first_name, salary from employees
where DEPARTMENT_ID=10

Plan hash value: 2056577954


-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time |Buffers |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 3 |
| 1 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 1 | 1 | 1 |00:00:00.01 | 3 |
|* 2 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX | 1 | 1 | 1 |00:00:00.01 | 2 |
-----------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("DEPARTMENT_ID"=10)
Note
-----
- dynamic sampling used for this statement (level=2)

24 rows selected.
=> Plano de Execução demonstrando table scan :

SYSDBA@orcl:SQL>select * from TABLE(DBMS_XPLAN.DISPLAY_CURSOR('fcgyhzfzwnkbn',null,'ALLSTATS'));

ABLE_OUTPUT
-------------------------------------------------------------------------------------------------------
--------------
fcgyhzfzwnkbn, child number 0
-------------------------------
department_id, last_name, first_name, salary from employees
DEPARTMENT_ID>0

ash value: 1445457117

-----------------------------------------------------------------------------------
| Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------
| SELECT STATEMENT | | 1 | | 106 |00:00:00.01 | 14 |
| TABLE ACCESS FULL| EMPLOYEES | 1 | 106 | 106 |00:00:00.01 | 14 |
-----------------------------------------------------------------------------------

ate Information (identified by operation id):


---------------------------------------------

filter("DEPARTMENT_ID">0)

ynamic sampling used for this statement (level=2)

selected.

SYSDBA@orcl:SQL>

Funcionamento do CBO

onalidade básica do CBO consiste em estimar o Custo (ie, o esforço/tempo/recursos gastos) nos diversas métodos possíveis
so aos dados para poder escolher o mais eficiente. O principal indicador é a quantidade de linhas esperadas a serem
adas pelo SQL.

stração : distribuição regular dos dados

o : demonstrar os recursos básicos utilizados pelo CBO

emos que numa apresentação há uma platéia de 1200 pessoas. Digamos que precisamos estimar quantas pessoas nasceram
de Dezembro – ora, se não tivermos infomação em contrário, suporemos que em cada mês do ano nascem o mesmo
o de pessoas, assim sabendo que há 12 meses no ano, a chance de uma pessoa nascer num mês qualquer (inclusive em
bro, que é o que nos interessa) é de 1 em 12, ou de 1/12 avos, que é aproximadamente igual a 0,0834. Assim, do total de
essoas, a estimativa seria de 1200 * 0,0834, que é 100. Assim, na falta de informações mais detalhadas, responderíamos que
soas nasceram em Dezembro.

emplo, embora simples, ilustra bem precisamente o comportamento básico do CBO, que supõe que os dados estão
ídos mais ou menos regularmente, não havendo nenhum valor possível com muito mais ou muito menos ocorre.

e continuarmos, porém, primeiro vamos empregar a nomenclatura adequada dos itens referentes à análise do CBO:

O Mês informado a pesquisar é válido -- validação da entrada, se fosse um mês inválido a


não continuaria

Há 12 possíveis meses -- número de valores distintos

Datas de nascimento estão igualmente distribuídas pelos meses -- distribuição dos dados
1/12 da platéia nasceu em um dado mês -- seletividade (ie, qual fração do total será acessada) –
e 1 , mas normalmente expressa em percentual

Total de 1200 pessoas -- cardinalidade base (ie, total da informação)

1/12 de 1200 é a resposta, ou seja, 100 -- cardinalidade computada

agora transformar isto num case via sqlplus :

:@orcl:SQL>create table PLATEIA(


pessoa number,
e_pessoa varchar2(80),
_aniv number);

:@orcl:SQL>create sequence seq_pessoa ;

:@orcl:SQL>BEGIN
for r in 1..12 loop
insert into PLATEIA (select seq_pessoa.nextval, chr(65+r) || object_name, r
from all_objects where rownum < 101);
end loop;
commit;
D;

procedure successfully completed.

etaremos estatísticas solicitando análise de valores apenas para as colunas indexadas (que não
m), portanto o CBO vai “chutar” esses valores :

:@orcl:SQL>exec dbms_stats.gather_table_stats(ownname=>user, tabname=>'PLATEIA', method_opt=>'FOR ALL


D COLUMNS SIZE 1', estimate_percent=>NULL, granularity=>'ALL', cascade=>TRUE) ;

procedure successfully completed.

ORTANTE : por default somente são registradas num Plano de Execução as informações mais básicas : para
os as informações Extendidas do plano, tais como linhas estimadas e processadas, devemos Ativar a
completa de estatísticas de SQL :

:@orcl:SQL>alter session set statistics_level=ALL;

n altered.

cutamos uma query :

:@orcl:SQL>SELECT COUNT(*) FROM PLATEIA WHERE MES_ANIV=12;

T(*)

----

100

amos que a Estimativa de linhas está totalmente defasada :

:@orcl:SQL>select sql_id from v$sql where sql_text like 'SELECT COUNT(*) FROM PLATEIA WHERE
IV=12%';

-------

18umt04

:@orcl:SQL>select * from table(dbms_xplan.display_cursor('gzvkc618umt04', null, 'ALLSTATS'));


ABLE_OUTPUT

----------------------------------------------------------------------------------------------------

gzvkc618umt04, child number 0

-------------------------------

COUNT(*) FROM PLATEIA WHERE MES_ANIV=12

ash value: 3474681971

----------------------------------------------------------------------------------
| Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
----------------------------------------------------------------------------------
| SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 5 |
| SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.01 | 5 |
| TABLE ACCESS FULL| PLATEIA | 1 | 12 | 100 |00:00:00.01 | 5 |
----------------------------------------------------------------------------------

ate Information (identified by operation id):

---------------------------------------------

filter("MES_ANIV"=12)

s selected.

estimativa de linhas do CBO passo MUITO LONGE da real : existem MUITO MAIS que 12 linhas, por que isso
u ? Porque o CBO ** NÃO TINHA ** a informação de que só há 12 meses possíveis – ele possui a informação
de linhas da tabela (cardinalidade-base) :

:@orcl:SQL>select NUM_ROWS, BLOCKS, EMPTY_BLOCKS, SAMPLE_SIZE, LAST_ANALYZED from user_tables where


name='PLATEIA';

ROWS BLOCKS EMPTY_BLOCKS SAMPLE_SIZE LAST_ANAL

---- ---------- ------------ ----------- ---------

1200 4 0 1200 19-MAY-14

não possui a informação de Cardinalidade para cada coluna :

:@orcl:SQL>column low_value format a15


:@orcl:SQL>column high_value format a15
:@orcl:SQL>column column_name format a18

:@orcl:SQL> select COLUMN_NAME, NUM_DISTINCT, LOW_VALUE, HIGH_VALUE,


NULLS, NUM_BUCKETS, LAST_ANALYZED, SAMPLE_SIZE
om user_tab_columns where table_name='PLATEIA';

_NAME NUM_DISTINCT LOW_VALUE HIGH_VALUE NUM_NULLS NUM_BUCKETS LAST_ANAL SAMPLE_SIZE


------------ ------------ --------------- --------------- --------- ----------- --------- -----------
SOA
ESSOA
IV
:@orcl:SQL>

o Procedimento para se coletar informação sobre a Cardinalidade é através de uma coleta de estatísticas que incluam a(s)
una(s) em questão : no exemplo, coletaremos estatísticas sobre todas as colunas, mas é Totalmente possível coletar para
umas colunas apenas :

SYSTEM:@orcl:SQL>exec dbms_stats.gather_table_stats(ownname=>user, tabname=>'PLATEIA',


method_opt=>'FOR ALL COLUMNS SIZE 1', estimate_percent=>NULL, granularity=>'ALL', cascade=>TRUE);

PL/SQL procedure successfully completed.

SYSTEM:@orcl:SQL>select COLUMN_NAME, NUM_DISTINCT, LOW_VALUE, HIGH_VALUE,


NUM_NULLS, NUM_BUCKETS, LAST_ANALYZED, SAMPLE_SIZE
from user_tab_columns where table_name='PLATEIA';

COLUMN_NAME NUM_DISTINCT LOW_VALUE HIGH_VALUE NUM_NULLS NUM_BUCKETS


LAST_ANAL SAMPLE_SIZE
------------------ ------------ --------------- --------------- ---------- ----------- --------
- -----------
ID_PESSOA 1200 C102 C20D 0 1 19-MAY-
14 1200
NOME_PESSOA 1200 42424F4F5453545 4D5649455724 0 1 19-MAY-
14 1200
MES_ANIV 12 C102 C10D 0 1 19-MAY-
14 1200

SYSTEM:@orcl:SQL>

(a informação sobre o domínio/valores possíveis coletados fica armazenada em raw, portanto vamos
criar uma pequena procedure para fazer a conversão) :

SYSTEM:@orcl:SQL>create or replace function display_raw (rawval raw, type varchar2)


return varchar2
is
cn number;
cv varchar2(32);
cd date;
cnv nvarchar2(32);
cr rowid;
cc char(32);
cbf binary_float;
cbd binary_double;
begin
if (type = 'VARCHAR2') then
dbms_stats.convert_raw_value(rawval, cv);
return to_char(cv);
elsif (type = 'DATE') then
dbms_stats.convert_raw_value(rawval, cd);
return to_char(cd);
elsif (type = 'NUMBER') then
dbms_stats.convert_raw_value(rawval, cn);
return to_char(cn);
elsif (type = 'BINARY_FLOAT') then
dbms_stats.convert_raw_value(rawval, cbf);
return to_char(cbf);
elsif (type = 'BINARY_DOUBLE') then
dbms_stats.convert_raw_value(rawval, cbd);
return to_char(cbd);
elsif (type = 'NVARCHAR2') then
dbms_stats.convert_raw_value(rawval, cnv);
return to_char(cnv);
elsif (type = 'ROWID') then
dbms_stats.convert_raw_value(rawval, cr);
return to_char(cr);
elsif (type = 'CHAR') then
dbms_stats.convert_raw_value(rawval, cc);
return to_char(cc);
else
return 'UNKNOWN DATATYPE';
end if;
end;
/

Function created.

=> agora sim a consulta :

SYSTEM:@orcl:SQL>select COLUMN_NAME, NUM_DISTINCT, display_raw(low_value, data_type)


LOW_VALUE, display_raw(high_value, data_type) HIGH_VALUE ,
NUM_NULLS, NUM_BUCKETS, LAST_ANALYZED, SAMPLE_SIZE
from user_tab_columns where table_name='PLATEIA';

COLUMN_NAME NUM_DISTINCT LOW_VALUE HIGH_VALUE NUM_NULLS NUM_BUCKETS LAST_ANAL


SAMPLE_SIZE
------------------ ------------ --------------- --------------- ---------- ----------- ---------
-----------
ID_PESSOA 1200 1 1200 0 1 19-MAY-14
1200
NOME_PESSOA 1200 BBOOTSTRAP$ MVIEW$ 0 1 19-MAY-14
1200
MES_ANIV 12 1 12 0 1 19-MAY-14
1200

ora sim o CBO possui a informação necessária, ele “sabe” que há 12 meses possíveis, portanto (ainda supondo distribuição
al) qualquer valor usado como filtro deve retornar 1/12 do total de 1200 linhas :

(primeiro, vamos executar o SQL anterior com alterações mínimas no texto para que seja feito um novo PARSE e
portanto montado um novo Plano, agora se aproveitando das informações adicionais)

SYSTEM:@orcl:SQL>SELECT COUNT(*) RESULT FROM PLATEIA WHERE MES_ANIV=12;

RESULT
------------------------
100

SYSTEM:@orcl:SQL>select sql_id from v$sql where sql_text like 'SELECT COUNT(*) RESULT FROM
PLATEIA WHERE MES_ANIV=12%';

SQL_ID
-------------
g7bumkdzhs4c1

=> eis o resultado, ver que agora o CBO fez a estimativa adequada :

SYSTEM:@orcl:SQL>select * from table(dbms_xplan.display_cursor('g7bumkdzhs4c1', null,


'ALLSTATS'));

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------
---------------------------------
SQL_ID g7bumkdzhs4c1, child number 0
-------------------------------------
SELECT COUNT(*) RESULT FROM PLATEIA WHERE MES_ANIV=12

Plan hash value: 3474681971

----------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 5 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.01 | 5 |
|* 2 | TABLE ACCESS FULL| PLATEIA | 1 | 100 | 100 |00:00:00.01 | 5 |
----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):


---------------------------------------------------

2 - filter("MES_ANIV"=12)

19 rows selected.

SYSTEM:@orcl:SQL>

Avançando um pouco mais : uma das principais pontos do CBO é que,sem informação extra, da única maneira que
ele pode ele supõe que a distribuição é regular, ie, que cada um dos valores distintos de uma coluna retorna mais ou
menos o mesmo número de linhas se usado como filtro num WHERE – no caso recém-apresentado isso era verdade,
vamos alterar o caso para que não o seja mais :
SYSTEM:@orcl:SQL>insert into PLATEIA (select object_id, object_name, 12 from all_objects);

73559 rows created.


SYSTEM:@orcl:SQL>commit;

Commit complete.

SYSTEM:@orcl:SQL>create index IDX_PLATEIA on PLATEIA(MES_ANIV);

Index created.

SYSTEM:@orcl:SQL>select mes_aniv, count(*) from PLATEIA group by mes_aniv;

MES_ANIV COUNT(*)
---------- ----------
1 100
6 100
11 100
2 100
4 100
5 100
8 100
3 100
7 100
9 100
10 100
12 73659

12 rows selected.

==> ou seja, a distribuição deixou de ser uniforme. Vamos re-calcular estatísticas SEM informar
isso :

SYSTEM:@orcl:SQL> exec dbms_stats.gather_table_stats(ownname=>user, tabname=>'PLATEIA',


method_opt=>'FOR ALL COLUMNS SIZE 1', estimate_percent=>NULL, granularity=>'ALL', cascade=>TRUE,
no_invalidate=>false);

PL/SQL procedure successfully completed.

 a opção de SIZE acima indica quantas "amostras" para cada valor distinto devem ser tomadas,
se especificado 1, uma única amostra não serve para indicativo, na prática é desprezada e o CBO
considera distribuição regular, e a opção de NO_INVALIDATE desativada faz com que os planos
anteriores que dependiam das estatísticas antigas sejam invalidados, é o que queremos neste
caso. Vamos ver o comportamento do CBO :

SYSTEM:@orcl:SQL>select sql_id from v$sql where sql_text like 'SELECT COUNT(*) RESULT FROM
PLATEIA WHERE MES_ANIV=12%';

SQL_ID

-------------

g7bumkdzhs4c1
SYSTEM:@orcl:SQL>select * from table(dbms_xplan.display_cursor('g7bumkdzhs4c1', null,
'ALLSTATS'));

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------
---------------------------------
SQL_ID g7bumkdzhs4c1, child number 0
-------------------------------------
SELECT COUNT(*) RESULT FROM PLATEIA WHERE MES_ANIV=12

Plan hash value: 1688887100

-------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.11 | 146 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.11 | 146 |
|* 2 | INDEX RANGE SCAN| IDX_PLATEIA | 1 | 6230 | 73659 |00:00:00.06 | 146 |
-------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):


---------------------------------------------------

2 - access("MES_ANIV"=12)

19 rows selected.

SYSTEM:@orcl:SQL>

==> mais uma vez, estimativa de linhas TOTALMENTE off, o select anterior já nos mostrou que há
muitas mais, esse Cardinalidade está longe da real, ** E ** ( o mais importante) devido à isso o
CBO optou por fazer um INDEX RANGE SCAN, ie, vai ler um a um as chaves do índice que se
encontram no intervalo : isso seria ótimo se estivéssemos realmente recuperando poucas linhas,
mas no caso de um grande universo de linhas, muito mais eficiente seria ser (em operação
multibloco) os extents inteiros do índice, de maneira similar ao FULL TABLE SCAN...

Vamos ofertar ao CBO a informação de distribuição de valores, criando HISTOGRAMAS (que é o nome
técnico desse levantamento de distribuição de valores distintos em colunas), isso se faz
colocando um SIZE não-único e veremos que a estimativa será ajustada de acordo :

SYSTEM:@orcl:SQL> exec dbms_stats.gather_table_stats(ownname=>user, tabname=>'PLATEIA',


method_opt=>'FOR ALL COLUMNS SIZE AUTO', estimate_percent=>NULL, granularity=>'ALL',
cascade=>TRUE, no_invalidate=>FALSE);

PL/SQL procedure successfully completed.

SYSTEM:@orcl:SQL> SELECT COUNT(*) RESULT FROM PLATEIA WHERE MES_ANIV=12;

RESULT

------------------------

73,659

SYSTEM:@orcl:SQL>select sql_id from v$sql where sql_text like 'SELECT COUNT(*) RESULT FROM
PLATEIA WHERE MES_ANIV=12%';

SQL_ID
-------------

g7bumkdzhs4c1

SYSTEM:@orcl:SQL>select * from table(dbms_xplan.display_cursor('g7bumkdzhs4c1', null,


'ALLSTATS'));

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------
---------------------------------
SQL_ID g7bumkdzhs4c1, child number 0
-------------------------------------
SELECT COUNT(*) RESULT FROM PLATEIA WHERE MES_ANIV=12

Plan hash value: 3127632608

-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.08 | 154 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.08 | 154 |
|* 2 | INDEX FAST FULL SCAN| IDX_PLATEIA | 1 | 73659 | 73659 |00:00:00.04 | 154 |
-----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):


---------------------------------------------------

2 - filter("MES_ANIV"=12)

19 rows selected.

OBS : a cláusula AUTO utilizada acima para o SIZE dos histogramas tenta fazer uma coleta mediana, nem muito curta
nem muito completa – em alguns casos ela pode não fornecer uma amostra estatisticamente significativa, aí se
deverá fornecer um tamanho diretamente. Quanto maior o SIZE mais amostras serão coletadas, o que vai demandar
mais tempo e processamento/esforço por parte do RDBMS. Devido à isso , o limite máximo de tamanho para
histograma é 254.

Bind variables : conforme já apresentado anteriormente, o reconhecimento de um SQL já presente no cache de SQLs
para fins de re-utilização do plano é através da comparação do texto do SQL – assim sendo, além de padronização
num ambiente (para que não se termine com múltiplos SQLs diferentes apenas por espaços, ordem das colunas,
comentários e etc), é vital, caso o ambiente seja do tipo OLTP (ie, com muitas e múltiplas instruções SQL
simultâneas) que se utilize o recurso de BIND VARIABLEs , a fim de evitarmos SQLs diferentes apenas nos valores de
entrada.

Exemplo : digamos que necessitamos consultar os empregados com os códigos 100, 101 e 102 , vejamos o
comportamento do RDBMS se eu informar os valores diretamente :

HR:@orcl:SQL>select employee_id, first_name, last_name, salary from employees where employee_id


= 100;

EMPLOYEE_ID FIRST_NAME LAST_NAME SALARY


----------- -------------------- ------------------------- ----------
100 Steven King 24000

HR:@orcl:SQL>select employee_id, first_name, last_name, salary from employees where employee_id


= 101;

EMPLOYEE_ID FIRST_NAME LAST_NAME SALARY


----------- -------------------- ------------------------- ----------
101 Neena Kochhar 17000
HR:@orcl:SQL>select employee_id, first_name, last_name, salary from employees where employee_id
= 102;

EMPLOYEE_ID FIRST_NAME LAST_NAME SALARY


----------- -------------------- ------------------------- ----------
102 Lex De Haan 17000

HR:@orcl:SQL>

 em outra sessão vamos consultar o cache de SQLs e as estatísticas de execução do texto


SQL informado :

SYSTEM:@orcl:SQL>column sql_text format a45


SYSTEM:@orcl:SQL>select sql_id, sql_text, executions, rows_processed from v$sql
where sql_text like 'select employee_id, first_name, last_name, salary from employees where
employee_id =%';

SQL_ID SQL_TEXT EXECUTIONS ROWS_PROCESSED


------------- --------------------------------------------- ---------- --------------
4ns4xjs555fpu select employee_id, first_name, last_name, sa 1 1
lary from employees where employee_id = 101

fumj64jwp2qgq select employee_id, first_name, last_name, sa 1 1


lary from employees where employee_id = 102

dbsbkhz883fx8 select employee_id, first_name, last_name, sa 1 1


lary from employees where employee_id = 100

 ou seja , como havia a mínima diferença devido aos valores diferentes, cada Execução foi considerada um
SQL diferente, portanto cada uma precisou de um PARSE, cada uma ocupou memória no cache de SQL, não
houve reaproveitamento....

Agora vamos aplicar o conceito de BIND VARIABLE, criando uma variável no programa/linguagem/tool de
programação cliente em uso e referenciando essa variável no texto do SQL, que será sempre o mesmo – no caso
do sqlplus, que estamos usando aqui, o comando apropriado é o comando VARIABLE, mas necessariamente a
Esmagadora maioria das tools/linguagens de programação Possui algum comando similar :

HR:@orcl:SQL>variable V_ID number;


HR:@orcl:SQL>exec :V_ID := 100;

PL/SQL procedure successfully completed.

HR:@orcl:SQL>print V_ID

V_ID
----------
100

HR:@orcl:SQL>select employee_id, first_name, last_name, salary from employees where


employee_id = :V_ID;

EMPLOYEE_ID FIRST_NAME LAST_NAME SALARY


----------- -------------------- ------------------------- ----------
100 Steven King 24000

HR:@orcl:SQL>exec :V_ID := 101;

PL/SQL procedure successfully completed.

HR:@orcl:SQL>select employee_id, first_name, last_name, salary from employees where


employee_id = :V_ID;

EMPLOYEE_ID FIRST_NAME LAST_NAME SALARY


----------- -------------------- ------------------------- ----------
101 Neena Kochhar 17000

HR:@orcl:SQL>exec :V_ID := 102;

PL/SQL procedure successfully completed.


HR:@orcl:SQL>select employee_id, first_name, last_name, salary from employees where
employee_id = :V_ID;

EMPLOYEE_ID FIRST_NAME LAST_NAME SALARY


----------- -------------------- ------------------------- ----------
102 Lex De Haan 17000

 resultado :

SYSTEM:@orcl:SQL>select sql_id, sql_text, executions, rows_processed, from v$sql


where sql_text = 'select employee_id, first_name, last_name, salary from employees where
employee_id = :V_ID';

SQL_ID SQL_TEXT EXECUTIONS ROWS_PROCESSED


------------- --------------------------------------------- ---------- --------------
4m8dmxbytra9s select employee_id, first_name, last_name, sa 3 3
lary from employees where employee_id = :V_ID

SYSTEM:@orcl:SQL>

 apenas um SQL foi armazenado no cache, e o plano gerado para esse único foi re-aproveitado.
OBS : no módulo de PL/SQL, será referenciado que não só automaticamente os textos SQL presentes em
programas PL/SQL já são convertidos para maiúsculas e tem espaços em branco redundantes eliminados
como Também toda variável PL/SQL citada neles será usada como BIND VARIABLE automaticamente – assim
sendo, uma Opção a se considerar se a tool/linguagem de programação apresentar dificuldades para
BINDING e para padronização de SQLs, é encapsular os comandos SQL necessários em stored PL/SQL , sejam
procedures, Functions ou Packages.

===============================================================================================
=============================================

Definição de um Plano de Execução : um Plano de Execução pode ser definido como uma sequência de passos (cada
passo definido como uma Operação lógica num database). Cada passo produz um rowset que será enviado ao passo
hierarquicamente superior, até que o rowset final seja encontrado e enviado ao cliente/solicitante. Planos de
Execução são comumente representados em formato tabular, com as operações-pai indentadas à esquerda das
operações-filho que as alimentam, mas podem ser também representados por uma árvore.

OBS : a qualquer momento é possível se forçar um hard parse de um texto SQL (e


portanto ser gerado um plano de execução para ele) sem que o SQL seja
executado, através do comando EXPLAIN PLAN FOR textodosqlaanalisar, e depois
exibir-se o plano com o script UTLXPLS.SQL fornecido pela Oracle ou com o
comando SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY());
Nos exemplos abaixo, porém, vamos extrair dos caches o plano já gerado.

Exemplo - digamos que eu tenha a query abaixo (a hint de obtenção de estatísticas de execução do plano é apenas
para obtermos os detalhes extendidos no plano de Execução) :
HR:@orcl:SQL> select d.DEPARTMENT_NAME, min(e.salary), max(e.salary), count(*)
from departments d, employees e
where d.department_id = e.department_id
and d.department_id = 60
* group by d.department_name;

DEPARTMENT_NAME MIN(E.SALARY) MAX(E.SALARY) COUNT(*)


------------------------------ ------------- ------------- ----------
IT 4200 9000 5

HR:@orcl:SQL>
 vamos verificar o Plano de Execução – o primeiro passo é localizar o ID, a sequência alfanumérica que todo
SQL residente no cache possui :

SYS:AS SYSDBA@orcl:SQL>select sql_id, prev_sql_id from v$session where username='HR';

SQL_ID PREV_SQL_ID
------------- -------------
5150gs690qykc

SYS:AS SYSDBA@orcl:SQL>select * from table(dbms_xplan.display_cursor('5150gs690qykc',null));

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------
-------
SQL_ID 5150gs690qykc, child number 0
-------------------------------------
select d.DEPARTMENT_NAME, min(e.salary),
max(e.salary), count(*) from departments d, employees e where
d.department_id = e.department_id and d.department_id = 60 group by
d.department_name

Plan hash value: 89707066

------------------------------------------------------------------------------------------------
---
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time
|
------------------------------------------------------------------------------------------------
---
| 0 | SELECT STATEMENT | | | | 1 (100)|
|
| 1 | HASH GROUP BY | | 5 | 280 | 1 (0)|
00:00:01 |
| 2 | NESTED LOOPS | | 5 | 280 | 1 (0)|
00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| DEPARTMENTS | 1 | 30 | 0 (0)|
|
|* 4 | INDEX UNIQUE SCAN | DEPT_ID_PK | 1 | | 0 (0)|
|
| 5 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 5 | 130 | 1 (0)|
00:00:01 |
|* 6 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX | 5 | | 0 (0)|
|
------------------------------------------------------------------------------------------------
---
Predicate Information (identified by operation id):
---------------------------------------------------

4 - access("D"."DEPARTMENT_ID"=60)
6 - access("E"."DEPARTMENT_ID"=60)

Note
-----
- dynamic sampling used for this statement (level=2)

31 rows selected.

SYS:AS SYSDBA@orcl:SQL>

(para que a identação fique mais visível, vamos traçar linhas coloridas nos níveis) :

------------------------------------------------------------------------------------------------
---
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time
|
------------------------------------------------------------------------------------------------
---
| 0 | SELECT STATEMENT | | | | 1 (100)|
|
| 1 | HASH GROUP BY | | 5 | 280 | 1 (0)|
00:00:01 |
| 2 | NESTED LOOPS | | 5 | 280 | 1 (0)|
00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| DEPARTMENTS | 1 | 30 | 0 (0)|
|
|* 4 | INDEX UNIQUE SCAN | DEPT_ID_PK | 1 | | 0 (0)|
|
| 5 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 5 | 130 | 1 (0)|
00:00:01 |
|* 6 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX | 5 | | 0 (0)|
|
------------------------------------------------------------------------------------------------
---

 no exemplo acima, o comando cujo resultado vai ser enviado ao solicitante(o SELECT, passo 0) recebe os
resultados do HASH GROUP BY (passo 1), já que o passo 1 está endentado em relação à ele (linha azul) . No
nível abaixo do HASH GROUP BY (linha vermelha) encontramos um NESTED LOOP (um loop de repetição),
indicando que os passos endentados em relação à ele vão ser repetidos (os passos abaixo do nível indicado
pela linha amarela). Os alimentadores do NESTED LOOP (passos 3 e 5, os TABLE ACCESS) estão no mesmo
nível (isso está indicado pela linha verde) , o que implica que são independentes entre si , podendo inclusive
ser executados simultaneamente em paralelo pelo RDBMS. Os passos mais endentados em relação a cada
TABLE ACESS são os seus alimentadores (passos 4 e 6).
Lendo o plano logicamente, o pseudocódigo ficaria :

a. início do loop
b. se ainda não foi feita, fazer uma busca única (INDEX UNIQUE) no índice DEPT_ID_PK aonde
DEPARTMENT_ID=60 (cláusula 4 - access("D"."DEPARTMENT_ID"=60) de filtro, acima), e com
o valor encontrado acessar o registro correspondente na tabela DEPARTMENTS – armazenar
a informação encontrada numa tabela temporária, em memória, que o RDBMS vai construir
para isso (uma HASH TABLE)
c. ler o valor corrente (ou o primeiro valor, se começou agora) do índice
EMP_DEPARTMENT_IX aonde DEPARTMENT_ID=60 (claúsula 6 - access("E"."DEPARTMENT_ID"=60),
acima) , e com esse valor buscar o registro na tabela EMPLOYEES – armazenar a
informação encontrada numa tabela temporária, em memória que o RDBMS vai construir
para isso (uma HASH TABLE)
d. como é um INDEX RANGE SCAN, não único, pode haver N registros com a chave especificada :
movimentar o ponteiro lógico para a próxima ocorrência de DEPARTMENT_ID=60 no índice
EMP_DEPARTMENT_IX
e. se não finalizou a leitura do índice, goto a., se sim quebrar o loop
f. após o loop encerrado, os resultados vão estar nas hash tables, que alimentarão o passo
hierarquicamente superior (o HASH GROUP BY), que será o encarregado de processar as hash tables e
agrupar a informação
g. a informação agrupada alimenta o SELECT, que enviará o resultset final para o cliente

A representação gráfica, em árvore, desse SQL ficaria :

SELECT

HASH GROUP BY
TABLE ACCESS DEPARTMENTS via INDEX TABLE ACCESS
EMPLOYEES via INDEX

Potrebbero piacerti anche