Relational Database

MySQL

MySQL é o banco de dados relacional por trás de uma enorme parcela da web. O InnoDB oferece transações reais, a replicação facilita a escala de leituras e seu ecossistema é colossal.

intermediate15 min readUpdated 16 de set. de 2026
schema.sql
sql
-- schema.sql
CREATE TABLE products (
  id          BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  sku         VARCHAR(64) NOT NULL,
  name        VARCHAR(200) NOT NULL,
  price_cents INT UNSIGNED NOT NULL,
  stock       INT NOT NULL DEFAULT 0,
  attributes  JSON NOT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY uniq_products_sku (sku)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Lançado
1995
Licença
GPL / comercial (Oracle)
Storage engine
InnoDB (padrão)
Isolamento padrão
Repeatable Read
Charset
utf8mb4
Versão principal
9.x

Por que importa

Por que o MySQL ainda domina a web

Transações InnoDB

Bloqueio a nível de linha, transações ACID e recuperação de falhas são gerenciados pelo engine padrão, garantindo que a consistência não seja negligenciada.

Replicação para escala de leitura

Um primário transmite seu binary log para réplicas, e essas réplicas absorvem o tráfego de leitura ou ficam em standby para failover.

Um ecossistema colossal

Todo framework, linguagem e provedor de hospedagem suporta MySQL, o que significa que tutoriais, drivers e serviços gerenciados estão em toda parte.

O panorama completo

As três ideias por trás do MySQL

InnoDB para transações, índices para velocidade e replicação para escala de leitura — o motor foi refinado em torno dessas três funções.

InnoDB

Commit

O storage engine padrão fornece bloqueio a nível de linha, leituras MVCC, chaves estrangeiras e recuperação automática de falhas.

Indexes

Locate

Índices B-tree transformam varreduras completas em buscas rápidas, e a escolha do otimizador é visível através do EXPLAIN.

Connections

Serve

O MySQL gerencia cada conexão com uma thread, portanto, pools no lado da aplicação evitam que milhares de clientes sobrecarreguem o servidor.

Modelo de dados

Uma linha por produto

Uma linha representa um único produto vendável, com um SKU estável, um preço inteiro e um conjunto JSON de atributos variáveis.

A tabela de produtosMySQL table
  • idbigint unsignedChave primária AUTO_INCREMENT
  • skuvarchar(64)Unidade de manutenção de estoque (SKU) única e legível
  • namevarchar(200)Nome de exibição mostrado aos clientes
  • price_centsint unsignedPreço em unidades menores; evita dinheiro em ponto flutuante
  • stockintUnidades em estoque; pode ser negativo durante correções
  • attributesjsonPropriedades variáveis como cor, tamanho ou material

Uma linha representa um único produto vendável, com um SKU estável, um preço inteiro e um conjunto JSON de atributos variáveis.

Uma breve historia

De 18 tags a um padrao vivo

  1. 1995

    Lançamento do MySQL

    Michael Widenius e David Axmark publicam um banco de dados rápido e simples, voltado para cargas de trabalho web com alta leitura.

    95
  2. 2000

    Licenciamento GPL e a stack LAMP

    O licenciamento de código aberto ajuda o MySQL a se tornar o M em Linux, Apache, MySQL e PHP.

    00
  3. 2008

    Sun adquire MySQL AB

    A empresa é comprada pela Sun Microsystems e, dois anos depois, herdada pela Oracle.

    08
  4. 2010

    InnoDB torna-se o padrão

    O MySQL 5.5 torna o engine transacional o padrão, aposentando o MyISAM para cargas de trabalho sérias.

    10
  5. 2018

    MySQL 8 reescreve os internos

    Um dicionário de dados transacional, CTEs, window functions e melhor suporte a JSON chegam juntos.

    18

O guia completo

MySQL: Tudo que voce precisa saber

O que é MySQL?

MySQL é um banco de dados relacional de código aberto que se tornou a camada de armazenamento padrão da web primitiva. Foi lançado em 1995 com foco em velocidade e simplicidade para sites com alta carga de leitura, e cresceu junto com a stack LAMP — Linux, Apache, MySQL e PHP — tornando-se um dos bancos de dados mais amplamente implantados que existem.

Atualmente, ele é desenvolvido pela Oracle, mas uma grande comunidade e um rico conjunto de serviços gerenciados o mantêm presente em todos os lugares. WordPress, Magento, plataformas de e-commerce no estilo Shopify e inúmeras aplicações customizadas rodam em MySQL. Se você já utilizou um site com formulário de login, há uma boa chance de que uma tabela MySQL estivesse envolvida.

O MySQL moderno não é mais aquele motor simples dos anos 90. Desde a versão 8, ele possui um dicionário de dados transacional, common table expressions, window functions e um suporte robusto a JSON. Aprendê-lo hoje significa aprender um banco de dados relacional sério que, por acaso, também é extraordinariamente bem suportado.

Onde o MySQL é executado

A maior vantagem prática do MySQL é a sua ubiquidade. Quase todo host compartilhado, provedor de nuvem e plataforma-as-a-service o oferece. Frameworks já trazem drivers integrados, ORMs oferecem suporte nativo e DBAs possuem décadas de experiência com ele. Isso reduz o custo de tudo ao redor do banco de dados: contratação, ferramentas, monitoramento e migração.

É a escolha habitual para sistemas de gerenciamento de conteúdo, e-commerce, backends de SaaS e qualquer aplicação onde o ecossistema importa tanto quanto o motor. Ele escala de uma única instância pequena para clusters com sharding, e o caminho de um para o outro é amplamente documentado.

InnoDB é o engine que importa

O MySQL possui uma arquitetura de storage engine plugável, mas, na prática, você usará o InnoDB. Ele oferece:

  • Transações ACID com COMMIT e ROLLBACK.
  • Row-level locking (bloqueio a nível de linha), para que escritores não bloqueiem leitores.
  • Foreign keys e aplicação de constraints.
  • Crash recovery através de um redo log.
  • MVCC para leituras consistentes.

O engine MyISAM, mais antigo, não possuía transações nem row-level locking. Ele não tem espaço em um novo schema. Sempre declare ENGINE=InnoDB explicitamente para que a escolha fique visível e não dependa dos padrões do servidor.

CREATE TABLE products (
  id          BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  sku         VARCHAR(64) NOT NULL,
  name        VARCHAR(200) NOT NULL,
  price_cents INT UNSIGNED NOT NULL,
  stock       INT NOT NULL DEFAULT 0,
  attributes  JSON NOT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY uniq_products_sku (sku)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Tipos de dados e AUTO_INCREMENT

O sistema de tipos do MySQL é pragmático. As escolhas mais comuns:

  • INT e BIGINT — números inteiros; adicione UNSIGNED para IDs e contagens que não podem ser negativas.
  • VARCHAR(n) — texto de comprimento variável com um limite máximo. Ao contrário do Postgres, o MySQL realmente se beneficia de um comprimento sensato.
  • TEXT — textos grandes, armazenados fora da linha quando necessário.
  • DECIMAL(p, s) — decimais exatos, o tipo correto para dinheiro caso você não utilize unidades menores em inteiros.
  • TIMESTAMP e DATETIME — timestamps; TIMESTAMP converte para UTC e possui um limite de intervalo, enquanto DATETIME armazena exatamente o que você fornece.
  • JSON — um documento JSON validado e armazenado de forma eficiente.
  • ENUM — um conjunto fixo de strings; conveniente, mas alterar a lista exige uma mudança no schema.

O AUTO_INCREMENT gera o próximo número inteiro para uma coluna, quase sempre a chave primária. Ele é rápido e tolerante a lacunas: inserts que sofreram rollback consomem um valor, e inserts concorrentes podem não produzir números contíguos. Nunca dependa de que o ID não tenha lacunas ou de que sua ordem signifique algo.

INSERT INTO products (sku, name, price_cents, stock, attributes)
VALUES ('SKU-1', 'Widget', 999, 10, JSON_OBJECT('colour', 'blue'));

SELECT LAST_INSERT_ID();

CRUD sem surpresas

As quatro operações básicas mapeiam-se para quatro instruções. Leia-as como um conjunto, pois a estrutura se repete.

INSERT INTO products (sku, name, price_cents, stock, attributes)
VALUES ('SKU-2', 'Gadget', 1499, 5, JSON_OBJECT('colour', 'red'));

SELECT id, sku, name, price_cents
FROM products
WHERE stock > 0
ORDER BY price_cents ASC
LIMIT 20 OFFSET 40;

UPDATE products
SET price_cents = 1299, stock = stock - 1
WHERE id = 42;

DELETE FROM products
WHERE stock = 0 AND created_at < NOW() - INTERVAL 90 DAY;

Dois hábitos evitam os acidentes clássicos. Primeiro, sempre inclua uma cláusula WHERE em UPDATE e DELETE; sem ela, todas as linhas serão alteradas. Segundo, execute o SELECT equivalente primeiro para confirmar quais linhas você está prestes a afetar. O MySQL possui um modo sql_safe_updates que recusa instruções sem uma chave no WHERE, e ativá-lo em desenvolvimento é uma salvaguarda simples e eficaz.

LIMIT com OFFSET faz a paginação, mas offsets grandes escaneiam e descartam linhas. Para paginações profundas, prefira a paginação por keyset: WHERE id > :last_id ORDER BY id LIMIT 20.

Joins e agregação

Joins combinam tabelas com base em uma condição de correspondência, exatamente como no SQL padrão.

SELECT c.name             AS category,
       COUNT(*)           AS product_count,
       SUM(p.price_cents) AS inventory_value
FROM products p
JOIN categories c ON c.id = p.category_id
WHERE p.stock > 0
GROUP BY c.id, c.name
ORDER BY inventory_value DESC
LIMIT 20;

JOIN mantém os pares correspondentes, LEFT JOIN mantém todas as linhas da esquerda e preenche as colunas da direita ausentes com NULL. Agregadores como COUNT, SUM, AVG, MIN e MAX colapsam grupos; toda coluna selecionada que não seja agregada deve ser agrupada.

Historicamente, o MySQL permitia a seleção de colunas não agrupadas e retornava um valor arbitrário, o que escondia bugs. Com o ONLY_FULL_GROUP_BY ativado — o padrão no MySQL 8 — o servidor rejeita queries ambíguas, que é o comportamento desejado. Não o desative para fazer uma query antiga funcionar; corrija a query.

WHERE filtra linhas antes do agrupamento e HAVING filtra depois, portanto, condições sobre agregadores pertencem ao HAVING.

Índices e EXPLAIN

Um índice é uma estrutura ordenada que evita a varredura completa da tabela (full table scan). O MySQL cria automaticamente um para a chave primária e para cada constraint UNIQUE. Adicione outros para as colunas que você utiliza em filtros, joins e ordenações.

CREATE INDEX idx_products_price ON products (price_cents);

Um índice composto abrange várias colunas e segue a regra do prefixo à esquerda (leftmost-prefix rule): um índice em (category_id, price_cents) ajuda consultas que filtram por category_id, ou por category_id e price_cents, mas não por price_cents sozinho. Ordene as colunas da mais seletiva e filtrada com mais frequência para as menos.

Pergunte ao otimizador o que ele fará:

EXPLAIN
SELECT id, name, price_cents
FROM products
WHERE price_cents < 2500
ORDER BY price_cents
LIMIT 25;

Leia a coluna type primeiro: const, eq_ref e ref são bons; range é aceitável; index e ALL indicam uma varredura. A coluna key mostra qual índice foi escolhido, e rows estima quantas linhas serão examinadas. Um rows alto para um resultado pequeno geralmente significa um índice ausente ou inutilizável. Use EXPLAIN ANALYZE no MySQL 8 para executar a query e ver os tempos reais.

Os índices de cobertura (covering indexes) merecem menção. Se um índice contém todas as colunas que uma query precisa, o MySQL pode responder apenas com o índice e nunca tocar na linha da tabela. Adicionar uma coluna a um índice puramente para torná-lo de cobertura costuma trazer um ganho significativo de performance.

utf8mb4 e a armadilha do charset

Os conjuntos de caracteres são onde o MySQL costuma surpreender as pessoas. Durante a maior parte de sua história, o utf8 padrão suportava no máximo três bytes por caractere, o que cobre a maioria dos textos, mas não os code points de quatro bytes, como emojis e diversos scripts raros. Tentar armazenar um emoji em uma coluna utf8 gera um erro ou trunca o valor, dependendo do modo do servidor.

A solução é o utf8mb4, que é o UTF-8 real e armazena tudo. Configure-o em todos os níveis — servidor, banco de dados, tabela e conexão:

CREATE DATABASE shop
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_0900_ai_ci;

A collation decide como as strings são comparadas e ordenadas. A utf8mb4_0900_ai_ci é insensível a acentos e maiúsculas/minúsculas (case-insensitive), que é geralmente o que os usuários esperam em buscas. As collations também afetam o comportamento dos índices, portanto, mantenha-as consistentes em colunas relacionadas (joins); uma divergência força conversões que podem tornar um índice inutilizável.

Transações e níveis de isolamento

O InnoDB oferece transações. Agrupe escritas relacionadas para que elas tenham sucesso ou falhem juntas.

START TRANSACTION;

UPDATE inventory SET quantity = quantity - 1
WHERE product_id = 42 AND quantity >= 1;

INSERT INTO orders (product_id, quantity)
VALUES (42, 1);

COMMIT;

Verifique as linhas afetadas e ROLLBACK se a atualização protegida não alterou nada. O nível de isolamento padrão é o Repeatable Read, que fornece um snapshot consistente para a transação e, no InnoDB, utiliza gap locks que evitam phantom rows. Os outros níveis são Read Uncommitted, Read Committed e Serializable.

Uma diferença útil em relação ao Postgres: devido ao gap locking no Repeatable Read, inserts concorrentes em um intervalo podem causar bloqueios ou deadlocks com mais facilidade. Mantenha as transações curtas, atualize as linhas em uma ordem consistente e esteja preparado para tentar novamente em caso de deadlock, que o InnoDB reporta como um erro em vez de corromper o estado.

Replicação e escalonamento de leitura

A maioria das implantações de MySQL escala as leituras antes das escritas. O primário registra cada alteração em seu binary log, e uma ou mais réplicas se conectam e reproduzem esse log. A replicação é assíncrona por padrão, portanto, uma réplica pode apresentar um atraso (lag) em relação ao primário de milissegundos ou mais.

O padrão comum é enviar as escritas para o primário e distribuir as leituras entre as réplicas, aceitando que uma leitura possa retornar brevemente dados levemente desatualizados. Para consistência de leitura após a escrita (read-after-write consistency), direcione as leituras de um usuário para o primário por um curto período após a escrita dele, ou utilize a réplica apenas para dados que toleram esse atraso.

A replicação também proporciona alta disponibilidade. Se o primário falhar, uma réplica pode ser promovida. Ferramentas como orquestradores e serviços gerenciados automatizam esse failover, mas você ainda precisa entender a compensação (trade-off) entre os modos síncrono e assíncrono: o síncrono aguarda as réplicas e coloca a disponibilidade em risco, enquanto o assíncrono corre o risco de perder as últimas transações em caso de failover.

Upserts com ON DUPLICATE KEY UPDATE

O upsert idiomático do MySQL é o INSERT ... ON DUPLICATE KEY UPDATE. Quando o insert violaria uma chave primária ou única, a cláusula de update é executada em seu lugar.

INSERT INTO products (sku, name, price_cents, stock, attributes)
VALUES ('SKU-1', 'Widget', 1099, 5, JSON_OBJECT('colour', 'blue'))
ON DUPLICATE KEY UPDATE
  price_cents = VALUES(price_cents),
  stock       = stock + VALUES(stock);

Isso é atômico, o que é fundamental para contadores e inventários. A alternativa — fazer um select e então decidir se deve inserir ou atualizar — possui uma janela de race condition onde duas sessões podem não encontrar a linha e ambas realizarem o insert. Use o upsert sempre que a operação for genuinamente de inserção ou atualização.

Colunas JSON

O tipo JSON do MySQL armazena um documento validado e oferece suporte a funções para ler e escrever partes dele.

SELECT id, name,
       attributes->>'$.colour' AS colour
FROM products
WHERE attributes->>'$.colour' = 'blue';

Você pode indexar JSON utilizando colunas geradas. Como o MySQL não consegue indexar uma expressão JSON diretamente, extraia-a para uma coluna gerada armazenada (stored generated column) e indexe essa coluna:

ALTER TABLE products
  ADD COLUMN colour VARCHAR(32)
    GENERATED ALWAYS AS (attributes->>'$.colour') STORED,
  ADD INDEX idx_products_colour (colour);

Use JSON para atributos variáveis e payloads de terceiros. Mantenha os campos que você filtra constantemente como colunas reais; o truque da coluna gerada funciona, mas uma coluna nativa com um tipo real é mais simples e rápida.

Stored procedures: use com moderação

O MySQL suporta stored procedures, funções, triggers e eventos agendados. Eles podem reduzir as idas e vindas (round trips) ao servidor e centralizar a lógica, mas também movem a lógica de negócio para uma linguagem que é mais difícil de testar, versionar e depurar do que o código da sua aplicação.

Use-os para tarefas que sejam genuinamente voltadas ao banco de dados: manutenção em massa, migrações de dados e jobs que precisem rodar próximos aos dados. Evite colocar regras centrais de domínio em procedures que apenas uma equipe compreende. Triggers são especialmente fáceis de esquecer; um UPDATE que dispara silenciosamente três triggers é difícil de analisar, e efeitos colaterais ocultos acabam surpreendendo a todos eventualmente.

Usuários, permissões e backups

Crie um usuário de aplicação dedicado apenas com os privilégios necessários, em vez de se conectar como root.

CREATE USER 'app'@'%' IDENTIFIED BY 'a-strong-password';
GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'app'@'%';
FLUSH PRIVILEGES;

Mantenha o DDL e as migrations em uma conta separada e com mais privilégios. Restrinja os hosts sempre que possível, exija TLS e rotacione as credenciais.

Para backups, o mysqldump gera um dump lógico que é fácil de mover e restaurar:

mysqldump --single-transaction --routines --triggers shop > shop.sql
mysql shop_restore < shop.sql

A flag --single-transaction tira um snapshot consistente das tabelas InnoDB sem travá-las durante todo o dump. Para bancos de dados grandes onde o tempo de dump é proibitivo, utilize uma ferramenta física como o Percona XtraBackup. De qualquer forma, registre a posição do binary log para que você possa realizar a recuperação para um ponto específico no tempo (point-in-time recovery) e teste as restaurações.

MySQL vs MariaDB

O MariaDB começou como um fork comunitário do MySQL após a aquisição pela Oracle e divergiu desde então. Ele mantém a maior parte da sintaxe do MySQL e adiciona seus próprios storage engines e funcionalidades. Enquanto isso, o MySQL evoluiu rapidamente desde a versão 8, trazendo um novo dicionário de dados, window functions e melhorias no JSON.

Para a maioria das aplicações, as diferenças são pequenas. Escolha o MySQL se o seu provedor de banco de dados gerenciado ou contrato de suporte for baseado nele, e o MariaDB se você preferir um projeto governado pela comunidade ou precisar de um de seus engines. Os conceitos relacionais, SQL e padrões operacionais são transferíveis entre eles, portanto, a escolha raramente é irreversível.

Busca textual (Full-text search) no InnoDB

O InnoDB já vem com um índice de texto completo, portanto, um recurso de busca não exige um serviço separado. Crie um índice FULLTEXT nas colunas de texto e faça a consulta com MATCH ... AGAINST.

CREATE FULLTEXT INDEX ft_products_search
  ON products (name, attributes);

SELECT id, name,
       MATCH(name, attributes) AGAINST ('running shoe' IN NATURAL LANGUAGE MODE) AS score
FROM products
WHERE MATCH(name, attributes) AGAINST ('running shoe' IN NATURAL LANGUAGE MODE)
ORDER BY score DESC
LIMIT 20;

O NATURAL LANGUAGE MODE classifica por relevância e ignora palavras que aparecem na maioria das linhas. O BOOLEAN MODE oferece operadores como +must, -exclude e "exact phrase" para maior controle. O índice possui um comprimento mínimo de token controlado por innodb_ft_min_token_size, portanto, palavras muito curtas podem ser ignoradas.

A busca textual é ideal para catálogos de produtos e busca de artigos. Migre para um motor dedicado quando precisar de tolerância a erros de digitação (typo tolerance), faceting ou análise multilíngue que o MySQL não fornece.

Views e colunas geradas

Uma view é uma consulta nomeada que se comporta como uma tabela, sendo útil para empacotar um formato comum de dados e para limitar quais colunas um usuário de relatórios pode visualizar.

CREATE VIEW in_stock AS
SELECT id, sku, name, price_cents, stock
FROM products
WHERE stock > 0;

SELECT * FROM in_stock WHERE price_cents < 2500;

O MySQL 8 suporta window functions, portanto, as views também podem pré-formatar resultados analíticos. Lembre-se de que uma view executa sua consulta subjacente toda vez que é chamada, a menos que seja materializada manualmente em uma tabela; o MySQL não possui materialized views nativas, então as equipes utilizam ou uma tabela de resumo atualizada via agendamento ou um evento.

Colunas geradas, demonstradas na seção de JSON, são a outra metade desse conceito: uma coluna gerada do tipo stored é computada na escrita e pode ser indexada, enquanto uma virtual é computada na leitura. Use uma coluna stored quando precisar indexar a expressão, e uma virtual quando precisar do valor apenas ocasionalmente.

Alterando schemas em um banco de dados em produção

ALTER TABLE em uma tabela InnoDB grande pode reconstruir a tabela e manter locks por um longo período. O MySQL 8 suporta DDL online para diversas operações através do ALGORITHM=INPLACE, que reconstrói a tabela sem bloquear leituras e escritas simultâneas em muitos casos.

ALTER TABLE products
  ADD COLUMN updated_at TIMESTAMP NULL,
  ALGORITHM=INPLACE, LOCK=NONE;

ALTER TABLE products
  ADD INDEX idx_products_updated (updated_at),
  ALGORITHM=INPLACE, LOCK=NONE;

Nem toda alteração é online. Alterar o tipo de uma coluna, adicionar um índice FULLTEXT ou reconstruir uma chave primária geralmente ainda exige a cópia da tabela. Para esses casos, utilize ferramentas como pt-online-schema-change ou gh-ost, que criam uma tabela espelho (shadow table), copiam as linhas em lotes e fazem a substituição com o mínimo de bloqueio. Independentemente do método, execute as alterações de schema através de migrações versionadas e teste-as primeiro em uma cópia dos dados de produção.

Encontrando queries lentas

O MySQL registra queries que excedem long_query_time no slow query log, e o EXPLAIN mostra como uma query específica é executada. Juntos, eles são o caminho mais rápido para ir de “o app está lento” até uma correção concreta.

SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 0.2;

SHOW VARIABLES LIKE 'slow_query_log_file';

O performance_schema e o schema sys resumem os mesmos dados. Comece pelas instruções que consomem a maior quantidade de tempo total, e não apenas a query individual mais lenta, e verifique o EXPLAIN para identificar full scans em tabelas grandes. O SHOW PROFILE e o EXPLAIN ANALYZE no MySQL 8 adicionam tempos por etapa para quando você precisar de uma análise mais profunda.

Carregando dados rapidamente

Um loop INSERT linha por linha é a maneira mais lenta de carregar dados. Agrupe várias linhas em um único comando, o que reduz as viagens de ida e volta (round trips) e permite que o InnoDB escreva as páginas de forma eficiente.

INSERT INTO products (sku, name, price_cents, stock, attributes)
VALUES
  ('SKU-10', 'Widget', 999, 5, JSON_OBJECT('colour', 'blue')),
  ('SKU-11', 'Gadget', 1499, 3, JSON_OBJECT('colour', 'red')),
  ('SKU-12', 'Gizmo', 2499, 7, JSON_OBJECT('colour', 'green'));

Para importações massivas, o LOAD DATA INFILE transmite um arquivo diretamente para uma tabela e é drasticamente mais rápido do que qualquer forma de INSERT.

LOAD DATA LOCAL INFILE '/data/products.csv'
INTO TABLE products
FIELDS TERMINATED BY ',' ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS
(sku, name, price_cents, stock, @attributes)
SET attributes = CAST(@attributes AS JSON);

Envolva grandes cargas de dados em uma transação para que uma falha não deixe a tabela parcialmente importada; considere desativar índices secundários ou verificações de unicidade durante uma carga massiva pontual e, em seguida, reconstruí-los. Para gravações cotidianas da aplicação, um INSERT em lote é o padrão ideal.

Melhores práticas

  • Use InnoDB para todas as tabelas e declare-o explicitamente.
  • Crie bancos de dados, tabelas e conexões com utf8mb4 e uma collation consistente.
  • Armazene valores monetários como unidades menores em inteiros ou DECIMAL, nunca como FLOAT ou DOUBLE.
  • Adicione índices para as queries que você realmente executa e confirme-os com EXPLAIN.
  • Mantenha o modo ONLY_FULL_GROUP_BY padrão e escreva queries GROUP BY corretas.
  • Use INSERT ... ON DUPLICATE KEY UPDATE para upserts atômicos em vez de check-then-act.
  • Mantenha as transações curtas, atualize as linhas em uma ordem estável e tente novamente em caso de deadlock.
  • Atribua à aplicação um usuário com privilégios mínimos e mantenha o DDL em uma conta de migração.
  • Faça backups consistentes com --single-transaction, registre a posição do binlog e teste as restaurações.
  • Prefira keyset pagination em vez de scans extensos com LIMIT ... OFFSET.

Erros comuns

  • Manter tabelas em MyISAM e perder transações e o bloqueio a nível de linha (row-level locking).
  • Usar o antigo utf8 de três bytes e depois se perguntar por que os emojis não são salvos.
  • Armazenar dinheiro em FLOAT e acumular erros de arredondamento.
  • Executar UPDATE ou DELETE sem um WHERE e reescrever a tabela inteira.
  • Indexar todas as colunas em vez de focar nas queries que importam, o que torna as escritas mais lentas.
  • Confiar que o AUTO_INCREMENT seja contínuo (gapless) ou significativo.
  • Fazer read-modify-write no código da aplicação em vez de um upsert atômico.
  • Ler de uma réplica imediatamente após uma escrita e visualizar dados obsoletos (stale data).
  • Desativar o ONLY_FULL_GROUP_BY para esconder uma agregação incorreta.
  • Permitir que a aplicação se conecte como root.

Próximos passos

O MySQL e seus conceitos são amplamente aplicáveis. Se você quiser comparar com o outro principal motor relacional, leia o guia de PostgreSQL, que aborda os mesmos conceitos, mas com padrões diferentes. Para a linguagem que fundamenta ambos, o guia de SQL explora joins, CTEs e window functions a partir dos princípios básicos. E como a maioria das implantações de MySQL utiliza um cache, o Redis e o modelo de documentos do MongoDB são complementos naturais.

Na pratica

Schema, query, plano, upsert

Quatro tarefas cotidianas de MySQL: definir uma tabela InnoDB, fazer um join, inspecionar um plano e realizar um upsert com segurança.

schema.sql
CREATE TABLE categories (
  id   INT UNSIGNED NOT NULL AUTO_INCREMENT,
  name VARCHAR(80) NOT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY uniq_categories_name (name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE products (
  id           BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  category_id  INT UNSIGNED NOT NULL,
  sku          VARCHAR(64) NOT NULL,
  name         VARCHAR(200) NOT NULL,
  price_cents  INT UNSIGNED NOT NULL,
  stock        INT NOT NULL DEFAULT 0,
  attributes   JSON NOT NULL,
  created_at   TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  UNIQUE KEY uniq_products_sku (sku),
  KEY idx_products_category (category_id),
  CONSTRAINT fk_products_category
    FOREIGN KEY (category_id) REFERENCES categories (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Escrevendo um upsert

O banco de dados pode lidar com a inserção ou atualização atomicamente. Verificar primeiro e depois inserir deixa uma race condition entre as duas instruções.

Preferir
INSERT INTO products (sku, name, price_cents, stock)
VALUES ('SKU-1', 'Widget', 999, 5)
ON DUPLICATE KEY UPDATE
  stock = stock + VALUES(stock);
Evitar
SELECT id FROM products WHERE sku = 'SKU-1';
-- another session can insert the same
-- SKU between these two statements
INSERT INTO products (sku, name, price_cents, stock)
VALUES ('SKU-1', 'Widget', 999, 5);

Escolhendo um charset

utf8mb4 é o único charset que armazena todos os caracteres Unicode, incluindo emojis. O utf8 antigo é uma armadilha.

Preferir
CREATE TABLE notes (
  id   BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  body TEXT NOT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
  COLLATE=utf8mb4_0900_ai_ci;
Evitar
CREATE TABLE notes (
  id   BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  body TEXT NOT NULL,
  PRIMARY KEY (id)
) DEFAULT CHARSET=utf8;
-- utf8 here is a 3-byte subset: emoji
-- are silently rejected or mangled

Trade-offs

O MySQL é o banco de dados relacional certo para você?

O MySQL é testado em batalha e universalmente suportado. As compensações referem-se principalmente a padrões e dialetos.

Strengths

  • Provado em escala web

    Ele roda aplicações web com alta leitura há décadas, e o guia operacional para replicação e failover é maduro.

  • Presente em todo lugar

    Frameworks, ORMs, hosts e DBAs conhecem o MySQL, facilitando a integração e contratação, com abundância de opções gerenciadas.

  • Escala de leitura simples

    A replicação assíncrona torna simples adicionar réplicas e rotear queries de leitura para elas sem alterar o schema.

Trade-offs

  • Padrões podem ser traiçoeiros

    Servidores antigos ainda usam configurações e charsets mais fracos, e o Repeatable Read se comporta de forma diferente do Postgres; revise a configuração em vez de confiar nela.

  • O dialeto é peculiar

    Upserts, operadores JSON e funções são específicos do MySQL. SQL escrito aqui raramente migra para outro engine sem alterações.

  • Acúmulo de funcionalidades

    Stored procedures, triggers e events são poderosos e fáceis de usar em excesso, transformando a lógica de negócio em código de banco de dados difícil de testar.

Perguntas frequentes

Perguntas frequentes

Keep learning

Related topics from the roadmap.

$ comecar a aprender

Pronto para aprender MySQL?

Nosso tutorial interativo te guia por MySQL passo a passo — com quizzes e codigo real que voce pode executar no navegador.