Tempo esperando lock de outra sessão não se resolve no SQL de quem espera: a causa está na transação que segura o lock, e essa transação não aparece no trace. O trabalho é achar quem segura e por quê.
Sintomas
No Diagnóstico, aparece o achado Espera por bloqueio de outra sessão (BLOQUEIO). O texto diz quanto do tempo total ficou na classe de espera Application, em percentual e em segundos, lista os eventos da classe que pesaram e termina com Ver perfil por classe.
O achado aparece quando a classe Application pesa no tempo total. A severidade é média e passa a alta quando a classe pesa mais. Se um evento da classe domina o perfil, o relatório não repete o achado de componente dominante: este achado já trata desse tempo.
Entram na classe Application estes eventos, pela wait_class do Oracle 19c:
enq: TX - row lock contention: linha alterada por outra transação que ainda não terminou.enq: TM - contention: lock da tabela inteira.enq: UL - contention: lock pedido pela própria aplicação comDBMS_LOCK.enq: KO - fast object checkpointeenq: RO - fast object reuse: gravação ou descarte dos blocos do objeto antes de leitura direta,TRUNCATEouDROP.SQL*Net break/reset to clienteSQL*Net break/reset to dblink: chamada interrompida ou erro devolvido ao cliente.
Evento que o relatório não classifica aparece no perfil como Não classificado e não conta para este achado.
Causa
A sessão pediu um lock que outra sessão segurava e ficou parada até ele ser liberado. O título do achado fala em bloqueio, mas o evento que pesa diz o que aconteceu:
enq: TX - row lock contention: transação longa ou commit tardio segurando linhas; várias sessões atualizando o mesmo registro (contador, saldo, próximo número de documento); duas sessões inserindo a mesma chave única.enq: TM - contention: quase sempre chave estrangeira sem índice. Ao alterar a chave ou apagar a linha da tabela pai, o Oracle trava a tabela filha inteira.LOCK TABLEe DDL também causam essa espera.enq: UL - contention: o processo que segura o lock da aplicação demora para soltar.SQL*Net break/reset to client: não é bloqueio. A chamada foi interrompida ou devolveu erro à aplicação.
Resolução
Comece pelo evento que o achado lista primeiro. Os casos vão do mais comum ao menos comum.
Achar a sessão que segura o lock
Enquanto a espera acontece, a sessão bloqueada mostra quem a segura. Rode no banco do trace; em RAC, gv$session cobre todas as instâncias:
select inst_id, sid, serial#, username, event, seconds_in_wait,
blocking_instance, blocking_session,
final_blocking_instance, final_blocking_session,
sql_id, row_wait_obj#
from gv$session
where blocking_session is not null
order by seconds_in_wait desc;
A sessão de final_blocking_session é a do começo da fila. Veja o que ela executou por último e desde quando a transação dela está aberta:
select s.inst_id, s.sid, s.serial#, s.username, s.module, s.status,
s.sql_id, s.prev_sql_id, t.start_time
from gv$session s
join gv$transaction t
on t.inst_id = s.inst_id
and t.addr = s.taddr
where s.inst_id = &inst_id
and s.sid = &sid;
Se a espera já passou, o ASH guarda blocking_session por amostra em v$active_session_history e dba_hist_active_sess_history. O ASH exige a licença do Oracle Diagnostics Pack. Outra saída é ligar o trace também na sessão que segura o lock, na próxima vez.
Encurtar a transação que segura as linhas
- Faça o commit ao fim de cada unidade de trabalho, sem deixar a transação aberta esperando o usuário, outro sistema ou um arquivo.
- Em registro que muitas sessões atualizam (contador, saldo, controle de numeração), troque a linha única por sequence ou por linhas separadas por sessão ou por faixa.
- Quando a regra permitir, trave antes de alterar com
select ... for update waite um tempo limite, ouskip lockedem fila de trabalho, em vez de esperar sem limite.
Lock de tabela: indexar a chave estrangeira
Para enq: TM - contention, liste as chaves estrangeiras do esquema sem um índice que comece pelas colunas delas, na mesma ordem. Troque EXEMPLO pelo dono das tabelas:
select c.owner, c.table_name, c.constraint_name
from dba_constraints c
where c.constraint_type = 'R'
and c.owner = 'EXEMPLO'
and not exists (
select 1
from dba_cons_columns cc
join dba_ind_columns ic
on ic.table_owner = cc.owner
and ic.table_name = cc.table_name
and ic.column_name = cc.column_name
and ic.column_position = cc.position
where cc.owner = c.owner
and cc.constraint_name = c.constraint_name
group by ic.index_owner, ic.index_name
having count(*) = (select count(*)
from dba_cons_columns x
where x.owner = c.owner
and x.constraint_name = c.constraint_name))
order by c.table_name, c.constraint_name;
Crie o índice na tabela filha com as colunas da chave, na ordem da constraint:
create index exemplo.filha_pai_ix on exemplo.filha (pai_id);
Em tabela grande e em uso, create index ... online (Enterprise Edition) não trava a DML durante a criação. Para saber qual tabela foi travada, o campo object # da linha WAIT do enq: TM - contention no trace é o object_id dela:
grep "enq: TM - contention" arquivo.trc | head
A documentação da Oracle sobre concorrência e consistência de dados explica os locks de DML e o efeito da chave estrangeira sem índice.
Mesma chave inserida por duas sessões
Na linha WAIT do enq: TX - row lock contention, o campo name|mode diz o modo do lock. 1415053318 é modo 6: a linha foi alterada por outra transação. 1415053316 é modo 4: em geral, duas sessões inserindo a mesma chave única ou primária, ou índice bitmap sob DML concorrente. Para contar as esperas em modo 4:
grep "enq: TX - row lock contention" arquivo.trc | grep -c "name|mode=1415053316"
No modo 4, revise como a aplicação gera a chave e se a mesma carga roda duas vezes ao mesmo tempo. Em tabela de OLTP, troque o índice bitmap por um índice B-tree.
Outros eventos da classe
enq: UL - contention: peça os locks deDBMS_LOCKsempre na mesma ordem e solte cedo, comrelease_on_commitouDBMS_LOCK.RELEASE.SQL*Net break/reset to client: siga para os erros Oracle no trace. A espera some quando o erro deixa de acontecer.enq: KO - fast object checkpoint: leitura direta de uma tabela que muda o tempo todo. Confira o plano do comando que esperou.enq: RO - fast object reuse:TRUNCATEouDROPrepetido, muitas vezes dentro de laço.
Como confirmar
O bloqueio depende do que as outras sessões fazem ao mesmo tempo. Um trace tirado num momento calmo não prova a correção: meça no mesmo horário e com a mesma carga da medição anterior.
- Aplique uma correção por vez: o índice, o commit mais cedo ou a ordem dos locks.
- Ligue o trace da mesma operação com esperas (ligar o trace com o DBMS_MONITOR).
- Envie o arquivo novo e abra o relatório.
- Compare a linha Application em Por classe de espera e as linhas dos eventos no perfil.
Resultado: a classe Application deixa de pesar no tempo total e o achado some do Diagnóstico. No perfil, o evento que pesava cai em Ocorrências e em Duração (s).
No relatório
O relatório de exemplo não tem espera da classe Application; os links abrem as mesmas seções nele.
- Diagnóstico: o achado, com o link Ver perfil por classe, que leva à linha Application de Por classe de espera.
- Por classe de espera: a linha Application, com os eventos em Principais componentes. Clique no nome da classe para abrir o cartão com o que ela mede e onde olhar (classes de espera).
- Perfil do tempo de resposta: cada evento com Ocorrências, Média (s) e Máx (s). Poucas esperas longas apontam para transação esquecida aberta; muitas curtas, para linha disputada (perfil).
- Principais comandos por componente e por classe: os comandos que ficaram bloqueados (comandos por componente).
- Objetos com mais tempo de espera: o
obj#da linha ou da tabela disputada. Na console, o número abre a página do objeto, com consultas prontas de colunas e índices da tabela (objetos). - Distribuição de latência: as faixas de duração de cada evento (latência).
Se as sessões chegaram a esperar umas pelas outras em ciclo, o Oracle devolve o ORA-00060: veja deadlock entre sessões.