Aula 14: Transactions — COMMIT, ROLLBACK e controle de concorrência
Seja bem-vindo à décima-quarta aula do curso MySQL — Do Zero ao Avançado. Hoje mergulhamos em um dos conceitos mais fundamentais para qualquer profissional que trabalha com bancos de dados relacionais em ambientes de produção: Transactions. Dominar o início, o desfecho e o cancelamento de uma operação composta é o que separa uma aplicação frágil de um sistema verdadeiramente confiável. Em nossos projetos na JRT Technology Solutions, a utilização correta de transactions é pré-requisito inegociável em qualquer pipeline de dados que envolva consistência financeira, logs de auditoria ou sincronização entre múltiplos microsserviços.
Nesta aula, você vai além da sintaxe básica de COMMIT e ROLLBACK. Vamos entender o modelo transacional do MySQL, como o motor InnoDB implementa as propriedades ACID e de que forma o controle de concorrência multiversão (MVCC) permite que centenas de conexões trabalhem simultaneamente sem corromper os dados. Você aprenderá a configurar e inspecionar o comportamento de transações, manipular savepoints e diagnosticar problemas comuns de bloqueio e deadlock — tudo com exemplos práticos que simulam cenários reais.
Se você vem acompanhando o curso, já sabe criar bancos, tabelas com constraints e escrever consultas complexas. Agora, vai aprender a empacotar múltiplas instruções INSERT, UPDATE e DELETE em unidades atômicas, de modo que ou todas as mudanças são persistidas ou nenhuma é. Veremos por que o modo autocommit padrão pode ser um aliado perigoso e como desativá-lo temporariamente para transações explícitas. Ao final, você será capaz de implementar rotinas transacionais seguras e identificar rapidamente quando um bloqueio ou um nível de isolamento inadequado está prejudicando a performance.
Prepare seu terminal e seu cliente MySQL preferido. Todos os exemplos foram testados no MySQL 8.0 e são compatíveis com as versões 5.7 e posteriores que utilizam o mecanismo InnoDB. A aula está estruturada para ser executada passo a passo: cada comando mostrado deve funcionar se reproduzido exatamente como descrito. Vamos começar.
O que você vai aprender nesta aula
- O conceito de Transaction e sua importância em sistemas de produção
- As quatro propriedades ACID — Atomicidade, Consistência, Isolamento e Durabilidade
- Como verificar e alterar o motor de armazenamento para InnoDB
- Os comandos START TRANSACTION, COMMIT e ROLLBACK em detalhes
- Uso avançado de SAVEPOINT para rollback parcial
- Controle de concorrência: locks de linha, locks de tabela e MVCC
- Os quatro níveis de isolamento definidos pelo padrão SQL e seu comportamento no MySQL
- Configuração de timeout de lock e detecção de deadlock
- Diagnóstico de transações abertas com SHOW ENGINE INNODB STATUS e INFORMATION_SCHEMA
- Erros comuns e estratégias de resolução em ambientes de alta concorrência
Pré-requisitos e Ambiente
Para executar todos os procedimentos desta aula sem obstáculos, você precisa de um servidor MySQL em funcionamento. O recomendado é o MySQL 8.0 ou superior, porém os comandos funcionam a partir da versão 5.7 desde que o motor padrão seja o InnoDB. O ambiente foi validado em dois sistemas operacionais:
- Ubuntu Server 22.04 LTS — MySQL instalado via apt
- Rocky Linux 9 — MySQL instalado via dnf (repositório oficial Oracle)
Você precisará de um cliente de linha de comando (mysql), que já vem com a instalação do servidor ou do pacote mysql-client. Tenha também privilégios de SUPER ou pelo menos permissão para criar bancos de dados, tabelas e consultar o INFORMATION_SCHEMA. Se você concluiu a Aula 13, seu ambiente já atende a esses requisitos. Caso utilize um usuário restrito, os comandos de consulta a status e variáveis de sistema ainda funcionarão normalmente.
O banco de dados que utilizaremos nos exemplos se chamará curso_transactions. Para ambientes de produção, recomendamos que você sempre isole os testes em bases separadas das aplicações reais — esta é uma prática padrão em todas as implementações que conduzimos na JRT Technology Solutions.
1. O que são Transactions e por que elas são essenciais
Uma Transaction é uma sequência de uma ou mais operações SQL que o banco de dados trata como uma unidade de trabalho indivisível. Isso significa que, ao final da execução, apenas dois desfechos são possíveis: todas as instruções são aplicadas com sucesso e os dados são persistidos em disco — operação conhecida como COMMIT — ou toda e qualquer modificação feita até aquele ponto é desfeita — operação chamada de ROLLBACK. Esse é o princípio da atomicidade, o “A” do acrônimo ACID.
Pense em uma transferência bancária clássica: a conta de origem precisa ser debitada e a conta de destino precisa ser creditada. As duas operações devem acontecer por completo ou não devem acontecer de forma alguma. Se o débito ocorrer mas o crédito falhar por uma exceção de rede ou constraint violada, o sistema financeiro ficaria inconsistente. Ao envolver ambas as instruções em uma única transaction, garantimos que um ROLLBACK seja executado automaticamente em caso de falha, retornando o banco ao estado anterior ao início da operação.
Em sistemas modernos de e-commerce, logística e ERPs, as transactions não só previnem inconsistências como também definem os limites de visibilidade dos dados entre conexões concorrentes. É por meio delas que o MySQL decide quais versões de uma linha são visíveis para cada consulta, utilizando o mecanismo de MVCC (Multiversion Concurrency Control). Isso será detalhado na seção sobre níveis de isolamento.
Ao longo dos anos, na JRT Technology Solutions, já recuperamos bases inteiras de clientes simplesmente porque suas aplicações não utilizavam transactions e um script de migração falhou no meio do caminho — deixando tabelas com metade dos dados novos e metade dos antigos. Compreender a teoria e a prática de transactions não é um luxo: é dever profissional de quem administra dados corporativos.
2. Propriedades ACID: os pilares da confiabilidade transacional
O padrão ACID (Atomicity, Consistency, Isolation, Durability) define as quatro propriedades que um sistema de banco de dados deve satisfazer para que suas transações sejam consideradas confiáveis. Embora o termo seja amplamente utilizado, é comum que profissionais confundam o significado prático de cada letra. Vamos destrinchar uma por uma e mapeá-las para os recursos concretos do MySQL com InnoDB.
| Propriedade | Significado | Mecanismo no MySQL/InnoDB |
|---|---|---|
| Atomicidade | “Tudo ou nada”: uma transação é uma unidade atômica que ou é completamente executada ou não deixa efeito algum no banco | COMMIT / ROLLBACK, UNDO log |
| Consistência | A transação leva o banco de um estado válido a outro estado válido, preservando todas as constraints, triggers e regras de integridade | Foreign keys, CHECK constraints, triggers, unique indexes |
| Isolamento | Transações concorrentes não interferem umas nas outras — os efeitos de uma transação em andamento são invisíveis para outras até o COMMIT | MVCC, locks de linha, níveis de isolamento |
| Durabilidade | Uma vez confirmada, a transação sobrevive a falhas de energia, crash do sistema ou qualquer outro evento catastrófico | REDO log, doublewrite buffer, fsync em disco |
O UNDO log é o componente que permite o rollback e a leitura consistente sem bloqueios. Cada vez que uma linha é modificada, a versão anterior é copiada para o undo log. Assim, outras transações podem continuar lendo a versão antiga enquanto a nova ainda não foi confirmada — característica fundamental do MVCC. O REDO log, por sua vez, registra as modificações antes mesmo que elas sejam aplicadas nos datafiles, garantindo a durabilidade em caso de crash.
É importante destacar que a garantia de consistência não é uma mágica do motor: as constraints precisam ser definidas corretamente. Um banco sem foreign keys e sem triggers pode sofrer corrupção lógica mesmo dentro de transações ACID, pois a consistência depende das regras de negócio que você implementa.
3. Preparando o ambiente: InnoDB e o modo autocommit
No MySQL, o suporte a transações depende do motor de armazenamento. O MyISAM, por exemplo, não é transacional. Para a imensa maioria dos cenários de produção, o InnoDB é o motor padrão e o único recomendado. Vamos começar verificando o ambiente e criando a base de testes.
Conecte-se ao MySQL como usuário com privilégios de criação de banco de dados (por exemplo, root) e execute:
-- Conectando ao MySQL (execute no terminal do SO)
mysql -u root -p
-- Dentro do cliente MySQL, verifique o motor padrão
SHOW ENGINES \G
A saída será extensa. Procure pela linha referente ao InnoDB. A coluna Support deve mostrar DEFAULT ou YES. Uma saída típica é:
*************************** 1. row ***************************
Engine: InnoDB
Support: DEFAULT
Comment: Supports transactions, row-level locking, and foreign keys
Transactions: YES
XA: YES
Savepoints: YES
*************************** 2. row ***************************
Engine: MyISAM
Support: YES
Comment: MyISAM storage engine
Transactions: NO
XA: NO
Savepoints: NO
...
As colunas Transactions, XA e Savepoints indicam se o motor suporta transações, transações distribuídas (XA) e savepoints, respectivamente. Se InnoDB não aparecer como DEFAULT, você pode alterá-lo permanentemente no arquivo de configuração ou apenas para a sessão atual. Agora crie a base de testes e verifique o comportamento do autocommit:
-- Criação do banco de dados de exemplo
CREATE DATABASE IF NOT EXISTS curso_transactions;
USE curso_transactions;
-- Criação da tabela de contas para os exemplos de transferência
CREATE TABLE contas (
id INT AUTO_INCREMENT PRIMARY KEY,
titular VARCHAR(100) NOT NULL,
saldo DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
CONSTRAINT chk_saldo CHECK (saldo >= 0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Inserção de dados iniciais
INSERT INTO contas (titular, saldo) VALUES
('Alice', 1000.00),
('Bob', 500.00),
('Carlos', 250.00);
-- Verificando o conteúdo
SELECT * FROM contas;
+----+---------+---------+
| id | titular | saldo |
+----+---------+---------+
| 1 | Alice | 1000.00 |
| 2 | Bob | 500.00 |
| 3 | Carlos | 250.00 |
+----+---------+---------+
3 rows in set (0.00 sec)
Agora, o ponto mais crítico antes de começarmos com transações explícitas: o MySQL opera por padrão com autocommit = ON. Isso significa que cada instrução SQL é automaticamente tratada como uma transação que sofre COMMIT ao final. Em outras palavras, um INSERT isolado já é uma transação completa. Para transações de múltiplos passos, precisamos desligar esse comportamento ou usar o comando START TRANSACTION, que desabilita temporariamente o autocommit até o próximo COMMIT ou ROLLBACK.
-- Verificando o estado atual de autocommit
SHOW VARIABLES LIKE 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.01 sec)
Nosso ambiente está pronto. A tabela contas usa InnoDB e temos dados para simular operações reais.
4. Primeiros passos com START TRANSACTION, COMMIT e ROLLBACK
Este é o núcleo prático da aula. Vamos simular uma transferência de R$ 200,00 de Alice para Bob. As operações necessárias são: debitar 200 da conta de Alice e creditar 200 na conta de Bob. Se ambas forem bem-sucedidas, confirmamos a transação. Caso contrário, desfazemos tudo.
Abra uma única sessão do MySQL (vamos chamá-la de Sessão A) e execute os comandos passo a passo. Observe os comentários em linha.
-- 1. Inicia uma nova transação explicitamente
START TRANSACTION;
-- 2. Débito da conta de origem (Alice)
UPDATE contas SET saldo = saldo - 200.00 WHERE id = 1;
-- 3. Crédito na conta de destino (Bob)
UPDATE contas SET saldo = saldo + 200.00 WHERE id = 2;
-- 4. Verifica os saldos DENTRO da transação ativa
SELECT id, titular, saldo FROM contas WHERE id IN (1, 2);
+----+---------+---------+
| id | titular | saldo |
+----+---------+---------+
| 1 | Alice | 800.00 |
| 2 | Bob | 700.00 |
+----+---------+---------+
2 rows in set (0.00 sec)
Dentro da mesma sessão, os saldos refletem as alterações, mas elas não estão visíveis para outras conexões e ainda não foram persistidas em disco como parte durável do banco. Se abrirmos uma segunda sessão agora, ela verá os saldos originais. Verifique isso na prática abrindo outro terminal e executando:
-- Sessão B: verificar os saldos enquanto a transação da Sessão A está ativa
USE curso_transactions;
SELECT id, titular, saldo FROM contas WHERE id IN (1, 2);
+----+---------+---------+
| id | titular | saldo |
+----+---------+---------+
| 1 | Alice | 1000.00 |
| 2 | Bob | 500.00 |
+----+---------+---------+
2 rows in set (0.00 sec)
Isso comprova o isolamento padrão (nível REPEATABLE READ). Agora, na Sessão A, você tem duas escolhas: confirmar com COMMIT ou desfazer com ROLLBACK. Vamos primeiro executar um ROLLBACK para ver o efeito:
-- Sessão A: desfazendo todas as alterações
ROLLBACK;
-- Verificando após rollback
SELECT id, titular, saldo FROM contas WHERE id IN (1, 2);
+----+---------+---------+
| id | titular | saldo |
+----+---------+---------+
| 1 | Alice | 1000.00 |
| 2 | Bob | 500.00 |
+----+---------+---------+
2 rows in set (0.00 sec)
Os saldos retornaram ao estado original, e a Sessão B também nunca viu os valores intermediários. Agora, repita a operação, mas desta vez execute COMMIT:
-- Sessão A: nova transação
START TRANSACTION;
UPDATE contas SET saldo = saldo - 200.00 WHERE id = 1;
UPDATE contas SET saldo = saldo + 200.00 WHERE id = 2;
COMMIT;
SELECT id, titular, saldo FROM contas WHERE id IN (1, 2);
+----+---------+---------+
| id | titular | saldo |
+----+---------+---------+
| 1 | Alice | 800.00 |
| 2 | Bob | 700.00 |
+----+---------+---------+
2 rows in set (0.00 sec)
Agora, na Sessão B, uma nova consulta confirmará os saldos atualizados. A transação foi durável. O mais importante: se entre o débito e o crédito ocorresse um erro — como uma violação de constraint — o MySQL faria um rollback automático, desde que a transação estivesse ativa e o erro fosse tratado pela aplicação.
5. Savepoints: criando pontos de recuperação dentro de uma Transaction
Em cenários mais complexos, pode ser necessário reverter apenas uma parte da transação, preservando o trabalho já realizado. Para isso, o padrão SQL prevê os SAVEPOINTs, que funcionam como marcadores nomeados dentro de uma transação ativa. Você pode criar múltiplos savepoints e retornar a qualquer um deles sem desfazer a transação inteira.
Vamos simular uma operação que envolve três atualizações: debitar Alice, creditar Bob e creditar Carlos. No meio do processo, um savepoint permite voltar atrás apenas na terceira etapa.
-- Sessão A: iniciando transação com savepoints
START TRANSACTION;
-- Primeira etapa: débito de Alice
UPDATE contas SET saldo = saldo - 100.00 WHERE id = 1;
SAVEPOINT sp_debito_alice; -- marcador 1
-- Segunda etapa: crédito para Bob
UPDATE contas SET saldo = saldo + 60.00 WHERE id = 2;
SAVEPOINT sp_credito_bob; -- marcador 2
-- Terceira etapa: crédito para Carlos (vamos supor que algo deu errado)
UPDATE contas SET saldo = saldo + 40.00 WHERE id = 3;
-- Verificando os saldos parciais
SELECT id, titular, saldo FROM contas ORDER BY id;
+----+---------+--------+
| id | titular | saldo |
+----+---------+--------+
| 1 | Alice | 700.00 |
| 2 | Bob | 760.00 |
| 3 | Carlos | 290.00 |
+----+---------+--------+
3 rows in set (0.00 sec)
Agora, imagine que você decidiu que o crédito para Carlos foi incorreto, mas o débito de Alice e o crédito para Bob devem ser mantidos. Em vez de desfazer tudo com ROLLBACK, você pode retornar ao savepoint sp_credito_bob:
-- Retorna ao ponto imediatamente após o crédito para Bob
ROLLBACK TO SAVEPOINT sp_credito_bob;
-- Verificando após rollback parcial
SELECT id, titular, saldo FROM contas ORDER BY id;
+----+---------+--------+
| id | titular | saldo |
+----+---------+--------+
| 1 | Alice | 700.00 |
| 2 | Bob | 760.00 |
| 3 | Carlos | 250.00 |
+----+---------+--------+
3 rows in set (0.00 sec)
Carlos voltou ao saldo original (250), enquanto Alice e Bob mantiveram as alterações. Você pode liberar savepoints quando não forem mais necessários usando RELEASE SAVEPOINT nome;. Se quiser desfazer toda a transação, um ROLLBACK simples (sem o TO) ainda funciona. Finalize com COMMIT para persistir as mudanças restantes.
COMMIT;
Na JRT Technology Solutions, utilizamos savepoints extensivamente em rotinas de ETL e migração de dados. Eles oferecem uma forma elegante de realizar rollback granular, especialmente quando um bloco de 50 instruções pode falhar na 47ª e você não quer repetir o processamento das 46 anteriores.
6. Controle de concorrência: Locks e níveis de isolamento em Transactions
Um dos maiores desafios em bancos de dados transacionais é equilibrar consistência e performance quando múltiplas conexões tentam ler e modificar as mesmas linhas simultaneamente. O MySQL com InnoDB aborda isso com dois mecanismos complementares: locks (bloqueios) e MVCC (controle de concorrência multiversão).
O InnoDB implementa bloqueios de linha (row-level locking) para operações de modificação. Quando um UPDATE ou DELETE é executado dentro de uma transação, as linhas afetadas recebem um exclusive lock (X). Outras transações não podem escrever nessas linhas até que a primeira libere o lock com COMMIT ou ROLLBACK. Para leitura, é possível usar SELECT … FOR UPDATE (lock exclusivo) ou SELECT … LOCK IN SHARE MODE (lock compartilhado, renomeado para FOR SHARE no MySQL 8.0).
O MVCC, por outro lado, permite que leituras consistentes sejam feitas sem locks, usando snapshots dos dados no momento em que a transação começou. O comportamento exato depende do nível de isolamento configurado. O MySQL suporta quatro níveis, conforme o padrão SQL:
| Nível de Isolamento | Dirty Read | Non-Repeatable Read | Phantom Read | Padrão no MySQL? |
|---|---|---|---|---|
| READ UNCOMMITTED | Sim | Sim | Sim | Não |
| READ COMMITTED | Não | Sim | Sim | Não (padrão em PostgreSQL, Oracle) |
| REPEATABLE READ | Não | Não | Parcialmente (MySQL evita com gap locks) | Sim (padrão no MySQL/InnoDB) |
| SERIALIZABLE | Não | Não | Não | Não |
Vamos testar o REPEATABLE READ na prática com duas sessões concorrentes:
-- Sessão A: verificar o nível de isolamento e iniciar uma transação
SELECT @@transaction_isolation; -- Deve retornar REPEATABLE-READ
START TRANSACTION;
SELECT id, titular, saldo FROM contas WHERE id = 1; -- Alice tem 700.00 neste snapshot
+----+---------+--------+
| id | titular | saldo |
+----+---------+--------+
| 1 | Alice | 700.00 |
+----+---------+--------+
1 row in set (0.00 sec)
-- Sessão B: atualiza Alice e comita
UPDATE contas SET saldo = 999.99 WHERE id = 1;
COMMIT;
-- Sessão A: lê novamente a mesma linha
SELECT id, titular, saldo FROM contas WHERE id = 1;
+----+---------+--------+
| id | titular | saldo |
+----+---------+--------+
| 1 | Alice | 700.00 |
+----+---------+--------+
1 row in set (0.00 sec)
Mesmo após o COMMIT na Sessão B, a Sessão A continua enxergando o saldo de 700.00 — o snapshot consistente da transação ativa. Esse comportamento garante leituras repetíveis e é exatamente o que se espera do nível REPEATABLE READ. Se você precisar enxergar dados confirmados por outras transações durante a sua, mude para READ COMMITTED:
-- Em ambas as sessões, altere o nível de isolamento para a transação atual
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
Refaça o teste: na Sessão A, após o COMMIT da Sessão B, uma nova leitura retornará 999.99. Isso elimina o non-repeatable read, mas reintroduz o phantom read e reduz o isolamento geral, algo que deve ser avaliado cuidadosamente em ambientes financeiros.
7. Configuração e personalização do comportamento transacional
O MySQL expõe diversas variáveis de sistema que controlam aspectos transacionais. Além do autocommit e do transaction_isolation, temos parâmetros críticos para cenários de alta concorrência e volume. As principais estão resumidas na tabela a seguir, e podem ser configuradas dinamicamente ou no arquivo de configuração do servidor.
Em sistemas Linux, o arquivo principal é /etc/mysql/mysql.conf.d/mysqld.cnf (Ubuntu/Debian) ou /etc/my.cnf (Rocky Linux/CentOS). Em nossos projetos na JRT Technology Solutions, sempre adicionamos um bloco específico para tuning transacional no grupo [mysqld]. Abaixo, um exemplo funcional de configuração:
# /etc/mysql/mysql.conf.d/mysqld.cnf (Ubuntu 22.04)
# ou /etc/my.cnf (Rocky Linux 9)
[mysqld]
# Nível de isolamento padrão para todas as conexões
transaction-isolation = READ-COMMITTED
# Desabilita autocommit por padrão (exige START TRANSACTION em cada conexão)
autocommit = 0
# Tempo máximo (em segundos) que uma transação pode esperar por um lock de linha
innodb_lock_wait_timeout = 50
# Habilita a detecção automática de deadlock (altamente recomendado)
innodb_deadlock_detect = ON
# Tamanho do buffer para o undo log (relevante para transações longas)
innodb_undo_log_truncate = ON
innodb_max_undo_log_size = 2G
O parâmetro innodb_lock_wait_timeout é crucial. Se uma transação tentar modificar uma linha que está bloqueada por outra, ela aguardará até esse timeout em segundos antes de desistir e lançar um erro de Lock wait timeout. O valor padrão é 50 segundos. Em sistemas com alta taxa de requisições, valores menores (10-20s) podem ser apropriados para evitar filas de conexões bloqueadas. A detecção de deadlock, por sua vez, é ativada por padrão e deve permanecer ligada — o InnoDB consegue identificar ciclos de espera e resolver rompendo uma das transações envolvidas automaticamente.
Para aplicar as alterações sem reiniciar o servidor, você pode modificar variáveis dinâmicas (algumas requerem privilégio SUPER):
-- Alterando o timeout de lock dinamicamente (vale para novas conexões)
SET GLOBAL innodb_lock_wait_timeout = 20;
-- Alterando o nível de isolamento global (requer SUPER)
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
8. Verificando a Instalação / Testando a Configuração
Após modificar quaisquer parâmetros transacionais, é obrigatório validar que o ambiente está se comportando como esperado. Execute os comandos abaixo em sequência e compare as saídas.
-- 1. Verificar se o InnoDB está ativo e é o motor padrão
SELECT engine, support, transactions
FROM information_schema.engines
WHERE engine = 'InnoDB';
+--------+---------+--------------+
| engine | support | transactions |
+--------+---------+--------------+
| InnoDB | DEFAULT | YES |
+--------+---------+--------------+
1 row in set (0.01 sec)
-- 2. Verificar o estado global de autocommit e nível de isolamento
SHOW GLOBAL VARIABLES LIKE 'autocommit';
SHOW GLOBAL VARIABLES LIKE 'transaction_isolation';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)
+-----------------------+-----------------+
| Variable_name | Value |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set (0.00 sec)
-- 3. Listar transações ativas no momento (útil em produção)
SELECT trx_id, trx_state, trx_started,
trx_requested_lock_id,
trx_mysql_thread_id,
trx_query
FROM information_schema.innodb_trx;
Empty set (0.00 sec)
A consulta à tabela innodb_trx é sua principal ferramenta de diagnóstico de transações em andamento. Em um ambiente sem transações abertas, o retorno deve ser vazio. Se houver alguma listada, as colunas indicam o identificador da transação, o estado (RUNNING, LOCK WAIT, ROLLING BACK) e a thread associada — informação essencial para matar processos travados com KILL thread_id.
-- 4. Teste prático de rollback automático por violação de constraint
-- Vamos tentar debitar um valor que torne o saldo negativo (nossa constraint impede)
START TRANSACTION;
UPDATE contas SET saldo = saldo - 2000.00 WHERE id = 1;
-- O erro ocorrerá aqui e a transação será marcada como inválida
ERROR 3819 (HY000): Check constraint 'chk_saldo' is violated.
-- Agora, qualquer comando diferente de ROLLBACK ou COMMIT falhará
UPDATE contas SET saldo = 100.00 WHERE id = 1;
ERROR 1305 (42000): SAVEPOINT does not exist
-- Você deve explicitamente reverter a transação
ROLLBACK;
SELECT saldo FROM contas WHERE id = 1; -- retorna ao valor anterior
+---------+
| saldo |
+---------+
| 700.00 |
+---------+
1 row in set (0.00 sec)
Esse teste confirma o funcionamento da atomicidade e das constraints. Seu ambiente está validado.
9. Erros Comuns e Como Resolver
Ao trabalhar com transactions no MySQL, alguns erros aparecem com frequência, especialmente em equipes que estão migrando de ambientes não transacionais ou ajustando o nível de isolamento pela primeira vez. Abaixo, os quatro erros mais comuns que encontramos em campo na JRT Technology Solutions e como resolvê-los de forma definitiva.
-
Erro: “Lock wait timeout exceeded; try restarting transaction”
Causa: Uma transação aguardou mais que innodb_lock_wait_timeout segundos tentando adquirir um lock de linha que pertence a outra transação.
Sintoma: A consulta fica “travada” no cliente e, após o timeout, retorna o erro. As demais conexões podem acumular na fila de locks.
Solução: Identifique a transação bloqueadora com SELECT * FROM information_schema.innodb_trx WHERE trx_state = ‘RUNNING’;. Encontre a thread correspondente e, se necessário, execute KILL <thread_id>. Revise a lógica da aplicação para reduzir a duração das transações e considere diminuir o timeout em ambientes de alta rotatividade. -
Erro: “Deadlock found when trying to get lock; try restarting transaction”
Causa: Duas ou mais transações estão esperando mutuamente por locks que a outra possui. O InnoDB detecta o ciclo e escolhe uma “vítima” para ROLLBACK automático.
Sintoma: Uma das sessões recebe o erro imediatamente, enquanto a outra prossegue normalmente. Afeta mais sistemas com ORMs que geram consultas complexas.
Solução: Obtenha o relatório do último deadlock com SHOW ENGINE INNODB STATUS \G e procure a seção LATEST DETECTED DEADLOCK. Ele mostra as queries envolvidas e os locks. A correção geralmente está em padronizar a ordem de acesso às tabelas (ex: sempre atualizar tabelas na mesma sequência) e adicionar índices adequados para evitar lock de gap desnecessário. -
Erro: “Transaction already rolled back” ou comportamento inesperado após violação de constraint
Causa: Após um erro dentro de uma transação ativa, tentativas de executar novas instruções sem antes fazer ROLLBACK resultam em falha.
Sintoma: Códigos de aplicação que capturam exceções e tentam continuar na mesma transação geram o erro 1305.
Solução: Sempre, após capturar um erro em uma transaction, execute ROLLBACK explícito antes de qualquer nova operação. Em algumas linguagens (PHP, Python), é comum esquecer de chamar rollback no bloco except, mantendo a conexão em estado inconsistente. -
Erro: “Transactions não estão funcionando” — tabelas criadas com MyISAM
Causa: O motor MyISAM não oferece suporte a transações. Comandos como START TRANSACTION e ROLLBACK são ignorados sem erro.
Sintoma: Updates são persistidos imediatamente, independentemente de COMMIT ou ROLLBACK, e o comando SHOW TABLE STATUS mostra Engine=MyISAM.
Solução: Converta a tabela com ALTER TABLE nome_tabela ENGINE=InnoDB;. Para evitar o problema em novas tabelas, defina default_storage_engine=InnoDB no arquivo de configuração. Em nossos deploys na JRT Technology Solutions, sempre verificamos o motor antes de liberar o schema para produção.
10. Boas Práticas e Dicas Avançadas
Com frequência, vemos equipes que dominam a sintaxe de COMMIT e ROLLBACK mas cometem deslizes de design que transformam transactions em gargalos severos. A primeira regra de ouro é: mantenha suas transações tão curtas quanto possível. Locks mantidos por longos períodos (especialmente durante pausas para I/O externo, chamadas a APIs ou interação com usuário) são a causa número um de problemas de concorrência. Em sistemas web, uma transação deve começar e terminar dentro do mesmo request, sem depender de input humano entre o START e o COMMIT.
A segunda prática essencial é escolher o nível de isolamento adequado para cada caso de uso. O padrão REPEATABLE READ é seguro e adequado para a maioria das aplicações, mas pode gerar locks de gap excessivos em tabelas com alta frequência de inserts. Se sua aplicação tolera leituras não repetíveis e você identificou contenção, migre para READ COMMITTED — mas esteja ciente dos phantoms reads e considere usar SELECT … FOR UPDATE quando precisar de consistência em múltiplas leituras.
A terceira dica vem diretamente da nossa experiência na JRT Technology Solutions: implemente um mecanismo de retry automático em sua aplicação para erros de deadlock e lock wait timeout. Como o MySQL já faz rollback da transação vítima, a aplicação pode simplesmente capturar o erro, aguardar um intervalo aleatório (backoff exponencial) e reexecutar toda a transação. Isso torna o sistema resiliente a picos de concorrência sem intervenção humana.
Por fim, invista em monitoramento proativo. Consulte periodicamente as tabelas information_schema.innodb_trx e information_schema.innodb_lock_waits (esta última obsoleta no MySQL 8.0 em favor de performance_schema.data_locks e data_lock_waits) para detectar transações com duração anômala. Configure alertas para transações com mais de N segundos de vida. Em ambientes de missão crítica, um simples SELECT travado em lock wait pode derrubar toda a aplicação se não houver visibilidade.
Resumo da Aula 14
Nesta aula extensa, você aprendeu que Transactions são o mecanismo fundamental para garantir a consistência dos dados em um banco relacional. Exploramos a implementação das propriedades ACID no MySQL com InnoDB, desde o papel dos logs UNDO e REDO até o controle de concorrência via MVCC. Você executou na prática os comandos START TRANSACTION, COMMIT, ROLLBACK e SAVEPOINT, em cenários simulados que reproduzem situações reais de desenvolvimento.
Discutimos os níveis de isolamento e como eles afetam a visibilidade dos dados entre conexões, com testes comparativos entre REPEATABLE READ e READ COMMITTED. Você configurou parâmetros transacionais no arquivo my.cnf e aprendeu a verificar transações ativas, diagnosticar deadlocks e resolver os quatro erros mais comuns em produção. Ao final, solidificamos as boas práticas que separam um DBA ou desenvolvedor que usa o MySQL de quem realmente entende seu comportamento transacional.
A tabela abaixo resume os comandos principais que você deve ter internalizado após esta aula:
Quer aprender na prática com especialistas?
A JRT Technology Solutions oferece treinamentos e implementação de MySQL para equipes corporativas.