Tutoriales

Migración de marcas de tiempo entre bases de datos: guía completa

Introducción

La migración de datos de marcas de tiempo entre diferentes sistemas de bases de datos es un desafío común en los proyectos de desarrollo de aplicaciones y almacenamiento de datos. Cada base de datos tiene sus propios tipos de datos de marca de tiempo, funciones y mejores prácticas. Esta guía completa le enseñará cómo migrar marcas de tiempo entre MySQL, PostgreSQL, SQLite, Oracle y SQL Server sin pérdida de datos.

Comprender las estrategias de almacenamiento de marcas de tiempo

Opción 1: Marca de tiempo de Unix (entero)

El enfoque más portátil es almacenar marcas de tiempo como segundos de época de Unix o milisegundos como números enteros.

-- MySQL
CREATE TABLE events (
    id INT PRIMARY KEY,
    created_at BIGINT NOT NULL  -- Unix timestamp in seconds
);

-- PostgreSQL  
CREATE TABLE events (
    id INT PRIMARY KEY,
    created_at BIGINT NOT NULL
);

-- SQLite
CREATE TABLE events (
    id INTEGER PRIMARY KEY,
    created_at INTEGER NOT NULL
);

Ventajas:

  • Formato universal en todas las bases de datos.
  • No hay problemas de zona horaria
  • Fácil de convertir a cualquier formato de visualización
  • Eficiente para indexación y comparación.

Desventajas:

  • No legible por humanos en formato crudo
  • Requiere conversión para depurar

Opción 2: Cadena ISO 8601

Almacene marcas de tiempo como cadenas con formato ISO 8601.

-- MySQL
CREATE TABLE events (
    id INT PRIMARY KEY,
    created_at VARCHAR(26) NOT NULL  -- '2025-01-15T10:30:00.000Z'
);

-- PostgreSQL
CREATE TABLE events (
    id INT PRIMARY KEY,
    created_at TIMESTAMPTZ NOT NULL
);

Ventajas:

  • Legible por humanos
  • Incluye información de zona horaria.
  • Formato estándar

Desventajas:

  • Mayor tamaño de almacenamiento
  • Comparaciones más lentas
  • La eficiencia del índice varía

Opción 3: tipos de marcas de tiempo nativas

Utilizando los tipos de marcas de tiempo nativas de cada base de datos.

-- MySQL
CREATE TABLE events (
    created_at DATETIME
);

-- PostgreSQL
CREATE TABLE events (
    created_at TIMESTAMP WITH TIME ZONE
);

-- SQL Server
CREATE TABLE events (
    created_at DATATIME2
);

Ventajas:

  • Mejor rendimiento para operaciones de fechas.
  • Validación incorporada
  • Ecosistema rico en funciones

Desventajas:

  • Complejidad de la migración
  • El manejo de la zona horaria varía

Escenarios de migración

Escenario 1: MySQL a PostgreSQL

Conversión de DATETIME de MySQL a TIMESTAMPTZ de PostgreSQL.

-- Source (MySQL)
SELECT id, created_at FROM events;

-- Migration Script (PostgreSQL)
-- Option A: Using TO_TIMESTAMP()
INSERT INTO events (id, created_at)
SELECT id, TO_TIMESTAMP(created_at, 'YYYY-MM-DD HH24:MI:SS')
FROM events_source;

-- Option B: Using Unix timestamp (recommended)
-- First, convert MySQL DATETIME to Unix timestamp
SELECT UNIX_TIMESTAMP(created_at) FROM events;

-- Then insert into PostgreSQL
INSERT INTO events (id, created_at)
SELECT id, TO_TIMESTAMP(unix_ts)
FROM events_source;

Escenario 2: Oracle a SQL Server

Migrando de DATE de Oracle a DATETIME2 de SQL Server.

-- Source (Oracle)
SELECT hire_date FROM employees;

-- Migration Script (SQL Server)
-- Option A: Direct conversion
INSERT INTO employees (hire_date)
SELECT CAST(hire_date AS DATETIME2)
FROM employees_source;

-- Option B: Using Unix epoch (preferred)
-- Oracle: Get epoch seconds
SELECT (hire_date - DATE '1970-01-01') * 86400 AS epoch_seconds
FROM employees;

-- SQL Server: Convert epoch to datetime
INSERT INTO employees (hire_date)
SELECT DATEADD(SECOND, epoch_seconds, '1970-01-01')
FROM employees_source;

Escenario 3: Conversión entre zonas horarias

Al migrar entre bases de datos con diferentes requisitos de zona horaria.

-- PostgreSQL: Convert UTC to specific timezone
INSERT INTO events (id, created_at)
SELECT id, created_at AT TIME ZONE 'America/New_York'
FROM events_source;

-- MySQL: Convert using CONVERT_TZ
INSERT INTO events (id, created_at)
SELECT id, CONVERT_TZ(created_at, '+00:00', '-05:00')
FROM events_source;

-- SQL Server: Convert using AT TIME ZONE
INSERT INTO events (id, created_at)
SELECT created_at AT TIME ZONE 'UTC' AT TIME ZONE 'Eastern Standard Time'
FROM events_source;

Mejores prácticas

1. Utilice siempre UTC para el almacenamiento

-- Store all timestamps in UTC
ALTER TABLE events 
ADD COLUMN created_at_utc TIMESTAMP WITH TIME ZONE;

UPDATE events 
SET created_at_utc = created_at AT TIME ZONE 'UTC';

-- Drop the old column after verification
ALTER TABLE events DROP COLUMN created_at;

2. Validar antes de la migración

-- Check for invalid timestamps
SELECT COUNT(*) as invalid_count
FROM events_source
WHERE created_at IS NULL 
   OR created_at < '1970-01-01'::timestamp
   OR created_at > '2038-01-19'::timestamp;  -- 32-bit overflow

3. Utilice tablas de preparación

-- Create staging table
CREATE TABLE events_staging (LIKE events);

-- Load data
INSERT INTO events_staging 
SELECT * FROM events_source;

-- Validate and clean
UPDATE events_staging 
SET created_at = NOW() 
WHERE created_at IS NULL;

-- Verify row counts
SELECT 
    (SELECT COUNT(*) FROM events_source) as source_count,
    (SELECT COUNT(*) FROM events_staging) as staging_count,
    (SELECT COUNT(*) FROM events) as target_count;

4. Manejar valores nulos

-- PostgreSQL
INSERT INTO events (id, created_at)
SELECT id, COALESCE(created_at, NOW())
FROM events_source;

-- MySQL
INSERT INTO events (id, created_at)
SELECT id, IFNULL(created_at, NOW())
FROM events_source;

-- SQL Server
INSERT INTO events (id, created_at)
SELECT id, ISNULL(created_at, GETDATE())
FROM events_source;

Ejemplos de código por idioma

JavaScript/Node.js

// Migrate timestamps using Node.js
const mysql = require('mysql2/promise');
const { Pool } = require('pg');

async function migrateTimestamps() {
    const mysqlPool = await mysql.createPool({
        host: 'mysql-source',
        database: 'source_db'
    });

    const pgPool = new Pool({
        host: 'postgres-target',
        database: 'target_db'
    });

    // Get all records
    const [rows] = await mysqlPool.query('SELECT * FROM events');

    // Transform and insert
    for (const row of rows) {
        const unixTimestamp = Math.floor(row.created_at.getTime() / 1000);
        await pgPool.query(
            'INSERT INTO events (id, created_at) VALUES ($1, to_timestamp($2))',
            [row.id, unixTimestamp]
        );
    }
}

Pitón

# Migrate timestamps using Python
import pymysql
import psycopg2
from datetime import datetime

def migrate_timestamps():
    # Source connection
    mysql_conn = pymysql.connect(
        host='mysql-source',
        database='source_db'
    )

    # Target connection
    pg_conn = psycopg2.connect(
        host='postgres-target',
        database='target_db'
    )

    with mysql_conn.cursor() as cursor:
        cursor.execute('SELECT id, created_at FROM events')
        rows = cursor.fetchall()

        with pg_conn.cursor() as pg_cursor:
            for row in rows:
                # Convert MySQL datetime to Unix timestamp
                unix_ts = int(row[1].timestamp())
                pg_cursor.execute(
                    'INSERT INTO events (id, created_at) VALUES (%s, to_timestamp(%s))',
                    (row[0], unix_ts)
                )

        pg_conn.commit()

Lista de verificación de validación

Antes de pasar a producción:

  • [] Compatibilidad de tipos de datos: Verifique que la columna de destino pueda contener todos los valores de origen
  • [] Manejo de zona horaria: confirme que todas las marcas de tiempo estén en UTC o documente la política de zona horaria
  • [] Validación de rango: verifique las marcas de tiempo antes de 1970 o después de 2038
  • [] Manejo de NULL: decida cómo manejar las marcas de tiempo NULL/faltantes
  • [] Rendimiento: pruebe la migración en un volumen de datos representativo
  • [] Plan de reversión: tenga un procedimiento de reversión verificado

Errores comunes

1. Ignorar milisegundos

-- Wrong: Losing millisecond precision
INSERT INTO target SELECT created_at FROM source;

-- Correct: Preserve milliseconds
INSERT INTO target 
SELECT created_at AT TIME ZONE 'UTC' FROM source;

2. Configuración incorrecta de la zona horaria

-- Wrong: Assuming local time
INSERT INTO target (created_at)
SELECT created_at FROM source;

-- Correct: Explicit UTC conversion
INSERT INTO target (created_at)
SELECT COALESCE(
    created_at AT TIME ZONE 'UTC',
    NOW()
) FROM source;

3. Desbordamiento de enteros de 32 bits

-- Check for timestamps that will overflow 32-bit
SELECT * FROM events 
WHERE created_at > '2038-01-19 03:14:07'::timestamp;

Conclusión

La migración de marcas de tiempo entre bases de datos requiere una planificación y ejecución cuidadosas. Las conclusiones clave son:

  1. Utilice marcas de tiempo de Unix para máxima portabilidad
  2. Valide siempre los datos antes de la migración
  3. Pruebe minuciosamente con volúmenes de datos similares a los de producción
  4. Documente su estrategia de manejo de zona horaria
  5. Tener un plan de reversión verificado

Seguir estas prácticas garantizará que la migración de su marca de tiempo sea exitosa y mantenible.