trilha

Tecnologias/02 - Bancos de Dados/01 - Relacionais/02 - MySQL6 min

MySQL

Conceito

A tese do MySQL é simplicidade operacional. Ele venceu a web dos anos 2000 sendo o banco que qualquer pessoa conseguia instalar, replicar e manter — e essa herança ainda define o produto: menos recursos, menos superfície, menos surpresa na operação.

Duas heranças históricas explicam quase tudo o que confunde quem chega do Postgres:

  1. Motores de armazenamento plugáveis. MyISAM, InnoDB, MEMORY, ARCHIVE… O InnoDB venceu e é o padrão desde o 5.5, mas o MySQL ainda é uma camada SQL sobre um motor, não um sistema único. Por isso o otimizador é historicamente mais simples que o do Postgres.
  2. Tolerância a dado inválido. Por muitos anos, inserir '2024-02-31' gerava um aviso, não um erro. O STRICT_TRANS_TABLES virou padrão no 5.7, mas a cultura ("o banco aceita, o app valida") ficou.

A decisão que muda seu modelo: índice clusterizado

Este é o fato do InnoDB, e o que mais diferencia MySQL dos outros dois:

No InnoDB, a tabela é a árvore B+ da chave primária. Não existe heap separado. As linhas estão fisicamente ordenadas pela PK, dentro das folhas do índice.

Consequências diretas, todas práticas:

  • Busca por PK é imbatível: a folha do índice já contém a linha. Zero acesso extra.
  • Índice secundário guarda a PK, não um ponteiro físico. Buscar por um índice secundário e precisar de outras colunas custa dois percursos de árvore (o secundário, depois o clusterizado). No Postgres seria um acesso ao heap.
  • PK grande engorda todos os índices. Uma PK VARCHAR(64) é copiada em cada entrada de cada índice secundário.
  • PK aleatória fragmenta a escrita. Inserir com UUID v4 significa inserir no meio da árvore, sempre: page splits, cache sujo, I/O aleatório. Com AUTO_INCREMENT ou UUID v7 (ordenável por tempo) as inserções vão sempre no fim — a diferença de throughput em tabela grande é de várias vezes.

Se você lembrar de uma única coisa sobre MySQL, lembre disso. Ver 5. Índices.

Arquitetura em uma tela

  • Thread por conexão (com thread pool na versão Enterprise). Conexão é bem mais barata que no Postgres — centenas ou milhares são normais.
  • MVCC por undo log: as versões antigas vão para o undo (segmentos de rollback), fora da tabela. Não há bloat estilo Postgres; existe history list length. Ver 3. MVCC e Isolamento na Prática.
  • Redo log + doublewrite buffer para durabilidade; innodb_flush_log_at_trx_commit é o botão de durabilidade vs throughput (1 = ACID de verdade, 2 = perde até 1s num crash do SO).
  • Binlog é o log lógico de replicação — separado do redo. Replicação assíncrona por padrão, com GTID; semi-síncrona é opcional; Group Replication / InnoDB Cluster dá failover automático.
  • Isolamento padrão: REPEATABLE READ — diferente dos outros dois, e a fonte de surpresa nº 1 em migração (3. MVCC e Isolamento na Prática).

Versões: o modelo LTS + Innovation

Desde o 8.1 a Oracle separou as trilhas, o que confunde bastante:

Trilha Ritmo Para quem
LTS ~a cada 2 anos, correções por anos produção que não quer upgrade trimestral
Innovation trimestral, features novas quem testa e sobe rápido

Panorama em set/2026:

  • 8.4 LTS — a LTS anterior, ainda mantida (8.4.11)
  • 9.7 LTS — primeira LTS desde a 8.4 (abr/2026); trouxe JavaScript no banco e JSON duality
  • 26.7 Innovation (jul/2026) — a trilha de features; note o salto de numeração para o ano
  • 9.0 foi a Innovation que introduziu o tipo VECTOR e DDL atômico de database

Trade-offs

Menos garantias no schema. Não há tipos ricos (nada de array, range, jsonb indexável no mesmo nível), não há índice parcial, não há RETURNING no INSERT, e check constraints só passaram a ser realmente aplicadas no 8.0.16. Muita validação que o Postgres faz por você fica no app.

DDL não é transacional. Este é o trade-off mais caro do dia a dia:

-- No Postgres isto é atômico. No MySQL, NÃO.
START TRANSACTION;
  ALTER TABLE pedidos ADD COLUMN cupom varchar(20);  -- commit implícito!
  ALTER TABLE pedidos ADD COLUMN erro_aqui INT;      -- se falhar...
ROLLBACK;                                            -- ...não desfaz a primeira

Uma migração que falha no meio deixa o schema num estado intermediário, e a correção é manual. O 8.0 trouxe atomic DDL (cada instrução isolada é atômica, não fica meio aplicada) — mas não DDL dentro de transação. É por isso que ferramentas como gh-ost e pt-online-schema-change existem no mundo MySQL e não têm equivalente óbvio no Postgres.

Otimizador mais simples. Melhorou muito no 8.0 (CTE, window functions, hash join, histogramas), mas ainda perde do Postgres em consultas complexas — subqueries correlacionadas e agregações em várias tabelas são onde a diferença aparece. Ver SQL Tuning e 3. Subqueries.

O trade-off central: MySQL é mais fácil de operar e menos capaz de garantir. Ele empurra responsabilidade do banco para a aplicação — o que é ótimo quando a aplicação é uma só, e virou o motivo de tantos dados inconsistentes quando são seis.

Exemplo prático

O impacto de escolher a chave primária — a decisão mais barata de tomar certo e mais caro de corrigir:

-- ❌ Antipadrão comum: UUID v4 como PK no InnoDB
CREATE TABLE eventos_ruim (
  id   CHAR(36) PRIMARY KEY,     -- 36 bytes, ordem aleatória
  user_id BIGINT NOT NULL,
  criado_em DATETIME(6) NOT NULL,
  INDEX (user_id)                -- este índice carrega os 36 bytes da PK
);

-- ✅ Duas alternativas boas
CREATE TABLE eventos_bom (
  id   BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,  -- 8 bytes, sempre no fim
  uuid BINARY(16) NOT NULL UNIQUE,                  -- id externo, se precisar
  user_id BIGINT NOT NULL,
  criado_em DATETIME(6) NOT NULL,
  INDEX (user_id, criado_em)
);
-- ou: UUID v7 em BINARY(16) — ordenável por tempo, insere sempre no fim

O que muda de fato:

UUID v4 CHAR(36) AUTO_INCREMENT / UUID v7
Local da inserção meio aleatório da árvore sempre no fim
Page splits constantes quase nenhum
Tamanho dos índices secundários +36 B por entrada +8/16 B por entrada
Cache útil dilui (páginas quentes espalhadas) concentrado

Se você precisa de identificador opaco na API (e é uma boa prática), a resposta não é "PK aleatória": é PK sequencial interna + UNIQUE no identificador externo, ou UUID v7 — disponível nativamente também no Postgres 18.

Como isso falha em produção

Sintoma Causa real
Escrita degradando com o tempo PK aleatória fragmentando o índice clusterizado
ALTER TABLE travando a tabela por horas DDL sem ALGORITHM=INPLACE; use gh-ost/pt-osc
Réplica com lag crescente apply do binlog com pouco paralelismo, ou transação enorme no primário
Dado lido logo após escrever não existe leitura na réplica com replicação assíncrona (read-after-write)
Acentos e emoji virando ? charset utf8 (3 bytes, incompleto) em vez de utf8mb4
GROUP BY retornando coluna aleatória ONLY_FULL_GROUP_BY desligado — o MySQL "aceita" o que é ambíguo
Deadlock em UPDATE de faixa gap locks do REPEATABLE READ (3. MVCC e Isolamento na Prática)
Escrita "perdida" após failover replicação assíncrona: o primário confirmou e caiu antes de replicar
utf8 não é UTF-8

No MySQL, utf8 é um apelido histórico para utf8mb3 — três bytes por caractere, o que não cobre emoji nem parte do CJK. O correto é sempre utf8mb4 (com utf8mb4_0900_ai_ci como collation). É a armadilha mais famosa do produto.

Aprofundamento aplicado

Contrato transacional inclui o mecanismo de armazenamento

Uma aplicação precisa conferir engine, isolamento e modo SQL efetivos. O comportamento esperado do InnoDB não deve ser atribuído a qualquer tabela só porque ela está em MySQL.

SELECT @@transaction_isolation, @@autocommit, @@sql_mode;
SHOW CREATE TABLE pedidos;

O exemplo pressupõe uma tabela pedidos existente. Além do schema, examine se as operações que compõem a regra realmente usam a mesma conexão e transação. Com autocommit, duas instruções independentes não se tornam uma unidade apenas por serem consecutivas.

Cenário: criar o pedido e reservar estoque precisam confirmar juntos. Uma falha entre as instruções pode deixar pedido sem reserva se cada uma já confirmou sozinha. O tratamento de erro deve abortar a unidade de trabalho e representar retries deliberadamente.

Exercício: provoque erro na segunda escrita em uma transação de estudo. Resultado esperado: após rollback, nenhuma das alterações da unidade permanece. Repita com autocommit para observar a diferença.

Referência: autocommit, commit e rollback.

Relacionado


Parte de MOC MySQL · dev.mysql.com/doc · Releases de jul/2026

Buscar

Busca por título, seção e texto das notas