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
- ¿Debo almacenar UTC u hora local en una base de datos?
- Almacena UTC para los timestamps de eventos y convierte a hora local al mostrar. Almacena un identificador de zona horaria por separado cuando importe la intención de reloj local del usuario, como reuniones recurrentes u horarios comerciales.
- ¿Es BIGINT mejor que TIMESTAMP?
- No en general. BIGINT es útil para pipelines numéricos de epoch, pero los tipos datetime nativos son más fáciles para la aritmética de fechas en SQL, la depuración legible y la salida con zona horaria.
- ¿MySQL debe usar TIMESTAMP o DATETIME?
- Para tablas de aplicación nuevas, DATETIME con valores UTC suele ser más seguro porque evita el límite de rango de 2038 y no depende silenciosamente de la conversión de zona horaria de sesión.
- ¿Debo almacenar los timestamps en UTC o con una zona horaria?
- Almacena el instante en UTC (TIMESTAMPTZ en PostgreSQL, o DATETIME con valores UTC en MySQL) y convierte a hora local al mostrar. Mantén una columna de zona horaria IANA separada solo cuando debas reconstruir la intención de reloj local del usuario, como reuniones recurrentes.