Bom, pra não ficar só no blablablá vou te passar um exemplo que usei num 
Treinamento particular que dei há um tempo atrás - no caso o Treinamento foi em 
11gR2 SE mas o conceito Funciona em outras editions/versões :


BIND PEEKING : porém, as BIND VARIABLEs abrem um outro senão, um Outro Ponto 
possível de divergência no que tange a performance : como vimos anteriormente, 
o Otimizador precisa conhecer os VALORES informados como argumentos de 
filtro/pesquisa de colunas no momento em que o PARSE inicial é feito e o plano 
de execução é montado.. Para isso foi introduzido no banco 9i o conceito de 
BIND PEEKING, ie : o Otimizador espera até que o SQL seja efetivamente 
executado para só então capturar o valor informado nas BINDs e aí sim montar e 
otimizar o Plano de execução - isso pode levar a uma mínima demora na primeira 
vez que o SQL é executado mas o tempo demonstrou que esse overhead é mínimo.. 
As próximas execuções, OBVIAMENTE, vão simplesmente reaproveitar esse mesmo 
plano, seja quais forem os valores informados para a BIND variable... VIA DE 
REGRA isso funciona bem (principalmente com acesso a registros via chave 
completa, já que uma chave completa ocorre só uma vez na tabela, assim qualquer 
valor chave retorna exatamente 1 registro), MAS para acesso com chave parcial 
(ou com colunas indexadas e com histogramas mas que não são chave) isso pode 
causar problemas... E não, não é viável refazer o Plano de Execução / reparsear 
o SQL a cada nova execução : o RDBMS Oracle é programado/arquitetado para poder 
servir a dezenas (ou mesmo centenas) de usuários simultâneos, e potencialmente 
todos eles podem estar executando SQLs com binds, o overhead impostos a cada 
Execução seria inviável....

Exemplo de SQL sujeito a issues de BIND VARIABLE PEEKING :

=> Setup, criando uma tabela com dados mal-distribuídos :

SYSTEM:@O11GR2SE:SQL>DROP TABLE acs_test_tab;
DROP TABLE acs_test_tab
           *
ERRO na linha 1:
ORA-00942: a tabela ou view não existe


SYSTEM:@O11GR2SE:SQL>
SYSTEM:@O11GR2SE:SQL>CREATE TABLE acs_test_tab (
  2    id          NUMBER,
  3    record_type NUMBER,
  4    description VARCHAR2(50),
  5    CONSTRAINT acs_test_tab_pk PRIMARY KEY (id)
  6  );

Tabela criada.

SYSTEM:@O11GR2SE:SQL>CREATE INDEX acs_test_tab_record_type_i ON 
acs_test_tab(record_type);

Índice criado.

==> vamos fazer a tabela ter uma distribuição Irregular de dados na coluna 
RECORD_TYPE, com qtdades diferentes de linhas para RECORD_TYPE=2 (50000) e para 
outros valores possíveis da coluna haverá apenas um registro :

SYSTEM:@O11GR2SE:SQL>DECLARE
  2    TYPE t_acs_test_tab IS TABLE OF acs_test_tab%ROWTYPE;
  3    l_tab t_acs_test_tab := t_acs_test_tab();
  4
  5  BEGIN
  6    FOR i IN 1 .. 100000 LOOP
  7      l_tab.extend;
  8      IF MOD(i,2)=0 THEN
  9        l_tab(l_tab.last).record_type := 2;
 10      ELSE
 11        l_tab(l_tab.last).record_type := i;
 12      END IF;
 13
 14      l_tab(l_tab.last).id          := i;
 15      l_tab(l_tab.last).description := 'Description for ' || i;
 16    END LOOP;
 17    --
 18    FORALL i IN l_tab.first .. l_tab.last
 19      INSERT INTO acs_test_tab VALUES l_tab(i);
 20
 21    COMMIT;
 22  END;
 23  /

Procedimento PL/SQL concluído com sucesso.

==> coelta de estatísticas :

SYSTEM:@O11GR2SE:SQL>EXEC DBMS_STATS.gather_table_stats(USER, 'acs_test_tab', 
method_opt=>'for all indexed columns size skewonly', cascade=>TRUE);

Procedimento PL/SQL concluído com sucesso.

SYSTEM:@O11GR2SE:SQL>SELECT column_name, histogram FROM user_tab_cols WHERE  
table_name = 'ACS_TEST_TAB';

COLUMN_NAME                    HISTOGRAM
------------------------------ ---------------
ID                             NONE
RECORD_TYPE                    HEIGHT BALANCED
DESCRIPTION                    NONE


==> O método de coleta usado ao invés de histogramas de tamanho fixo vai criar 
histogramas de tamanho variável cfrme a distribuição de dados em cada coluna 
seja regular ou irregular ...  Executando o SQL :

 
SYSTEM:@O11GR2SE:SQL>VARIABLE l_record_type NUMBER;
SYSTEM:@O11GR2SE:SQL>EXEC :l_record_type := 1;

Procedimento PL/SQL concluído com sucesso.

SYSTEM:@O11GR2SE:SQL>
SYSTEM:@O11GR2SE:SQL>SELECT MAX(id) FROM acs_test_tab WHERE record_type = 
:l_record_type;

   MAX(ID)
----------
         1

SYSTEM:@O11GR2SE:SQL>SELECT * FROM TABLE(DBMS_XPLAN.display_cursor);

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------------------------------------
SQL_ID  9bmm6cmwa8saf, child number 0
-------------------------------------
SELECT MAX(id) FROM acs_test_tab WHERE record_type = :l_record_type

Plan hash value: 3987223107

-----------------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name                       | Rows  | 
Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                            |       |     
  |     2 (100)|       |
|   1 |  SORT AGGREGATE              |                            |     1 |     
9 |            |       |
|   2 |   TABLE ACCESS BY INDEX ROWID| ACS_TEST_TAB               |     1 |     
9 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | ACS_TEST_TAB_RECORD_TYPE_I |     1 |     
  |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access("RECORD_TYPE"=:L_RECORD_TYPE)


20 linhas selecionadas.

==> Esse plano é Ótimo, pois para o RECORD_TYPE=1 realmente sei que só exite 
uma linha mesmo, é Eficiente buscar poucas linhas via índice...Porém, como está 
ativo o BIND PEEKING, este é o Plano que será usado daqui pra frente, mesmo que 
outros valores sejam informados :

SYSTEM:@O11GR2SE:SQL>EXEC :l_record_type := 2;

Procedimento PL/SQL concluído com sucesso.

SYSTEM:@O11GR2SE:SQL>
SYSTEM:@O11GR2SE:SQL>SELECT MAX(id) FROM acs_test_tab WHERE record_type = 
:l_record_type;

   MAX(ID)
----------
    100000

SYSTEM:@O11GR2SE:SQL>SELECT * FROM TABLE(DBMS_XPLAN.display_cursor);

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------------------------------------
SQL_ID  9bmm6cmwa8saf, child number 0
-------------------------------------
SELECT MAX(id) FROM acs_test_tab WHERE record_type = :l_record_type

Plan hash value: 3987223107

-----------------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name                       | Rows  | 
Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                            |       |     
  |     2 (100)|       |
|   1 |  SORT AGGREGATE              |                            |     1 |     
9 |            |       |
|   2 |   TABLE ACCESS BY INDEX ROWID| ACS_TEST_TAB               |     1 |     
9 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | ACS_TEST_TAB_RECORD_TYPE_I |     1 |     
  |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access("RECORD_TYPE"=:L_RECORD_TYPE)


20 linhas selecionadas.

SYSTEM:@O11GR2SE:SQL>

===> Este Plano está completamente mal-direcionado, nós SABEMOS como um fato 
que Existem mais de uma linha para RECORD_TYPE=2... 
 
 O que se pode fazer ? Até a versão 10g não havia solução direta, nós 
poderíamos :
 
 a) forçar um HARD PARSE (e portanto um PEEK nas BIND VARIABLES) a cada 
execução, forçando o aplicativo a cada vez enviar o texto do SQL sujeito a bind 
issues com um pequena alteração, um comentário talvez que seja
 
 ou
 
 b) zerar TODO o cache de SQLs (e portanto zerar também o cache de planos) via 
FLUSH com a sintaxe ALTER SYSTEM FLUSH SHARED_POOL;
 
 ou
 
 c) remover esse dado SQL bind sensitive (e consequentemente seu Plano de 
Execução) do cache, com o comando DBMS_SHARED_POOL.PURGE - vide no site de 
Suporte (pago) Oracle metalink/My Oracle Support a nota "How To Flush an Object 
Out The Library Cache [SGA] Using The DBMS_SHARED_POOL Package" (Doc ID 
457309.1) 
 
 ou 
 
 d) INVALIDAR esse SQL (e portanto seus planos), forçando um novo PARSE na 
próxima execução : isso pode ser feito coletando novas estatísticas via 
DBMS_STATS com o parâmetro NO_INVALIDATE marcado como FALSE, OU fazendo uma 
alteração simples de estrutura na tabela que não altere os dados (um simples 
COMMENT ON TABLE  nomedatabela IS ''; já é suficiente) : vide o artigo para 
mais refs/exemplos

 ou
 
 e) desativar o BIND variable peeking : isso pode ser feito ativando o 
parâmetro '_optim_peek_user_binds' (com Aprovação do Suporte Oracle, já que é 
um parâmetro interno / "oculto")
 
 ou
 
 f) forçar o Plano a ser sempre o mesmo não importando o valor dos BINDs, seja 
desativando Histogramas (não os coletando, ou os coletando-os com SIZE 1), seja 
fazendo o plano ser sempre o mesmo com a introdução de hints
 
 (Para detalhamento destas opções até a versão 10g, a ref é o artigo 
https://blog.pythian.com/stabilize-oracle-10gs-bind-peeking-behaviour/)
 
 No 11g em diante, porém, passamos a ter a figura do Adaptive Cursor Sharing : 
com esse recurso, o RDBMS Oracle já 'marcou' o SQL em questão como 
BIND-SENSITIVE :
 
SYSTEM:@O11GR2SE:SQL>SELECT sql_id, child_number, is_bind_sensitive, 
is_bind_aware
  2  FROM   v$sql
  3  WHERE  sql_text = 'SELECT MAX(id) FROM acs_test_tab WHERE record_type = 
:l_record_type';

SQL_ID        CHILD_NUMBER I I
------------- ------------ - -
9bmm6cmwa8saf            0 Y N

SYSTEM:@O11GR2SE:SQL>

==> na próxima Execução um SQL marcado como BIND-SENSITIVE (ou BIND AWARE, na 
terminologia Oracle) Automaticamente vai re-analisar Histogramas e 
potencialmente re-escrever (para melhor, espera-se) o Plano de Execução :

SYSTEM:@O11GR2SE:SQL>SELECT MAX(id) FROM acs_test_tab WHERE record_type = 
:l_record_type;

   MAX(ID)
----------
    100000

SYSTEM:@O11GR2SE:SQL>SELECT * FROM TABLE(DBMS_XPLAN.display_cursor);

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------------------
SQL_ID  9bmm6cmwa8saf, child number 1
-------------------------------------
SELECT MAX(id) FROM acs_test_tab WHERE record_type = :l_record_type

Plan hash value: 509473618

-----------------------------------------------------------------------------------
| Id  | Operation          | Name         | Rows  | Bytes | Cost (%CPU)| Time   
  |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |              |       |       |   138 (100)|        
  |
|   1 |  SORT AGGREGATE    |              |     1 |     9 |            |        
  |
|*  2 |   TABLE ACCESS FULL| ACS_TEST_TAB | 48819 |   429K|   138   (1)| 
00:00:02 |
-----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("RECORD_TYPE"=:L_RECORD_TYPE)


19 linhas selecionadas.

SYSTEM:@O11GR2SE:SQL>

==> Agora sim : o Plano reconheceu que há muito mais que uma linha, E um FULL 
TABLE SCAN vai fazer muito menos chamadas ao sub-sistema de I/O, já solicitando 
o máximo de blocos que o SO aceita a cada chamada, potencialmente completando a 
tarefa de ler a tabela TODA muito mais rapidamente... 
 È importante notar que esse novo Plano NÂO SUBSTITUIU o Plano original, o 
RDBMS reconheceu que é uma derivação do plano original - assim ele criou o que 
se chama de CHILD CURSOR, uma nova entrada na cache para manter o novo plano 
para este valor específico de BIND :
 
 SYSTEM:@O11GR2SE:SQL>SELECT sql_id, child_number, is_bind_sensitive, 
is_bind_aware
  2  FROM V$SQL
  3* WHERE  sql_text = 'SELECT MAX(id) FROM acs_test_tab WHERE record_type = 
:l_record_type'
SYSTEM:@O11GR2SE:SQL>/

SQL_ID        CHILD_NUMBER I I
------------- ------------ - -
9bmm6cmwa8saf            0 Y N
9bmm6cmwa8saf            1 Y Y

SYSTEM:@O11GR2SE:SQL>

SYSTEM:@O11GR2SE:SQL>SELECT * FROM 
table(DBMS_XPLAN.DISPLAY_CURSOR(SQL_ID=>'9bmm6cmwa8saf', CURSOR_CHILD_NO=>0, 
FORMAT=>'ALLSTATS'));

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  9bmm6cmwa8saf, child number 0
-------------------------------------
SELECT MAX(id) FROM acs_test_tab WHERE record_type = :l_record_type

Plan hash value: 3987223107

----------------------------------------------------------------------------
| Id  | Operation                    | Name                       | E-Rows |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                            |        |
|   1 |  SORT AGGREGATE              |                            |      1 |
|   2 |   TABLE ACCESS BY INDEX ROWID| ACS_TEST_TAB               |      1 |
|*  3 |    INDEX RANGE SCAN          | ACS_TEST_TAB_RECORD_TYPE_I |      1 |
----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access("RECORD_TYPE"=:L_RECORD_TYPE)

Note
-----
   - Warning: basic plan statistics not available. These are only collected 
when:
       * hint 'gather_plan_statistics' is used for the statement or
       * parameter 'statistics_level' is set to 'ALL', at session or system 
level


26 linhas selecionadas.

SYSTEM:@O11GR2SE:SQL>SELECT * FROM 
table(DBMS_XPLAN.DISPLAY_CURSOR(SQL_ID=>'9bmm6cmwa8saf', CURSOR_CHILD_NO=>1, 
FORMAT=>'ALLSTATS'));

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  9bmm6cmwa8saf, child number 1
-------------------------------------
SELECT MAX(id) FROM acs_test_tab WHERE record_type = :l_record_type

Plan hash value: 509473618

----------------------------------------------------
| Id  | Operation          | Name         | E-Rows |
----------------------------------------------------
|   0 | SELECT STATEMENT   |              |        |
|   1 |  SORT AGGREGATE    |              |      1 |
|*  2 |   TABLE ACCESS FULL| ACS_TEST_TAB |  48819 |
----------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("RECORD_TYPE"=:L_RECORD_TYPE)

Note
-----
   - Warning: basic plan statistics not available. These are only collected 
when:
       * hint 'gather_plan_statistics' is used for the statement or
       * parameter 'statistics_level' is set to 'ALL', at session or system 
level


25 linhas selecionadas.

SYSTEM:@O11GR2SE:SQL>
 
 Adicionalmente, no 11g também foram introduzidas as hints BIND_AWARE e 
NO_BIND_AWARE, para já 'marcar' logo de cara um dado SQL como bind-sensitive...
 
 ==> Olhando a captura de binds, que é a sua dúvida :
 
 SYSTEM:@O11GR2SE:SQL>alter session set NLS_DATE_FORMAT='dd/mm/yyyy hh24:mi:ss';

Sessão alterada.

SYSTEM:@O11GR2SE:SQL>select child_number, name, value_string, last_captured 
from V$sql_bind_capture
  2  where sql_id='9bmm6cmwa8saf';

CHILD_NUMBER NAME
------------ ------------------------------
VALUE_STRING
------------------------------------------------------------------------------------------------------------------
LAST_CAPTURED
-------------------
           1 :L_RECORD_TYPE
2
18/09/2018 14:48:10

           0 :L_RECORD_TYPE
1
18/09/2018 14:46:41


SYSTEM:@O11GR2SE:SQL>

===> Captou a mensagem, friendão ??? SIM, podem haver para o MESMO EXATO SQL_ID 
mais de uma data de captura, entre outras causas se houver CHILD CURSORS, yes 
????

[]s

  Chiappa

Responder a