Um PARSE para cada execução quer dizer que o cursor é aberto e fechado a cada uso. Mesmo leve, cada parse custa uma chamada, CPU e disputa na library cache; o SQL preparado uma vez e executado muitas evita tudo isso.
Sintomas
No Diagnóstico, aparece o achado Comando #n: um PARSE a cada execução (n PARSE, n EXEC) (PARSE_POR_EXECUCAO), por exemplo Comando #1: um PARSE a cada execução (2.000 PARSE, 2.000 EXEC). O texto diz que o cursor é aberto e fechado a cada uso e termina com Ver comando #1, que abre o detalhe do comando.
O achado aparece para os comandos muito executados em que quase toda execução (EXEC) vem com um PARSE próprio, a começar pelos que mais pesam. A severidade é baixa e passa a média quando o PARSE do comando pesa mais no tempo total; esse peso é o percentual ao lado do título.
Numa linha com o selo N variantes, que soma SQL só com literais diferentes, o PARSE com hard parse é o de cada valor novo, e quem trata dele é o achado de literais em vez de bind. Este achado só aparece nessa linha quando as variantes se repetem e o PARSE delas é quase todo leve: o cursor de cada variante é aberto e fechado a cada uso.
Causa
A aplicação prepara o SQL, executa uma vez e fecha o cursor, e repete o ciclo na próxima execução. Cada ciclo custa uma chamada PARSE a mais e pode custar uma ida e volta a mais na rede. Mesmo quando o parse é leve (soft parse, sem mis=1), ele gasta CPU e disputa a library cache com as outras sessões.
É comum em código que cria o statement dentro do laço, em drivers sem cache de statements e em camadas de acesso que abrem e fecham o cursor a cada chamada.
Resolução
- Na aplicação, prepare o SQL uma vez e execute muitas: guarde o statement preparado e reutilize, em vez de criar e fechar a cada execução.
- Ligue o cache de statements do driver, que mantém os cursores abertos entre usos. No JDBC da Oracle, por exemplo:
OracleConnection oconn = conn.unwrap(OracleConnection.class); oconn.setImplicitCachingEnabled(true); oconn.setStatementCacheSize(50); - Confira o
session_cached_cursors. No PL/SQL, ele define quantos cursores o PL/SQL mantém abertos, e o PARSE repetido some. Na aplicação cliente, ele deixa cada PARSE mais barato, mas a chamada continua no trace: o que a tira são os passos 1 e 2.select value from v$parameter where name = 'session_cached_cursors'; - Se o comando é SQL dinâmico montado com os valores no texto, o problema é outro: veja literais em vez de bind.
Como confirmar
Gere um trace novo da mesma operação depois da mudança e abra o detalhe do comando. Na tabela Chamadas, a Quantidade de PARSE deve ficar muito abaixo da de EXEC, e o achado some.
No perfil, CPU: PARSE dbcalls deve cair. Com o cache de statements na aplicação, as Ocorrências de SQL*Net message from client também podem cair.
No relatório
No relatório de exemplo, o comando #1 tem 1 PARSE e 1 EXEC: um PARSE por execução, mas numa execução só, sem achado. O relatório do caso 1, de um trace real, tem este achado (Diagnóstico do caso 1). Os links abaixo abrem as seções do exemplo:
- Diagnóstico: o achado e o link Ver comando #n.
- Detalhe do comando #1: na tabela Chamadas, a Quantidade de PARSE e de EXEC lado a lado.
- Tipos de chamada: PARSE e EXEC da sessão inteira, com Misses.
- Perfil, linha CPU: PARSE dbcalls: a CPU gasta em parse.
Esperas como library cache: mutex X e cursor: pin S, da classe Concurrency, também acompanham PARSE em excesso: veja disputa por estruturas internas. O detalhe do comando tem página própria: detalhe do comando.