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
-
MySQL Documentation: InnoDB Locking — Documentação oficial sobre todos os tipos de locks no InnoDB, incluindo gap locks e next-key locks.
-
PostgreSQL Documentation: Explicit Locking — Guia completo sobre locks em PostgreSQL, incluindo deadlocks e níveis de isolamento.
-
SQL Server: Transaction Locking and Row Versioning Guide — Documentação da Microsoft sobre locks, deadlocks e estratégias de resolução.
-
Use the Index, Luke: Avoiding Deadlocks — Artigo técnico sobre como índices adequados previnem deadlocks e melhoram performance.
-
Percona Blog: How to Deal with MySQL Deadlocks — Guia prático com exemplos reais de diagnóstico e resolução de deadlocks no MySQL.