Tecnologias/02 - Bancos de Dados/01 - Relacionais/02 - MySQL6 min
DDL Online e Operação
Conceito
Mudar o schema de uma tabela de 300 milhões de linhas sem parar o serviço é o problema operacional mais recorrente do MySQL. Ele existe por duas razões estruturais:
- DDL não é transacional (1. PostgreSQL vs MySQL vs Oracle): cada
ALTERfaz commit implícito, e uma migração que falha no meio deixa estado intermediário. - Historicamente, todo
ALTER TABLEreconstruía a tabela inteira com lock de escrita.
O MySQL 8.0 melhorou muito isso com três algoritmos:
ALGORITHM |
O que faz | Bloqueia escrita? | Reconstrói? |
|---|---|---|---|
INSTANT |
altera só os metadados | não | não — é instantâneo |
INPLACE |
altera a tabela sem copiá-la (na maioria dos casos) | não (só um lock breve no início/fim) | às vezes |
COPY |
cria tabela nova, copia tudo, troca | sim | sim |
-- Declare a intenção e falhe se o MySQL não puder cumprir:
ALTER TABLE pedidos ADD COLUMN cupom VARCHAR(20), ALGORITHM=INSTANT;
-- ERROR se não for possível instantâneo — melhor descobrir agora que em produção
O que é INSTANT (8.0+)
ADD COLUMN(no fim da tabela; a partir do 8.0.29, em qualquer posição)DROP COLUMN- Renomear coluna, alterar
DEFAULT SET/DROPdeNOT NULLem alguns casos, adicionar valores ao fim de umENUM
Limite importante: existe um teto de alterações instantâneas por tabela antes de exigir
uma reconstrução (total_row_versions), então INSTANT não é infinito.
O que não é online e exige ferramenta
- Mudar o tipo de uma coluna (
INT→BIGINT) — reconstrói - Mudar a chave primária
- Alterar charset/collation da tabela
- Adicionar
FULLTEXT(primeiro índice) ouSPATIAL - Qualquer coisa em versões antigas (5.6/5.7)
Para esses, o ecossistema resolveu com cópia com trigger ou binlog:
| Ferramenta | Como |
|---|---|
| pt-online-schema-change (Percona) | cria tabela nova + triggers que espelham escritas, copia em lotes, troca por RENAME |
| gh-ost (GitHub) | cria tabela nova e lê o binlog para espelhar (sem triggers) → menos impacto no caminho de escrita |
gh-ost --host=primario --database=loja --table=pedidos \
--alter="MODIFY id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT" \
--max-load=Threads_running=25 \
--critical-load=Threads_running=100 \
--chunk-size=1000 \
--max-lag-millis=1500 \
--allow-on-master --initially-drop-ghost-table --execute
O que faz o gh-ost superior em produção: ele pausa sozinho quando a carga ou o lag de
réplica sobem (--max-load, --max-lag-millis), e a troca final é um RENAME atômico.
Sem triggers, o caminho de escrita da aplicação não fica mais lento durante a migração.
Mesmo um ALTER "online" no primário chega na réplica como um evento que ela aplica
serialmente. Um ALTER de 40 minutos no primário é 40 minutos de lag na
réplica — e se sua aplicação lê da réplica, o incidente é lá, não no primário.
sql_mode: o interruptor de rigor
O MySQL histórico aceitava dado inválido com aviso. O sql_mode decide quanto disso
sobrevive:
sql_mode = 'STRICT_TRANS_TABLES,ONLY_FULL_GROUP_BY,NO_ZERO_DATE,
NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'
| Modo | Sem ele |
|---|---|
STRICT_TRANS_TABLES |
INSERT de string em INT grava 0 com aviso |
ONLY_FULL_GROUP_BY |
GROUP BY devolve coluna arbitrária de linha arbitrária |
NO_ZERO_DATE |
'0000-00-00' é uma data válida (e explode em todo driver) |
Verifique o sql_mode de produção hoje. Bancos antigos migrados de 5.x
frequentemente rodam permissivos, e apertar depois quebra queries existentes — o que é bom
saber antes do incidente.
performance_schema e sys
O MySQL moderno tem observabilidade boa, escondida atrás de nomes ruins. O schema sys
são views prontas sobre o performance_schema:
SELECT * FROM sys.statement_analysis LIMIT 20; -- queries por custo total
SELECT * FROM sys.schema_unused_indexes; -- índices que ninguém usa
SELECT * FROM sys.schema_redundant_indexes; -- índices duplicados
SELECT * FROM sys.schema_table_lock_waits; -- quem espera lock de tabela
SELECT * FROM sys.io_global_by_file_by_bytes LIMIT 10;
SELECT * FROM sys.host_summary; -- carga por host de origem
Trade-offs
DDL online reduz o bloqueio e não elimina o custo. INPLACE ainda lê e escreve a
tabela (I/O, redo, lag de réplica) e precisa de espaço temporário. "Online" quer dizer não
bloqueia, não é grátis.
pt-osc usa triggers: correto e caro. Cada INSERT/UPDATE/DELETE durante a
migração dispara uma trigger que escreve na tabela sombra — o que aumenta a latência de
escrita da aplicação justamente durante uma operação longa. gh-ost evita isso lendo
binlog, ao custo de precisar de acesso a binlog em formato ROW
(5. Replicação e Alta Disponibilidade).
Migração precisa ser retomável, porque DDL não é transacional. Toda migração de MySQL
deveria ser escrita como uma sequência de passos idempotentes (IF NOT EXISTS, checagem
em information_schema), com registro de progresso próprio. Isso não é preciosismo: é a
consequência direta do commit implícito.
O trade-off central: o MySQL escolheu simplicidade de motor e empurrou a complexidade de evolução de schema para ferramentas externas. É por isso que o ecossistema MySQL tem gh-ost e pt-osc, e o do Postgres não precisa de equivalente — lá o problema é o lock, e a solução é
lock_timeout+CONCURRENTLY(5. Índices e o Planejador).
Exemplo prático
Migração em produção: o procedimento
-- 1) descubra o que o MySQL consegue fazer, antes de rodar
ALTER TABLE pedidos ADD COLUMN canal VARCHAR(20) NULL, ALGORITHM=INSTANT;
-- deu erro? tente INPLACE. Erro de novo? é caso de gh-ost.
-- 2) declare limites: nunca deixe um DDL esperar indefinidamente
SET SESSION lock_wait_timeout = 5;
-- 3) evolução compatível em duas fases (expand/contract) — sempre
-- Fase 1 (deploy A): adiciona coluna NULL, app escreve nos dois lugares
ALTER TABLE pedidos ADD COLUMN canal VARCHAR(20) NULL, ALGORITHM=INSTANT;
-- Fase 2 (deploy B): app lê só a nova, backfill em lotes
UPDATE pedidos SET canal = 'web' WHERE canal IS NULL LIMIT 5000; -- em loop, com pausa
-- Fase 3 (deploy C): remove o código antigo e, por último, a coluna antiga
UPDATE de 50 milhões de linhas numa transação: undo gigante, lag de réplica enorme,
rollback impossível de esperar. Faça em lotes de 1–5 mil com LIMIT, uma pausa entre
eles, e um índice que ache o próximo lote rápido. Ver 3. Redo, Undo e Locks.
Checklist de operação MySQL
-
sql_modeestrito (verifique o valor real em produção) -
utf8mb4em servidor, banco, tabela e conexão do driver -
gtid_mode = ON,binlog_format = ROW,sync_binlog = 1 -
super_read_onlynas réplicas -
innodb_buffer_pool_sizeem 60–75% da RAM (2. InnoDB por Dentro) - Pool do app limitado, com
ConnMaxLifetime(5. Go em Produção) - gh-ost/pt-osc disponível e testado antes de precisar
-
performance_schemaligado;sys.statement_analysisno runbook - Slow query log com
long_query_time = 1 - Backup lógico (
mysqldump/mydumper) e físico (xtrabackup/clone plugin), com restore testado
Como isso falha em produção
| Sintoma | Causa |
|---|---|
ALTER travou toda a aplicação |
algoritmo COPY; ou lock de metadados esperando transação aberta |
| Migração falhou "no meio" | DDL não transacional; a migração não era retomável |
| Réplica com 40 min de lag após deploy | o ALTER é aplicado serialmente na réplica |
| Escrita lenta durante migração | triggers do pt-osc no caminho de escrita |
Waiting for table metadata lock |
uma transação longa impede o DDL — e o DDL enfileira todo o resto atrás |
Emoji virou ? depois de migrar |
charset da conexão ainda latin1/utf8mb3 |
GROUP BY mudou de resultado ao apertar sql_mode |
a query sempre foi ambígua; o modo permissivo escondia |
Aprofundamento aplicado
Migração também depende de quem já está usando a tabela
Uma operação online pode precisar de metadata locks no início ou no fim. Uma transação antiga pode impedir esse passo; novas consultas podem se acumular atrás da mudança. Online não significa ausência de espera, CPU, disco temporário ou impacto na replicação. Referência: limitações de DDL online.
Evolução em etapas
Para substituir uma coluna usada pela aplicação, uma estratégia é adicionar o novo campo, fazer as versões da aplicação conviverem, preencher o histórico em lotes e só remover o antigo após eliminar leitores e escritores dependentes.
O backfill deve ser retomável: grave progresso por chave, limite cada lote e monitore latência e atraso das réplicas. Uma única transação enorme torna rollback, retenção e recuperação mais caros.
Exercício: mantenha uma transação aberta e inicie uma alteração compatível com online em outra sessão de estudo. Resultado esperado: observe a fase que espera pelo lock; registre timeout e como a migração será retomada sem deixar o schema ambíguo.
Relacionado
- 2. InnoDB por Dentro
- 3. Redo, Undo e Locks
- 4. Índices e o Otimizador
- 5. Replicação e Alta Disponibilidade
- 1. PostgreSQL vs MySQL vs Oracle
- 6. Particionamento e Pooling de Conexões
Parte de MOC MySQL · Online DDL (docs) · gh-ost