Todos os artigos
143 artigos · atualizado semanalmente Veja nossas Ferramentas
Todos os artigos
Tutoriais

O que é UUID e quais versões existem

UUID é um identificador único de 128 bits. Entenda por que ele substitui o auto-increment e qual versão usar — com destaque para o v7 em tabelas de alta escrita.

O que é UUID e quais versões existem
COVER · Tutoriais

Você precisa criar o registro antes de conhecer o seu identificador. Esse é o problema de fundo. Com auto-increment, o banco só te entrega o ID depois do INSERT — então você não pode gerar a chave no cliente, não pode montar relações antes do commit, e não pode coordenar dois serviços que escrevem na mesma tabela sem um ponto central de sincronização. O UUID resolve isso invertendo a ordem: o identificador nasce antes do registro, em qualquer máquina, sem perguntar nada ao banco.

Este artigo explica o que é um UUID, por que ele substitui (ou não) o auto-increment, e quais das versões realmente importam na prática — com uma opinião firme sobre qual usar quando a tabela escreve muito.

O que é um UUID, em concreto

UUID é um identificador único de 128 bits. Na forma textual canônica ele aparece como 32 dígitos hexadecimais em cinco grupos separados por hífen, no formato 8-4-4-4-12. O número total de valores possíveis é grande o bastante para que a colisão entre dois UUIDs gerados de forma independente seja, na prática, desprezível — você não precisa de um servidor central distribuindo faixas para garantir unicidade.

É justamente essa unicidade sem coordenação que dá ao UUID as suas três vantagens reais sobre o auto-increment. Primeiro: ele é gerável no cliente. O frontend, um worker ou um microsserviço criam o ID antes de tocar no banco. Segundo: não há ponto central de contenção — dois bancos em regiões diferentes podem gerar chaves para a mesma tabela lógica sem se falar. Terceiro: o UUID não vaza a contagem de registros. Um ID sequencial 47 na URL conta para qualquer um quantas linhas existem e abre a porta para enumeração; um UUID não diz nada sobre quantos vizinhos ele tem.

As versões que importam

A especificação define várias versões de UUID, mas no dia a dia você vai lidar com três, e talvez ouvir falar de mais duas. A versão é codificada dentro do próprio valor, então dá para saber qual gerou cada ID só de olhar.

A v4 é o padrão de fato. Ela é quase inteiramente aleatória: 122 bits de entropia, alguns bits fixos para marcar versão e variante. Não carrega timestamp, não carrega informação de máquina, não diz nada sobre quando ou onde foi gerada. É o que a maioria das bibliotecas entrega por padrão quando você pede "um UUID".

3f9a1c2e-7b4d-4f8a-9c1e-2a6b8d0f3e57

A v1 combina timestamp com o endereço MAC da placa de rede da máquina que gerou o ID. Em troca de ser ordenável no tempo, ela vaza duas coisas que você raramente quer expor: o instante exato da criação e um identificador de hardware. Hoje quase não há motivo para escolher v1 — a v7 entrega o lado bom sem o lado ruim.

A v7 é a versão que mudou o jogo. Ela coloca um timestamp em milissegundos nos bits mais significativos e preenche o resto com aleatoriedade. O resultado é um ID que é único como o v4, mas ordenável no tempo: dois UUIDs v7 gerados em sequência ordenam, como string ou como bytes, na ordem em que foram criados.

0190d4f2-3c7a-7e10-8b6f-1d2c3a4b5e6f

Repare no 7 que abre o terceiro grupo — é o nibble de versão. No v4 acima, esse mesmo dígito era 4.

Por fim, v3 e v5 são determinísticas: você passa um namespace e um nome, e elas devolvem sempre o mesmo UUID via hash (MD5 na v3, SHA-1 na v5). Servem quando você quer um ID estável derivado de uma entrada conhecida — por exemplo, mapear uma URL ou um e-mail para um identificador reproduzível. Não são para chave primária de linha nova; são para dedupe e idempotência.

O custo de usar UUID como primary key

Nada disso é de graça. Um auto-increment cabe em 4 bytes (INT) ou 8 bytes (BIGINT). Um UUID ocupa 16 bytes. Isso parece pouco até você lembrar que a primary key é replicada em toda secondary index da tabela, e em toda foreign key que aponta para ela. Em uma tabela com vários índices, a diferença de 8 a 12 bytes por linha se multiplica e infla o tamanho em disco e em memória — menos linhas por página, mais I/O, cache menos eficiente.

Guardar o UUID como texto de 36 caracteres piora tudo. Use o tipo nativo do banco (uuid no Postgres) ou BINARY(16) quando não houver um; nunca VARCHAR(36) para a chave.

v4 versus v7 em tabela de alta escrita

Aqui vem a opinião, e ela é firme: para tabela de alta escrita, use v7, não v4.

O motivo é o índice. A primary key normalmente é um índice B-tree clusterizado ou clustered-friendly. Quando os IDs chegam em ordem aproximadamente crescente, cada novo INSERT cai na última página do índice — localidade boa, poucas páginas sujas, pouca reorganização. É exatamente o comportamento que o auto-increment sempre teve de graça.

O v4 destrói isso. Como ele é aleatório, cada INSERT cai numa posição arbitrária do índice. Você suja páginas espalhadas por toda a estrutura, força page splits no meio da árvore e fragmenta o índice. Em volume alto de escrita isso vira write amplification e queda de throughput visível.

O v7 recupera a localidade temporal: como o prefixo é um timestamp, inserts próximos no tempo caem próximos no índice, igual ao auto-increment — só que sem coordenação central e gerável no cliente. Você fica com o melhor dos dois mundos. Se a sua stack ainda não gera v7 nativamente, dá para gerar um UUID v7 direto no navegador para testar o formato antes de fechar o esquema.

A escolha de tipo de chave também conversa com a escolha de banco — vale o mesmo cuidado que você tem ao decidir entre Postgres e MySQL, porque o suporte nativo a uuid e o comportamento de índice clusterizado diferem entre os dois.

Quando o auto-increment ainda basta

Não troque por reflexo. Se a tabela é interna, de baixa escrita, vive num único banco e nunca expõe o ID numa URL pública, um BIGINT auto-increment é menor, mais rápido e mais simples de ler em log. UUID paga por flexibilidade — geração distribuída, sem vazamento de contagem, sem coordenação. Se você não usa nenhuma dessas três coisas, está só pagando 16 bytes e um índice maior por nada.

Perguntas frequentes

UUID v4 pode colidir?

Em teoria sim, na prática não. São 122 bits de aleatoriedade, o que dá um espaço de valores grande o suficiente para que a chance de duas máquinas gerarem o mesmo v4 seja desprezível dentro de qualquer volume realista. O risco real não é a matemática — é um gerador de aleatoriedade mal configurado. Use a biblioteca padrão da plataforma e confie nela.

Devo guardar UUID como texto ou binário?

Binário, sempre que possível. No Postgres use o tipo uuid nativo, que armazena os 128 bits em 16 bytes. Onde não houver tipo nativo, use BINARY(16). Guardar como VARCHAR(36) mais que dobra o espaço da chave e desperdiça memória em todo índice que a referencia.

v7 já é seguro para produção?

Sim. A versão foi padronizada e as bibliotecas das principais linguagens já a implementam. Se a sua versão de biblioteca ou de banco ainda não gera v7 nativamente, atualize ou gere o valor na camada de aplicação — o formato em si é estável e não vai mudar.

Posso migrar de auto-increment para UUID depois?

Pode, mas não é trivial. Você precisa adicionar a coluna UUID, popular as linhas existentes, reapontar todas as foreign keys e só então trocar a primary key. Em tabela grande isso é uma migração planejada com downtime ou com escrita dupla — não um ALTER TABLE de cinco minutos. Decidir cedo é mais barato.

O que levar daqui

UUID resolve um problema concreto: gerar identificadores únicos sem coordenação central, no cliente, sem vazar contagem. Se você não precisa de nenhuma dessas três propriedades, o auto-increment continua menor e mais rápido. Se precisa, escolha a versão pelo uso: v4 para a maioria dos casos, v3/v5 quando o ID tem que ser determinístico, e v7 — não v4 — sempre que a tabela escreve muito, porque a localidade temporal do v7 é o que mantém o seu índice B-tree saudável sob carga.

RD
Autor
Rafael Duarte
Desenvolvedor backend com passagem por fintech e SaaS B2B — trabalhou em times que escalaram APIs de zero a milhões de requisições. Carrega cicatrizes de produção suficientes para ter opiniões fortes sobre ferramentas, padrões e decisões de arquitetura. Não é acadêmico: leu a RFC do UUID quando precisou escolher entre v4 e v7 para uma tabela de alta escrita.
Ver perfil