Armazenar timestamps em bancos: DATETIME vs INT vs BIGINT
Escolher o tipo de coluna errado para timestamps causa deriva de fuso, o estouro do ano 2038, consultas de intervalo quebradas e saída de API confusa. Compare os tipos datetime nativos, os valores epoch BIGINT e as strings no MySQL e PostgreSQL.
Três formas de armazenar um timestamp
A maioria dos bancos oferece pelo menos três opções: um tipo datetime nativo (TIMESTAMP, DATETIME, TIMESTAMPTZ), um inteiro simples (INT ou BIGINT) ou uma string (VARCHAR). Cada um tem compromissos diferentes em tamanho de armazenamento, ergonomia de consulta, tratamento de fuso e preparação para o futuro. Para a maioria dos bancos de produto, uma coluna datetime nativa é o melhor padrão porque o banco pode comparar, indexar, truncar, agrupar e formatar o valor como tempo em vez de como um número anônimo.
- Tipo datetime nativo — o melhor para aritmética de datas, conversão de fuso e legibilidade
- Inteiro BIGINT — bom para inserções de alto throughput e consultas numéricas de intervalo simples
- String VARCHAR — quase sempre errado: a comparação de strings de datas só funciona com formato ISO 8601 estrito
- Inteiro INT — evite para timestamps futuros a menos que tenha verificado a fundo o limite do ano 2038
MySQL: TIMESTAMP vs DATETIME vs INT
O MySQL tem dois tipos de data-hora que parecem semelhantes mas se comportam de modo muito diferente — e um deles tem uma data de expiração rígida. TIMESTAMP é conveniente quando você quer conversão automática entre UTC e o fuso de sessão, mas seu intervalo histórico de 32 bits o torna arriscado para dados de produto voltados ao futuro. DATETIME armazena a data e hora literais que você fornece, o que costuma ser mais claro quando a aplicação padroniza em UTC antes de gravar.
- TIMESTAMP: armazenado internamente como segundos Unix de 32 bits — limitado de 1970-01-01 a 2038-01-19
- TIMESTAMP: auto-converte entre UTC e o fuso de sessão ao inserir/ler
- DATETIME: armazena a data-hora literal, sem fuso. Intervalo 1000-01-01 a 9999-12-31. Não afetado pelo Y2038.
- DATETIME: não converte fusos — você controla UTC no nível da aplicação
- Recomendação: use DATETIME com valores UTC explícitos para tabelas novas e evite o limite de 2038
PostgreSQL: TIMESTAMPTZ é a escolha certa
O TIMESTAMP WITH TIME ZONE (TIMESTAMPTZ) do PostgreSQL armazena os timestamps como microssegundos UTC internamente e os converte para o fuso de sessão na saída. É a opção mais segura e correta para a maioria dos casos porque representa um instante real no tempo. O nome pode enganar: TIMESTAMPTZ não armazena o rótulo de fuso original como America/New_York. Ele armazena o instante e então o exibe conforme o fuso de sessão atual.
- TIMESTAMPTZ: armazena UTC, converte para o fuso de sessão na saída — portátil e seguro quanto ao horário de verão
- TIMESTAMP (sem fuso): armazena o valor literal sem conversão — use apenas para dados sem fuso
- EXTRACT(EPOCH FROM col): retorna segundos Unix como float de qualquer coluna TIMESTAMP
- TO_TIMESTAMP(epoch): converte segundos Unix de volta para um TIMESTAMPTZ
Indexação e desempenho de consultas
Para tabelas de aplicação normais, a diferença de desempenho entre colunas datetime nativas e colunas epoch BIGINT raramente é o fator decisivo. A forma da consulta, o design do índice, o particionamento e o número de linhas importam mais. Escolha primeiro o tipo que mantém o significado correto, depois indexe-o para as consultas de intervalo que sua aplicação realmente executa.
- Os três tipos suportam índices B-tree e consultas de intervalo eficientes
- Inteiros BIGINT são marginalmente mais rápidos para varreduras de igualdade e intervalo em tabelas de altíssimo volume
- Tipos datetime nativos permitem consultas indexadas por parte da data: WHERE created_at::date = '2024-01-01'
- Timestamps VARCHAR são os piores para desempenho — a comparação de strings não entende datas
Quando o armazenamento epoch em BIGINT faz sentido
BIGINT é razoável quando os dados são do tipo evento, com muitas inserções, e já produzidos como tempo Unix por outro sistema. Pipelines de analytics, fluxos de telemetria, filas e protocolos binários compactos costumam usar milissegundos epoch porque valores numéricos são rápidos de comparar e neutros à linguagem. O compromisso é a legibilidade: humanos precisam de um conversor e a aritmética de datas em SQL fica mais verbosa.
- Use BIGINT para milissegundos Unix se clientes JavaScript produzem os eventos diretamente
- Use BIGINT para segundos Unix se o sistema de origem é estilo Unix e a precisão de segundos basta
- Documente a unidade no nome da coluna: created_at_ms é mais claro que created_at_epoch
- Adicione uma coluna datetime gerada se os analistas precisam de consultas SQL legíveis
- Evite INT para timestamps modernos voltados ao futuro por causa dos limites de intervalo de 32 bits
Padrões de esquema recomendados
Para a maioria das aplicações web, armazene um instante em UTC e armazene o fuso preferido do usuário separadamente apenas quando precisar reconstruir a intenção de relógio local. Uma reunião marcada para 9:00 America/New_York é diferente de um log de eventos criado em um instante UTC preciso; modele esses casos de forma diferente.
- Logs de eventos: created_at TIMESTAMPTZ no PostgreSQL, ou created_at DATETIME em UTC para MySQL
- Ingestão de eventos JavaScript: created_at_ms BIGINT mais documentação clara da API
- Agendamentos locais recorrentes: local_date, local_time e timezone_id, e então calcule o próximo instante
- Timestamps de expiração: expires_at como datetime nativo ou expires_at_seconds com segundos Unix explícitos
- Tabelas de auditoria: mantenha created_at e updated_at como colunas datetime nativas para depuração legível
FAQ sobre timestamps em bancos de dados
- Devo armazenar UTC ou hora local em um banco de dados?
- Armazene UTC para timestamps de eventos e converta para hora local ao exibir. Armazene um identificador de fuso separadamente quando a intenção de relógio local do usuário importar, como reuniões recorrentes ou horário comercial.
- BIGINT é melhor que TIMESTAMP?
- Não em geral. BIGINT é útil para pipelines numéricos de epoch, mas tipos datetime nativos são mais fáceis para aritmética de datas em SQL, depuração legível e saída ciente de fuso.
- O MySQL deve usar TIMESTAMP ou DATETIME?
- Para tabelas de aplicação novas, DATETIME com valores UTC costuma ser mais seguro porque evita o limite de intervalo de 2038 e não depende silenciosamente da conversão de fuso de sessão.
- Devo armazenar timestamps em UTC ou com um fuso?
- Armazene o instante em UTC (TIMESTAMPTZ no PostgreSQL, ou DATETIME com valores UTC no MySQL) e converta para hora local ao exibir. Mantenha uma coluna de fuso IANA separada apenas quando precisar reconstruir a intenção de relógio local do usuário, como reuniões recorrentes.