← Todos los artículos

Almacenar timestamps en bases de datos: DATETIME vs INT vs BIGINT

Elegir el tipo de columna equivocado para los timestamps causa deriva de zona horaria, el desbordamiento del año 2038, consultas de rango rotas y salidas de API confusas. Compara los tipos datetime nativos, los valores epoch BIGINT y las cadenas en MySQL y PostgreSQL.

Tres formas de almacenar un timestamp

La mayoría de las bases de datos ofrecen al menos tres opciones: un tipo datetime nativo (TIMESTAMP, DATETIME, TIMESTAMPTZ), un entero simple (INT o BIGINT) o una cadena (VARCHAR). Cada uno tiene distintos compromisos en tamaño de almacenamiento, ergonomía de consultas, manejo de zonas horarias y preparación para el futuro. Para la mayoría de las bases de datos de producto, una columna datetime nativa es el mejor valor por defecto porque la base puede comparar, indexar, truncar, agrupar y formatear el valor como tiempo en lugar de como un número anónimo.

  • Tipo datetime nativo — el mejor para aritmética de fechas, conversión de zona horaria y legibilidad
  • Entero BIGINT — bueno para inserciones de alto rendimiento y consultas numéricas de rango simples
  • Cadena VARCHAR — casi siempre incorrecto: la comparación de cadenas de fechas solo funciona con formato ISO 8601 estricto
  • Entero INT — evítalo para timestamps futuros salvo que hayas verificado a fondo el límite del año 2038

MySQL: TIMESTAMP vs DATETIME vs INT

MySQL tiene dos tipos de fecha-hora que parecen similares pero se comportan de forma muy distinta, y uno de ellos tiene una fecha de caducidad rígida. TIMESTAMP es cómodo cuando quieres conversión automática entre UTC y la zona horaria de sesión, pero su rango histórico de 32 bits lo hace arriesgado para datos de producto orientados al futuro. DATETIME almacena la fecha y hora literales que proporcionas, lo que suele ser más claro cuando la aplicación estandariza en UTC antes de escribir.

  • TIMESTAMP: almacenado internamente como segundos Unix de 32 bits — limitado de 1970-01-01 a 2038-01-19
  • TIMESTAMP: auto-convierte entre UTC y la zona horaria de sesión al insertar/leer
  • DATETIME: almacena la fecha-hora literal, sin zona horaria. Rango 1000-01-01 a 9999-12-31. No afectado por el Y2038.
  • DATETIME: no convierte zonas horarias — controlas UTC a nivel de aplicación
  • Recomendación: usa DATETIME con valores UTC explícitos para tablas nuevas y así evitar el límite de 2038

PostgreSQL: TIMESTAMPTZ es la opción correcta

El TIMESTAMP WITH TIME ZONE (TIMESTAMPTZ) de PostgreSQL almacena los timestamps como microsegundos UTC internamente y los convierte a la zona horaria de sesión en la salida. Es la opción más segura y correcta para la mayoría de los casos porque representa un instante real en el tiempo. El nombre puede ser engañoso: TIMESTAMPTZ no almacena la etiqueta de zona horaria original como America/New_York. Almacena el instante y luego lo muestra según la zona horaria de sesión actual.

  • TIMESTAMPTZ: almacena UTC, convierte a la zona horaria de sesión en la salida — portable y seguro frente al horario de verano
  • TIMESTAMP (sin zona horaria): almacena el valor literal sin conversión — úsalo solo para datos sin zona horaria
  • EXTRACT(EPOCH FROM col): devuelve segundos Unix como float desde cualquier columna TIMESTAMP
  • TO_TIMESTAMP(epoch): convierte segundos Unix de vuelta a un TIMESTAMPTZ

Indexación y rendimiento de consultas

Para las tablas de aplicación normales, la diferencia de rendimiento entre columnas datetime nativas y columnas epoch BIGINT rara vez es el factor decisivo. La forma de la consulta, el diseño del índice, el particionado y el número de filas importan más. Elige primero el tipo que mantiene el significado correcto, luego indéxalo para las consultas de rango que tu aplicación ejecuta realmente.

  • Los tres tipos admiten índices B-tree y consultas de rango eficientes
  • Los enteros BIGINT son marginalmente más rápidos para escaneos de igualdad y rango en tablas de muy alto volumen
  • Los tipos datetime nativos permiten consultas indexadas por parte de la fecha: WHERE created_at::date = '2024-01-01'
  • Los timestamps VARCHAR son los peores para el rendimiento — la comparación de cadenas no entiende fechas

Cuándo tiene sentido el almacenamiento epoch en BIGINT

BIGINT es razonable cuando los datos son tipo evento, con muchas inserciones, y ya los produce otro sistema como tiempo Unix. Los pipelines de analítica, los flujos de telemetría, las colas y los protocolos binarios compactos suelen usar milisegundos epoch porque los valores numéricos son rápidos de comparar y neutrales al lenguaje. El compromiso es la legibilidad: las personas necesitan un conversor y la aritmética de fechas en SQL se vuelve más verbosa.

  • Usa BIGINT para milisegundos Unix si los clientes JavaScript producen los eventos directamente
  • Usa BIGINT para segundos Unix si el sistema de origen es estilo Unix y la precisión de segundos es suficiente
  • Documenta la unidad en el nombre de la columna: created_at_ms es más claro que created_at_epoch
  • Añade una columna datetime generada si los analistas necesitan consultas SQL legibles
  • Evita INT para timestamps modernos orientados al futuro por los límites de rango de 32 bits

Patrones de esquema recomendados

Para la mayoría de las aplicaciones web, almacena un instante en UTC y almacena la zona horaria preferida del usuario por separado solo cuando necesites reconstruir la intención de reloj local. Una reunión programada para las 9:00 AM America/New_York es distinta de un log de eventos creado en un instante UTC preciso; modela esos casos de forma diferente.

  • Logs de eventos: created_at TIMESTAMPTZ en PostgreSQL, o created_at DATETIME en UTC para MySQL
  • Ingesta de eventos de JavaScript: created_at_ms BIGINT más documentación clara de la API
  • Horarios locales recurrentes: local_date, local_time y timezone_id, y luego calcula el siguiente instante
  • Timestamps de expiración: expires_at como datetime nativo o expires_at_seconds con segundos Unix explícitos
  • Tablas de auditoría: mantén created_at y updated_at como columnas datetime nativas para una depuración legible

Preguntas frecuentes sobre timestamps en bases de datos

MongoDB stores dates natively as the BSON Date type — a signed 64-bit integer of milliseconds since the Unix epoch. The driver maps it to a Date object in JavaScript, datetime in Python, ISODate in the mongo shell.

  • BSON Date: 8 bytes, signed 64-bit, milliseconds since 1970 UTC
  • Indexes natively — range queries on Date are fast and idiomatic
  • Use ISODate("2024-01-01T00:00:00Z") in the shell to create comparable values
  • For multi-precision needs, use Decimal128 or a sub-document with seconds + nanos; Date itself is millisecond precision
  • TTL indexes operate on Date fields specifically — integer epoch fields will not work with expireAfterSeconds

DynamoDB: epoch attributes and TTL

DynamoDB has no dedicated date type. Two patterns dominate: ISO 8601 strings for human readability, or epoch numbers for compactness and TTL support.

  • TTL requires the attribute to be a Number representing Unix seconds (not milliseconds)
  • Strings store ISO 8601 with full timezone info but cost more bytes and sort lexicographically (so include the Z suffix consistently)
  • Range queries on a sort-key epoch number are extremely cheap and align with DynamoDB’s query model
  • Avoid mixing seconds and milliseconds across tables — DynamoDB will not protect you from the unit drift
  • DynamoDB Streams emit an ApproximateCreationDateTime in ISO 8601 regardless of how you stored the original value

FAQ

Should I store UTC or local time in a database?
Store UTC for event timestamps and convert to local time when displaying. Store a timezone identifier separately when the user's local wall-clock intent matters, such as recurring meetings or business hours.
Is BIGINT better than TIMESTAMP?
Not generally. BIGINT is useful for numeric epoch pipelines, but native datetime types are easier for SQL date arithmetic, readable debugging, and timezone-aware output.
Should MySQL use TIMESTAMP or DATETIME?
For new application tables, DATETIME with UTC values is often safer because it avoids the 2038 range limit and does not silently depend on session timezone conversion.
Should I store timestamps as UTC or with a timezone?
Store the instant in UTC (TIMESTAMPTZ in PostgreSQL, or DATETIME with UTC values in MySQL) and convert to local time on display. Keep a separate IANA timezone column only when you must reconstruct a user's local wall-clock intent, such as recurring meetings.