DELETE: removendo dados com segurança
1. Sintaxe Básica do DELETE
A instrução DELETE é um dos comandos DML (Data Manipulation Language) mais poderosos e perigosos do SQL. Sua sintaxe fundamental é enganosamente simples:
DELETE FROM nome_da_tabela WHERE condicao;
A estrutura básica parece inofensiva, mas o verdadeiro poder — e risco — está na cláusula WHERE. Se você omitir o WHERE, o comando se torna uma arma de destruição em massa:
DELETE FROM clientes; -- Remove TODOS os registros da tabela!
Diferença crucial entre DELETE e TRUNCATE:
| Característica | DELETE | TRUNCATE |
|---|---|---|
| Remove linhas | Sim | Sim |
| Pode usar WHERE | Sim | Não |
| Log de transação | Completo | Mínimo |
| Velocidade | Lento | Rápido |
| Reseta auto-increment | Não | Sim |
| Pode ser revertido | Sim (com transação) | Depende do banco |
Enquanto o DELETE opera linha por linha e mantém logs detalhados, o TRUNCATE remove páginas inteiras de dados de uma vez, sendo mais rápido mas menos flexível.
2. Filtrando Registros com WHERE
A cláusula WHERE é sua principal ferramenta para precisão. Vamos explorar operadores essenciais:
Operadores de comparação básicos:
DELETE FROM produtos WHERE preco = 0;
DELETE FROM usuarios WHERE idade < 18;
DELETE FROM pedidos WHERE status <> 'entregue';
DELETE FROM logs WHERE data_criacao > '2024-01-01';
Combinação de condições:
DELETE FROM transacoes
WHERE valor > 10000
AND data_transacao < '2023-01-01'
AND status = 'cancelada';
Uso de IN para múltiplos valores:
DELETE FROM categorias
WHERE id IN (10, 15, 20, 25, 30);
Intervalos com BETWEEN:
DELETE FROM auditoria
WHERE data_registro BETWEEN '2020-01-01' AND '2020-12-31';
Padrões com LIKE:
DELETE FROM usuarios_temporarios
WHERE email LIKE '%@teste-descartavel.com';
3. Exclusão com JOINs e Subconsultas
Quando a lógica de exclusão envolve múltiplas tabelas, você precisa de técnicas mais avançadas.
Exclusão com JOIN no MySQL:
DELETE p
FROM pedidos p
INNER JOIN clientes c ON p.cliente_id = c.id
WHERE c.ativo = 0 AND p.data_pedido < '2023-01-01';
Exclusão com USING no PostgreSQL:
DELETE FROM pedidos p
USING clientes c
WHERE p.cliente_id = c.id
AND c.ativo = 0
AND p.data_pedido < '2023-01-01';
Subconsultas para filtrar registros relacionados:
DELETE FROM itens_pedido
WHERE pedido_id IN (
SELECT id FROM pedidos
WHERE data_criacao < '2022-01-01'
AND status = 'cancelado'
);
Exclusão em cascata vs. manual:
Se você tem chaves estrangeiras com ON DELETE CASCADE, excluir um registro pai remove automaticamente os filhos. Caso contrário, você precisa excluir manualmente na ordem correta:
-- Primeiro os filhos
DELETE FROM itens_pedido WHERE pedido_id = 123;
-- Depois o pai
DELETE FROM pedidos WHERE id = 123;
4. Transações e Rollback para Segurança
Nunca subestime o poder de uma transação. Ela é sua rede de segurança:
BEGIN TRANSACTION;
-- Verificação prévia
SELECT COUNT(*) FROM funcionarios WHERE salario > 50000;
-- Exclusão planejada
DELETE FROM funcionarios WHERE salario > 50000;
-- Se algo errado, desfaz tudo
ROLLBACK;
-- Se tudo certo, confirma
COMMIT;
SAVEPOINT para rollback parcial:
BEGIN TRANSACTION;
SAVEPOINT antes_delete_importante;
DELETE FROM dados_criticos WHERE id = 100;
-- Se precisar desfazer apenas essa exclusão
ROLLBACK TO SAVEPOINT antes_delete_importante;
COMMIT;
Verificação prévia com SELECT:
Sempre execute um SELECT com a mesma condição antes do DELETE:
-- Passo 1: Verificar o que será afetado
SELECT COUNT(*), * FROM pedidos
WHERE data_criacao < '2020-01-01'
AND status = 'finalizado';
-- Passo 2: Executar o DELETE (idealmente dentro de uma transação)
DELETE FROM pedidos
WHERE data_criacao < '2020-01-01'
AND status = 'finalizado';
5. Boas Práticas de Segurança
Sempre testar com SELECT primeiro:
Nunca execute um DELETE sem antes validar o resultado com SELECT usando a mesma cláusula WHERE.
Uso de LIMIT para limitar danos:
DELETE FROM logs_antigos
WHERE data < '2023-01-01'
LIMIT 1000; -- Remove no máximo 1000 registros por execução
Implementação de soft delete:
Em vez de excluir fisicamente, marque o registro como inativo:
-- Adicione uma coluna à tabela
ALTER TABLE usuarios ADD COLUMN ativo BOOLEAN DEFAULT TRUE;
ALTER TABLE usuarios ADD COLUMN deleted_at TIMESTAMP NULL;
-- "Exclusão" segura
UPDATE usuarios
SET ativo = FALSE, deleted_at = NOW()
WHERE id = 42;
-- Consultas ignoram registros "excluídos"
SELECT * FROM usuarios WHERE ativo = TRUE;
6. Tratamento de Erros e Restrições
Violação de FOREIGN KEY:
Se você tentar excluir um registro pai que ainda tem filhos, o banco lançará um erro:
ERROR: update or delete on table "clientes" violates foreign key constraint "pedidos_cliente_id_fkey" on table "pedidos"
Estratégias de integridade referencial:
| Opção | Comportamento |
|---|---|
| ON DELETE CASCADE | Remove automaticamente os filhos |
| ON DELETE SET NULL | Define a FK como NULL nos filhos |
| ON DELETE RESTRICT | Impede a exclusão se houver filhos |
| ON DELETE NO ACTION | Similar a RESTRICT, verifica no final |
Lidando com timeouts em tabelas grandes:
-- Exclusão em lotes para evitar locks prolongados
WHILE (SELECT COUNT(*) FROM logs_antigos WHERE data < '2020-01-01') > 0
BEGIN
DELETE TOP (1000) FROM logs_antigos WHERE data < '2020-01-01';
WAITFOR DELAY '00:00:01'; -- Pausa de 1 segundo
END
7. Performance e Otimização
Impacto de índices:
Índices aceleram a localização dos registros a serem excluídos, mas cada exclusão também precisa atualizar os índices. Em tabelas com muitos índices, o DELETE pode ser mais lento.
Exclusão em lotes (batch delete):
Para tabelas volumosas, exclua em blocos menores:
-- Exclui 5000 registros por vez
DELETE FROM logs_auditoria
WHERE id IN (
SELECT id FROM logs_auditoria
WHERE data < '2022-01-01'
LIMIT 5000
);
Comparação de performance:
| Operação | Tabela 10M registros | Tabela 100M registros |
|---|---|---|
| DELETE (sem WHERE) | 30-60 segundos | 5-10 minutos |
| TRUNCATE | < 1 segundo | 1-3 segundos |
| DELETE com WHERE indexado | Milissegundos | Segundos |
| DELETE com WHERE não indexado | Minutos | Horas |
Para grandes volumes de dados onde você precisa remover tudo, o TRUNCATE é drasticamente mais rápido. Use DELETE apenas quando precisar de filtragem seletiva ou da capacidade de rollback.
Referências
- Documentação oficial MySQL: DELETE Statement — Documentação completa sobre sintaxe, opções e exemplos de DELETE no MySQL
- PostgreSQL Documentation: DELETE — Referência oficial da instrução DELETE no PostgreSQL, incluindo a cláusula USING
- Microsoft SQL Server: DELETE (Transact-SQL) — Documentação detalhada do DELETE no SQL Server com exemplos de JOINs e TOP
- Oracle Database: DELETE Statement — Guia oficial Oracle sobre a instrução DELETE e suas particularidades
- Use The Index, Luke: DELETE Performance — Artigo técnico aprofundado sobre performance de DELETE e impacto de índices
- SQL Performance Explained: Batch Deletes — Tutorial prático sobre exclusão em lotes para otimizar performance em tabelas grandes