Em 2015, atualizei nossos bancos de dados Oracle 11.2.0.4 para 12.1.0.2 e tive alguns problemas de desempenho relacionados ao uso de GTTs. Eu escrevi sobre essas questões aqui.
O cerne do problema que eu estava tentando resolver era que uma mudança de comportamento no 12c levava ao Oracle salvando estatísticas de que o GTT tem zero linhas quando não tem. As estatísticas que mostram o número de linhas igual a zero levam a verificações de tabela completas e produtos cartesianos em consultas que envolvem o GTT. Como afirmei naquele post do blog, usamos DBMS_STATS.SET_TABLE_STATS depois de preenchermos a tabela com dados para que cada sessão tivesse estatísticas adequadas para chegar a um melhor plano de execução.
Depois que atualizamos para o Oracle 19c, começamos a ver outros problemas de desempenho relacionados ao GTT. As consultas que usaram o GTT começaram a aguardar o evento de espera “cursor pin:S wait on X”. Isso pode ter sido uma mudança de comportamento com a nova versão do Oracle, mas também pode ter sido nossos desenvolvedores usando o GTT com mais frequência em nosso código e não ter nada a ver com a nova versão.
Para as consultas envolvidas no evento de espera do Pin do Cursor, notei um grande número de versões da instrução SQL no Pool Compartilhado. Quando consultei V$SQL_SHARED_CURSOR, descobri que PURGED_CURSOR='Y' para essas instruções SQL. O cursor está sendo invalidado.
Ao pesquisar este problema, descobri que o que acontece é que toda vez que chamamos DBMS_STATS.SET_TABLE_STATS para obter estatísticas baseadas em sessão no GTT, ele invalida todas as instruções SQL que usam esse GTT. Daí a espera. A espera não foi longa, muitos usuários finais nem notaram o problema.
Mas então tivemos um novo problema. Quando você faz uma chamada para SET_TABLE_STATS, o Oracle grava uma entrada em SYS.WRI$_OPTSTAT_TAB_HISTORY e você pode ver os valores que a sessão definiu para as estatísticas da tabela. Por padrão, esta tabela armazena 30 dias de histórico. A tabela estava crescendo muito e consumindo a maior parte do SYSAUX. De vez em quando (de hora em hora?) O Oracle removerá entradas com mais de 30 dias. Essa poda regular dessa tabela agora estava afetando negativamente o desempenho do usuário final. A seguir está um gráfico de desempenho da Lighty mostrando o impacto da poda desta tabela:
Toda aquela cor vermelha assustadora é quando as linhas antigas estavam sendo removidas de SYS.WRI$_OPTSTAT_TAB_HISTORY.
Então, minha “correção” de desempenho cinco anos atrás introduziu outro problema de desempenho. Para melhorar o desempenho, o que fiz foi criar estatísticas compartilhadas no GTT e parar de usar estatísticas de sessão. Aqui estão os passos:
--set prefs to SHARED globally
exec DBMS_STATS.set_global_prefs ( pname => 'GLOBAL_TEMP_TABLE_STATS', pvalue => 'SHARED');
--set the table and index stats
exec dbms_stats.set_table_stats(ownname=>'MY_SCHEMA',tabname=>'MY_GTT_TABLE',numrows=>1000,numblks=>2,avgrlen=>15);
exec dbms_stats.set_index_stats(ownname=>'MY_SCHEMA',indname=>'GTT_INDEX',indlevel=>1,numlblks=>2,numdist=>15,clstfct=>28,numrows=>1000);
-- set prefs back to SESSION
exec DBMS_STATS.set_global_prefs ( pname => 'GLOBAL_TEMP_TABLE_STATS', pvalue => 'SESSION');
-- verify stats set
select num_rows,blocks,last_analyzed,scope
from dba_tab_statistics
where table_name ='MY_GTT_TABLE';
select blevel,leaf_blocks,distinct_keys,num_rows,clustering_factor,last_analyzed,scope
from dba_ind_statistics
where index_name='GTT_INDEX' and owner='MY_SCHEMA';
Uma vez que as estatísticas compartilhadas estejam em vigor, removemos as chamadas para DBMS_SET_TABLE_STATS do nosso código.