name: mysql
description: Expertise específica em banco de dados MySQL/MariaDB — tipos de dados e charset/collation corretos (utf8mb4, DECIMAL para dinheiro, VARCHAR/INT no tamanho certo, trade-offs de ENUM), InnoDB vs MyISAM, design de índices e tuning de consultas guiado por EXPLAIN (ordem de índices compostos, índices covering, predicados sargáveis, paginação por keyset), isolamento de transações e locking (REPEATABLE READ, gap locks, deadlocks), e armadilhas específicas de MySQL. Use quando package.json listar mysql2 ou mysql, o dialeto/provider de um ORM estiver configurado como mysql (Prisma provider = "mysql", Sequelize dialect: 'mysql', TypeORM type: 'mysql'/'mariadb'), um serviço docker-compose executar imagem mysql/mariadb, ou uma string de conexão mysql:// estiver presente, inclusive quando for solicitado diretamente (em tailandês ou inglês) para escrever ou revisar schema/query MySQL de forma correta e eficiente. Não substitui o workflow de migração engine-agnóstico de database-schema-dev nem o código de camada de acesso de backend-dev — esta é a expertise MySQL que ambos utilizam quando o motor é MySQL/MariaDB.
MySQL
Camada de expertise específica MySQL para database-schema-dev e backend-dev:
esses agentes são engine-agnósticos e se adaptam a qualquer ferramenta de migração/ORM que o projeto
já tenha — esta habilidade é o conhecimento real de MySQL que eles utilizam
quando o motor é MySQL, ou seu fork compatível MariaDB.
Onde MariaDB se comporta de forma diferente, isso é destacado — não assuma
paridade total em funcionalidades avançadas.
Passo 1 — Confirmar que o motor é realmente MySQL/MariaDB
Verifique package.json para mysql2/mysql, um dialeto/provider de ORM
(provider = "mysql" no Prisma, dialect: 'mysql' no Sequelize,
type: 'mysql'/'mariadb' no TypeORM), um serviço em docker-compose.yml
executando uma imagem mysql/mariadb, ou uma string de conexão mysql://. Se
nada disso for verdade, pare — use os padrões engine-agnósticos de
database-schema-dev/backend-dev, ou a habilidade específica do motor correspondente.
Passo 2 — Charset, collation, storage engine
- O charset padrão é
utf8mb4, nãoutf8— outf8do MySQL é um alias de no máximo 3 bytes que não consegue armazenar a maioria dos emojis/caracteres astrais — com uma collation explícita (utf8mb4_0900_ai_cino MySQL 8+; MariaDB não tem collations0900, useutf8mb4_unicode_cipara qualquer coisa que almeje ambos). InnoDBa menos que haja uma razão específica paraMyISAM(sem transações, sem locks de linha, sem FKs) — é o padrão desde MySQL 5.5/MariaDB 10.x, então isso geralmente é apenas confirmar que nada o substitui.- Collation
_cisignifica que'[email protected]'corresponde a'[email protected]'— use_bin/_cs(ou uma coluna com hash) para qualquer coisa que precise comparar com distinção de maiúsculas/minúsculas (tokens, segredos).
Passo 3 — Tipos de dados corretos
- Dinheiro/decimais exatos:
DECIMAL(p,s), nuncaFLOAT/DOUBLE— ponto flutuante binário não consegue representar a maioria das frações base-10 exatamente. - Inteiros no tamanho certo (
TINYINT/SMALLINT/INT/BIGINT) pelo intervalo real, nãoBIGINTem todo lugar por hábito — tipos menores significam índices menores.UNSIGNEDé espaço livre adicional em uma coluna de id, que nunca é negativa. DATETIMEpara um valor de relógio de parede sem conversão de fuso horário pretendida;TIMESTAMP(normalizado para UTC, converte automaticamente por fuso horário da sessão, limite de faixa em 2038) apenas para um instante real no tempo — não use como padrão.VARCHAR(n)dimensionado pela restrição real, nãoVARCHAR(255)por reflexo;TEXT/BLOBpara conteúdo genuinamente sem limites — nenhum dos dois aceita um valor padrão ou um índice completo (não prefixado).ENUMapenas para um conjunto fixo e raramente mutável — valores são ordenados pela ordem de definição, não alfabeticamente (uma surpresa emORDER BY), e anexar um valor é apenas metadados no MySQL 8, mas pode reescrever a tabela no MariaDB/MySQL mais antigo. Uma tabela de lookup escala melhor assim que o conjunto cresce ou precisa de metadados.JSONnativo (MySQL 5.7.8+/MariaDB 10.2+ — oJSONdo MariaDB é na verdadeLONGTEXTmais umCHECK, não um tipo binário, então nenhum dos benefícios de armazenamento binário/atualização parcial do MySQL se aplicam) apenas para dados genuinamente sem schema — qualquer coisa filtrada ou com join regularmente pertence a uma coluna real.
Passo 4 — Design de índices
- Índices compostos: coluna(s) filtradas por igualdade primeiro, a coluna de faixa/ordenação
por último —
(status, created_at)atendeWHERE status = ? ORDER BY created_at; a ordem inversa não. - Regra do prefixo mais à esquerda: um índice em
(a, b, c)também atende consultas(a)e(a, b)— não adicione um índice redundante(a)junto com ele. - Um índice covering (toda coluna selecionada incluída) permite que MySQL responda a partir
apenas do índice (
Using indexnoEXPLAIN) — vale a pena para uma consulta quente e estreita, não por padrão em toda consulta. - Índice prefixo (
col(20)) paraVARCHAR/TEXTlongo — verifiqueCOUNT(DISTINCT LEFT(col, n))contraCOUNT(DISTINCT col)para dimensionarn. - Paginação profunda:
LIMIT 20 OFFSET 100000ainda varre e descarta 100.000 linhas a cada página. Paginação por keyset (WHERE (created_at, id) < (:last_created, :last_id) ORDER BY created_at DESC, id DESC LIMIT 20) reutiliza o mesmo índice composto e busca diretamente pelo cursor — use-o para scroll infinito ou paginação profunda de API em vez deOFFSET. - Cada índice custa tempo de escrita e espaço em disco — adicione índices para padrões de consulta
reais (
database-schema-devPasso 1), não especulativamente por coluna.
Passo 5 — Tuning de consultas guiado por EXPLAIN
- Execute
EXPLAIN(ouEXPLAIN ANALYZE, MySQL 8.0.18+/MariaDB 10.4+) antes de confiar que uma consulta é rápida — não julgue apenas pela forma da consulta. type: ALLé uma varredura completa de tabela;ref/range/constsignifica que um índice é realmente usado — uma consulta que vale a pena otimizar não deve mostrarALLalém de tamanho de tabela trivial.Extra: Using filesort/Using temporarysignifica que MySQL ordenou/materializou fora de um índice — geralmente corrigido por um índice que corresponda aoORDER BY/GROUP BY.- Uma função envolvendo ou cast implícito em uma coluna indexada
(
WHERE YEAR(created_at) = ..., comparando uma coluna string a um número) impede o uso do índice — reescreva como uma faixa de coluna nua (created_at >= '2024-01-01' AND created_at < '2025-01-01'). SELECT *em uma tabela larga impede índices covering e puxa colunasTEXT/BLOBque ninguém pediu — selecione apenas o que é usado.
Passo 6 — Transações e locking
- InnoDB bloqueia linhas, não tabelas, mas o isolamento padrão
REPEATABLE READtambém coloca gap locks em varreduras de faixa emUPDATE/DELETE/SELECTcom lock — uma condição de faixa pode bloquear mais do que as linhas correspondentes.READ COMMITTEDevita gap locks se leituras repetíveis não forem necessárias. - Esse snapshot cobre apenas um
SELECTsimples — uma leitura com lock na mesma transação (SELECT ... FOR UPDATE,UPDATE,DELETE) lê os dados mais recentes commitados, então pode ver uma linha que o snapshot nunca mostrou. Não assuma que o isolamento de snapshot também protege uma leitura com lock. - Mantenha transações curtas — uma transação aberta mantém seus locks e, sob
REPEATABLE READ, também bloqueia a purga de versões antigas de linhas pelo InnoDB. SELECT ... FOR UPDATEpara bloquear linhas lidas e depois escritas em uma transação (verificação de saldo antes do débito); adquira locks em uma ordem de linha consistente em todo o código para evitar deadlocks.- Um deadlock (
ERROR 1213, SQLSTATE40001) é esperado sob contenção real — InnoDB automaticamente desfaz um dos lados; capture esse código e tente novamente em vez de "consertar" com uma transação mais longa.SHOW ENGINE INNODB STATUS→LATEST DETECTED DEADLOCKmostra o que cada lado mantinha/esperava. - Autocommit está ligado por conexão por padrão — atomicidade de múltiplas instruções precisa de uma transação explícita (ou do wrapper do ORM), não apenas adjacência no código.
Passo 7 — Armadilhas específicas de MySQL e divergências do MariaDB
sql_modedeve incluirSTRICT_TRANS_TABLES(padrão desde MySQL 5.7/MariaDB 10.2) — sem ele, valores fora de faixa/tipo errado são truncados silenciosamente em vez de gerar erro; confirme que não foi relaxado.- Valores
AUTO_INCREMENTnão são livres de lacunas (um rollback ainda consome um) — nunca confie que ids são contínuos. time_zoneda sessão afeta a conversão deTIMESTAMPmas não deDATETIME— um pool com fusos horários mistos por conexão é um bug real de "hora errada"; fixe-o explicitamente.- MariaDB tem suas próprias extensões sem equivalente no MySQL (
CREATE SEQUENCE,RETURNINGem escritas) — não as use se MySQL também for um alvo; funções de janela/CTEs precisam de MySQL 8.0+/MariaDB 10.2+, verifique a versão fixada primeiro. - O otimizador e a saída
EXPLAINdo MariaDB não são o mesmo motor que os do MySQL — reexecuteEXPLAINno motor real em uso em vez de confiar em um plano ajustado no outro.
Passo 8 — Autoverificação antes de relatar como concluído
Confirme que toda coluna nova/alterada tem um tipo intencional e o charset padrão é
utf8mb4. Confirme que qualquer novo índice é baseado em um padrão de consulta real
com saída EXPLAIN mostrando que é usado, e qualquer endpoint de lista profunda/sem limites
usa keyset em vez de OFFSET. Confirme que escritas com múltiplas instruções
estão envolvidas em uma transação explícita e o comportamento de isolamento de uma leitura com lock
é deliberado, não assumido. Se o arquivo de compose/CI fixar MariaDB,
confirme que nada adicionado assume uma funcionalidade exclusiva do MySQL 8.
O que não fazer
- Não use charset
utf8ouFLOAT/DOUBLEpara dinheiro —utf8mb4eDECIMALsão os padrões corretos. - Não adicione um índice sem um padrão de consulta por trás dele, nem chame uma consulta
de "rápida" sem verificar
EXPLAIN. - Não use paginação
OFFSETem uma lista que pode paginar profundamente ou ser ilimitada — use keyset. - Não deixe uma transação aberta por mais tempo do que o trabalho que ela protege,
especialmente sob
REPEATABLE READcom seus gap locks. - Não confie na contiguidade de
AUTO_INCREMENTou em coerção implícita de tipo para correção. - Não assuma que um alvo MariaDB tem funcionalidades exclusivas do MySQL 8 (JSON
nativo,
utf8mb4_0900_ai_ci) sem verificar a versão fixada. - Não escreva o código de migração/camada de acesso engine-agnóstico você mesmo —
isso é papel de
database-schema-dev/backend-dev; esta habilidade é o conhecimento MySQL que eles aplicam sobre ele.