← Voltar ao blog
MySQL 22 de setembro de 2026 12 min de leitura

Tenho muitos dados, posso usar MySQL? Limites reais e como escalar

Bilhões de linhas em MySQL: onde estão os limites de verdade, por que o problema quase nunca é o tamanho da tabela, e as técnicas que uso — índices, particionamento, arquivamento e sharding.

Tenho muitos dados, posso usar MySQL? Limites reais e como escalar

"Ale, minha tabela vai passar de 500 milhões de linhas. MySQL aguenta?" Aguenta. O Facebook rodou (e roda) MySQL em escala que faria a sua tabela parecer uma planilha. A pergunta certa não é se aguenta — é o que você vai precisar fazer para que aguente bem.

Camadas empilhadas de dados luminosos representando grandes volumes em banco de dados
Volume não derruba banco. Working set mal dimensionado derruba.

Os limites formais (que você nunca vai alcançar)

LimiteValor
Tamanho de uma tabela InnoDB64 TB (com página de 16 KB)
Linhas por tabelaSem limite lógico do MySQL
Colunas por tabela4096 (na prática ~1017 no InnoDB)
Índices por tabela64

Ou seja: o teto do produto não é o seu problema. O seu problema é memória, I/O e desenho de índice.

O conceito que muda tudo: working set

O MySQL não precisa ter a tabela inteira na RAM. Ele precisa ter na RAM as páginas que você realmente acessa. Uma tabela de 2 TB em que 95% das consultas tocam os últimos 30 dias tem um working set de talvez 40 GB. Isso cabe num servidor comum.

Como medir se o seu buffer pool está sofrendo:

SELECT
  ROUND(@@innodb_buffer_pool_size/1024/1024/1024, 1) AS pool_gb,
  ROUND(100 - (
    (SELECT VARIABLE_VALUE FROM performance_schema.global_status
      WHERE VARIABLE_NAME='Innodb_buffer_pool_reads') /
    (SELECT VARIABLE_VALUE FROM performance_schema.global_status
      WHERE VARIABLE_NAME='Innodb_buffer_pool_read_requests') * 100
  ), 3) AS hit_ratio_pct;

Abaixo de 99% de hit ratio em carga OLTP, comece a se preocupar. Abaixo de 95%, o seu disco virou o seu banco de dados.

Passo 1: índice antes de qualquer arquitetura

Eu perdi a conta de quantos projetos queriam sharding e precisavam de um índice composto. Antes de mexer em topologia, ache as consultas que varrem a tabela inteira:

SELECT query, exec_count, rows_examined_avg, rows_sent_avg,
       ROUND(total_latency/1e12, 2) AS total_seg
FROM sys.statement_analysis
WHERE rows_examined_avg > 10000
ORDER BY total_latency DESC
LIMIT 10;

-- Índices que nunca foram usados (custam escrita e espaço)
SELECT * FROM sys.schema_unused_indexes;

Índice composto segue a regra da ordem: filtro de igualdade primeiro, faixa depois, ordenação por último. (cliente_id, criado_em) serve um WHERE cliente_id = ? ORDER BY criado_em DESC. O inverso não serve.

Passo 2: particionamento por data

Para tabelas históricas, partição por RANGE em data é o melhor custo-benefício que existe: o otimizador poda partições inteiras e você apaga meses antigos com DROP PARTITION — instantâneo, sem o inferno de um DELETE de 200 milhões de linhas.

CREATE TABLE eventos (
  id         BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  usuario_id BIGINT UNSIGNED NOT NULL,
  tipo       VARCHAR(40) NOT NULL,
  criado_em  DATETIME NOT NULL,
  PRIMARY KEY (id, criado_em),
  KEY idx_usuario (usuario_id, criado_em)
) ENGINE=InnoDB
PARTITION BY RANGE (TO_DAYS(criado_em)) (
  PARTITION p2026_01 VALUES LESS THAN (TO_DAYS('2026-02-01')),
  PARTITION p2026_02 VALUES LESS THAN (TO_DAYS('2026-03-01')),
  PARTITION p2026_03 VALUES LESS THAN (TO_DAYS('2026-04-01')),
  PARTITION pmax     VALUES LESS THAN MAXVALUE
);

-- Expurgo do mês mais antigo: segundos, não horas
ALTER TABLE eventos DROP PARTITION p2026_01;

Detalhe que pega muita gente: no MySQL, toda chave única — inclusive a primária — precisa conter a coluna de particionamento. Por isso o PRIMARY KEY (id, criado_em) acima.

Passo 3: arquivamento e leitura em réplicas

Dado morno vai para tabela de histórico ou para o data lake. Relatório pesado vai para réplica de leitura, nunca para o primário. Essa separação sozinha já devolve fôlego para o OLTP — e é o assunto de MySQL em aplicações críticas e escaláveis.

Passo 4: sharding (só quando doer de verdade)

Sharding é dividir os dados em vários servidores por uma chave (cliente, região, hash do id). Resolve escrita que estourou um servidor só — e cobra caro: você perde JOIN entre shards, transação distribuída vira problema seu, e rebalancear é projeto. Vá para sharding quando índice, partição e réplica já estiverem esgotados, não antes.

Quando MySQL realmente não é a resposta

  • Agregações analíticas sobre dezenas de TB o tempo todo: use engine colunar (ClickHouse, BigQuery) alimentada pelo MySQL.
  • Séries temporais com ingestão massiva contínua: TimescaleDB ou InfluxDB entregam mais por menos.
  • Busca textual sofisticada em escala: OpenSearch ao lado, com o MySQL como fonte da verdade.

Fora esses casos, "muitos dados" quase sempre significa "índice errado e buffer pool pequeno". Confira o panorama completo no guia definitivo de MySQL e, se a dúvida era de plataforma, veja MySQL ou PostgreSQL.


Sobre o autor: Alexandre Almeida atua com bancos de dados há mais de 25 anos, foi instrutor oficial de MySQL e Oracle e escreveu Aprendendo MySQL, onde há laboratórios guiados de índices, EXPLAIN e particionamento em máquina virtual pronta. Comandos testados em MySQL 8.4.

Fontes: MySQL 8.4 Reference Manual (InnoDB Limits, Partitioning, sys Schema) e experiência em ambientes de produção com tabelas acima de 1 bilhão de linhas.