Locks: tipos e como evitar deadlocks

1. Introdução aos Locks em Bancos de Dados

Locks são mecanismos fundamentais em sistemas de gerenciamento de banco de dados (SGBDs) para garantir a consistência e o isolamento das transações. Quando múltiplas transações concorrem pelo acesso simultâneo aos mesmos dados, os locks impedem que operações conflitantes interfiram umas nas outras.

O ciclo de vida de um lock segue três etapas principais: aquisição (quando uma transação solicita acesso a um recurso), retenção (durante a execução da transação) e liberação (no COMMIT ou ROLLBACK). Sem locks, fenômenos como leitura suja, leitura não repetível e escrita fantasma seriam comuns, comprometendo a integridade dos dados.

2. Tipos de Locks por Granularidade

A granularidade de um lock define o tamanho do recurso protegido. Cada nível apresenta um trade-off entre concorrência e overhead de gerenciamento.

Locks de linha (row-level): Protegem uma única linha em uma tabela. São o padrão em bancos como MySQL InnoDB e PostgreSQL. Permitem alta concorrência, pois transações diferentes podem modificar linhas distintas da mesma tabela simultaneamente. O overhead é maior devido ao gerenciamento individual de cada lock.

Locks de página (page-level): Protegem uma página de dados (geralmente 4KB a 16KB). Ocorrem em bancos como SQL Server e Oracle. Oferecem um equilíbrio entre concorrência e overhead, mas podem causar contenção desnecessária quando linhas não relacionadas estão na mesma página.

Locks de tabela (table-level): Protegem a tabela inteira. São utilizados em operações DDL (ALTER TABLE, DROP TABLE) ou quando uma consulta não encontra índices adequados, forçando uma varredura completa. Reduzem drasticamente a concorrência, mas têm baixo overhead.

Exemplo de lock de tabela explícito:

LOCK TABLES pedidos WRITE;
-- operações exclusivas aqui
UNLOCK TABLES;

3. Modos de Lock: Compartilhado vs. Exclusivo

Lock compartilhado (S-Lock): Permite que múltiplas transações leiam o mesmo recurso simultaneamente, mas impede qualquer escrita. É adquirido automaticamente em operações SELECT com isolamento REPEATABLE READ ou superior.

Lock exclusivo (X-Lock): Concede à transação o direito exclusivo de ler e escrever em um recurso. Nenhuma outra transação pode adquirir S-Lock ou X-Lock enquanto o recurso estiver bloqueado. É adquirido em operações INSERT, UPDATE, DELETE e SELECT...FOR UPDATE.

A matriz de compatibilidade entre modos é simples:

Lock concedido S-Lock X-Lock
S-Lock Sim Não
X-Lock Não Não

Exemplo de aquisição de lock exclusivo:

BEGIN TRANSACTION;
SELECT * FROM estoque WHERE produto_id = 101 FOR UPDATE;
-- outras transações não podem ler ou escrever nesta linha
UPDATE estoque SET quantidade = quantidade - 5 WHERE produto_id = 101;
COMMIT;

4. Locks de Intenção e Hierarquia

Locks de intenção são mecanismos que operam em nível de tabela para indicar que uma transação pretende adquirir locks mais granulares (linha ou página) em recursos filhos. Eles evitam verificações ineficientes em toda a tabela.

Os principais tipos são:
- IS (Intention Shared): Indica intenção de adquirir S-Lock em linhas
- IX (Intention Exclusive): Indica intenção de adquirir X-Lock em linhas
- SIX (Shared Intention Exclusive): Combina S-Lock na tabela com IX em linhas

No MySQL InnoDB, a hierarquia funciona assim: antes de adquirir um X-Lock em uma linha, a transação adquire automaticamente um IX-Lock na tabela. Se outra transação tentar adquirir um X-Lock na tabela inteira, o IX-Lock já existente impede a operação, sem precisar verificar cada linha individualmente.

5. Deadlocks: Causas e Diagnóstico

Um deadlock ocorre quando duas ou mais transações mantêm locks que a outra precisa, criando uma dependência circular. Nenhuma transação pode prosseguir até que uma seja abortada.

Exemplo clássico de deadlock:

-- Transação A
BEGIN;
UPDATE contas SET saldo = saldo - 100 WHERE id = 1;  -- lock na linha 1
UPDATE contas SET saldo = saldo + 100 WHERE id = 2;  -- espera lock na linha 2

-- Transação B (executada simultaneamente)
BEGIN;
UPDATE contas SET saldo = saldo - 50 WHERE id = 2;   -- lock na linha 2
UPDATE contas SET saldo = saldo + 50 WHERE id = 1;   -- espera lock na linha 1

Ambas as transações aguardam indefinidamente. O banco detecta o deadlock e escolhe uma vítima (geralmente a transação que realizou menos trabalho) para abortar.

Para diagnosticar deadlocks no MySQL:

SHOW ENGINE INNODB STATUS\G

A seção "LATEST DETECTED DEADLOCK" mostra as transações envolvidas, os locks retidos e os recursos solicitados.

6. Estratégias para Evitar Deadlocks

Ordenação consistente de acesso: Sempre acesse tabelas e linhas na mesma ordem em todas as transações. No exemplo anterior, se ambas as transações atualizassem primeiro a linha 1 e depois a linha 2, o deadlock não ocorreria.

Transações curtas: Mantenha transações com o menor tempo possível entre a aquisição de locks e o COMMIT. Evite entrada de usuário ou processamento pesado dentro de uma transação.

Índices adequados: Locks de intervalo (gap locks) ocorrem quando consultas não encontram índices apropriados. Índices bem projetados reduzem locks desnecessários:

-- Sem índice: lock de intervalo na tabela inteira
DELETE FROM pedidos WHERE status = 'PENDENTE';

-- Com índice em status: locks apenas nas linhas afetadas
CREATE INDEX idx_status ON pedidos(status);

Timeout e retry: Configure parâmetros como innodb_lock_wait_timeout (MySQL) para limitar o tempo de espera:

SET innodb_lock_wait_timeout = 5;  -- segundos

Implemente lógica de retry na aplicação:

-- Pseudocódigo
tentativas = 0
while tentativas < 3:
    try:
        BEGIN TRANSACTION
        -- operações com locks
        COMMIT
        break
    except DeadlockError:
        ROLLBACK
        tentativas += 1
        sleep(100ms * tentativas)

7. Ferramentas e Monitoramento de Locks

Para visualizar locks ativos no MySQL 8.0+:

SELECT * FROM performance_schema.data_locks;

Esta view mostra o tipo de lock, modo, objeto afetado e transação detentora.

Para prever locks gerados por uma query:

EXPLAIN SELECT * FROM pedidos WHERE cliente_id = 42 FOR UPDATE;

O plano de execução indica se a consulta usará índices (locks de linha) ou fará varredura completa (locks de tabela).

Práticas de monitoramento contínuo incluem:
- Alertas para deadlocks frequentes via logs do banco
- Dashboards com métricas de contenção de locks
- Revisão periódica de consultas com EXPLAIN para identificar locks desnecessários

Referências