Imagina que você trabalha num escritório onde todas as portas ficam trancadas porque alguém esqueceu a chave dentro da sala. É assim que funcionam os bloqueios no PostgreSQL. Se uma query ou transação bloqueou um recurso, outras operações tentando acessar esse mesmo recurso ficam esperando até ela terminar. Isso pode causar atrasos, cenários de conflito e, no pior caso, travar o sistema todo.
Quando acontecem os bloqueios?
Bloqueios (Locks) no PostgreSQL são usados pra controlar o acesso concorrente aos dados. Eles aparecem:
- Quando você faz operações de escrita:
UPDATE,DELETE,INSERT. - Quando uma transação segura um recurso por mais tempo do que deveria.
- Quando rola conflito entre transações diferentes querendo o mesmo recurso.
Um banco de dados real é tipo um "campo de batalha" por recursos, e mesmo que você ache que seu sistema tá rodando liso, uma transação descuidada pode "emperrar" tudo, igual aquele merge zoado no Git.
Ferramentas pra analisar bloqueios: pg_locks
pg_locks é uma view do sistema no PostgreSQL que mostra os bloqueios atuais, tanto os já concedidos quanto os que estão esperando. Ela responde à pergunta: "Quem tá segurando o lock e quem tá esperando?"
Principais campos do pg_locks:
locktype: tipo do lock (por exemplo,relation,transaction,page,tuple).database: id do banco de dados.relation: id da tabela (se o lock for relacionado a uma tabela).mode: modo do lock (tipoRowExclusiveLock,AccessShareLock).granted: flag mostrando se o lock já foi concedido (true) ou se a transação ainda tá esperando (false).
Obs: O PostgreSQL usa um "modo hierárquico de bloqueios". Isso quer dizer que operações diferentes podem colocar locks menos restritivos (tipo AccessShareLock pra leitura) ou mais restritivos (ExclusiveLock pra mexer na estrutura da tabela).
Exemplo: ver todos os locks atuais
SELECT *
FROM pg_locks;
Mas se você só jogar tudo do pg_locks na tela, vai ser muito ruído. Bora tentar algo mais útil!
Exemplo: locks que ainda não foram concedidos (ou seja, transações esperando)
SELECT pid, locktype, relation::regclass AS table_name, mode, granted
FROM pg_locks
WHERE NOT granted;
O que tá rolando aqui?
- A gente filtra só os registros onde
granted = false, ou seja, o lock ainda não foi concedido. relation::regclassconverte o id da tabela pro nome dela, pra ficar mais legível.
O resultado pode ser tipo assim:
| pid | locktype | table_name | mode | granted |
|---|---|---|---|---|
| 1234 | relation | students | RowExclusiveLock | false |
| 4321 | relation | courses | RowShareLock | false |
Essas queries vão te ajudar a identificar qual tabela/recurso tá bloqueado e qual transação pode ser a culpada.
Análise de conflitos: pg_blocking_pids()
Bloqueios já são chatos, mas e quando uma transação trava outra? O PostgreSQL tem um jeito fácil de achar o "culpado" usando a função pg_blocking_pids().
A função pg_blocking_pids() retorna uma lista de pids dos processos que tão bloqueando a execução da transação atual.
Exemplo: achar transações que tão bloqueando outras
SELECT pid, pg_blocking_pids(pid) AS blocking_pids
FROM pg_stat_activity
WHERE cardinality(pg_blocking_pids(pid)) > 0;
O que tá rolando aqui?
- A gente usa a view
pg_stat_activitypra pegar os processos ativos no sistema. - A função
pg_blocking_pids(pid)retorna a lista de processos que tão travando cadapid. Se a lista não tá vazia (tamanho maior que 0), quer dizer que o processo tá bloqueado.
Exemplo de resultado:
| pid | blocking_pids |
|---|---|
| 4567 | {1234, 5678} |
| 6789 | {4321} |
A transação com pid = 4567 tá travada pelos processos 1234 e 5678. Achamos nossos "culpados".
Finalizando processos que tão bloqueando
Quando você achou os processos que tão travando tudo, pode matar eles usando a função pg_terminate_backend():
SELECT pg_terminate_backend(1234); -- "Matando" o processo 1234
Mas cuidado! Forçar o fim de um processo pode fazer a transação atual dar rollback. Usa isso como "botão nuclear" só quando não tiver outro jeito.
Na prática: cenário de análise de bloqueios
Imagina que a gente tem um banco de dados de universidade com as tabelas students e enrollments. Várias transações tentam gravar na tabela enrollments ao mesmo tempo, e aí começam os bloqueios.
- Identificando os bloqueios:
SELECT pid, locktype, relation::regclass AS table_name, mode, granted
FROM pg_locks
WHERE NOT granted;
- Descobrindo quem tá bloqueando:
SELECT pid, pg_blocking_pids(pid) AS blocking_pids
FROM pg_stat_activity
WHERE cardinality(pg_blocking_pids(pid)) > 0;
- Resolvendo os bloqueios:
Força o fim de um dos processos em conflito:
SELECT pg_terminate_backend(1234); -- Finaliza o processo 1234
Obs: Antes de "matar" o processo, tenta entender por que rolou o bloqueio. Talvez valha a pena revisar a lógica das transações.
Erros comuns e como evitar
Bloqueios geralmente aparecem por causa de má gestão das transações. Por exemplo:
Erro: uma transação segura o lock por muito tempo sem fazer nada (fica "idle in transaction").
Solução: fica de olho no estado das transações usando pg_stat_activity e finaliza as que tão "penduradas".
SELECT pid, state, query
FROM pg_stat_activity
WHERE state = 'idle in transaction';
Erro: esqueceu de usar índices nas queries, aí rola lock na tabela inteira.
Solução: otimiza as queries, adicionando índices pros filtros mais usados.
Tabela "quem tá esperando quem"
Pra facilitar o diagnóstico, você pode montar uma árvore de dependências mostrando qual transação tá bloqueando qual:
WITH RECURSIVE blocking_tree AS (
SELECT pid, pg_blocking_pids(pid) AS blocked_by
FROM pg_stat_activity
WHERE cardinality(pg_blocking_pids(pid)) > 0
UNION ALL
SELECT a.pid, pg_blocking_pids(a.pid)
FROM pg_stat_activity a
JOIN blocking_tree b ON a.pid = ANY(b.blocked_by)
)
SELECT pid, blocked_by FROM blocking_tree;
Resultado:
| pid | blocked_by |
|---|---|
| 4567 | {1234} |
| 1234 | {5678} |
| 5678 | {} |
Dá pra ver que o processo 5678 tá bloqueando o 1234, que por sua vez tá bloqueando o 4567.
GO TO FULL VERSION