Tutorial

Migrar SQL Server 2016 a PostgreSQL en un VPS

Bases de datos12 min de lectura8 pasos

El 14 de julio de 2026, Microsoft dio por concluido el soporte extendido de SQL Server 2016. No se publicarán más parches de seguridad para esta versión. Los equipos que aún trabajan con esta base de datos tienen dos opciones reales: pagar una actualización a SQL Server 2022 o migrar a una alternativa de código abierto. PostgreSQL 16 es una base de datos relacional madura, de alto rendimiento y sin coste de licencia. La migración no es trivial — el dialecto T-SQL de Microsoft no es SQL estándar y los tipos de datos difieren en varios puntos importantes. Esta guía detalla las incompatibilidades clave, la correspondencia de tipos y un plan de corte estructurado para evitar pérdida de datos y regresiones en la aplicación.

Contenido· SQL Server 2016: fin del soporte extendido el 14 de julio de 20261/11
  1. 01SQL Server 2016: fin del soporte extendido el 14 de julio de 2026
  2. 02Por qué elegir PostgreSQL en lugar de actualizar SQL Server o migrar a Azure SQL
  3. 03Auditoría preliminar: qué migra bien y qué requiere trabajo
  4. 04Correspondencia de tipos SQL Server → PostgreSQL
  5. 05Incompatibilidades T-SQL comunes y sus equivalentes en PostgreSQL
  6. 06Plan de migración en 8 pasos
  7. 07Configurar PostgreSQL 16 en un VPS para producción
  8. 08El riesgo de los procedimientos almacenados y desencadenadores T-SQL
  9. 09Prueba de carga antes del corte: pgbench y comparación de planes de consulta
  10. 10Resolución de problemas — errores frecuentes en una migración SQL Server → PostgreSQL
  11. 11Verificar la migración y restablecer el acceso de usuarios

SQL Server 2016: fin del soporte extendido el 14 de julio de 2026

La política de ciclo de vida de Microsoft prevé diez años de soporte para cada versión de SQL Server: cinco años de soporte estándar y luego cinco años de soporte extendido. Para SQL Server 2016, la fecha de fin del soporte extendido es el 14 de julio de 2026. Pasada esta fecha, Microsoft ya no publica actualizaciones de seguridad, correcciones críticas ni actualizaciones técnicas para esta versión. Una instancia de SQL Server 2016 expuesta en una red después de esta fecha acumula vulnerabilidades sin parchear. Los requisitos regulatorios — PCI-DSS, RGPD, ISO 27001 — exigen en general que los componentes de infraestructura reciban los parches de seguridad disponibles. Permanecer en SQL Server 2016 después de julio de 2026 constituye, por tanto, una desviación documentable en una auditoría.

Por qué elegir PostgreSQL en lugar de actualizar SQL Server o migrar a Azure SQL

  • Coste de licencia cero: PostgreSQL está bajo la licencia PostgreSQL (permisiva, similar a BSD), sin CAL y sin edición Enterprise separada que adquirir.
  • Control total de la infraestructura: en un VPS dedicado, se gestionan versiones, extensiones, parámetros de memoria y copias de seguridad sin depender de un proveedor cloud.
  • SQL Server 2022 resuelve la deuda de versión pero mantiene los costes de licencia y vincula la organización al ecosistema Microsoft.
  • Azure SQL Managed Instance simplifica la migración pero introduce un coste mensual recurrente, dependencia de Azure y restricciones de red adicionales.
  • PostgreSQL soporta las mismas funcionalidades avanzadas: funciones de ventana analíticas, CTE recursivas, JSON nativo, particionado de tablas, replicación lógica.
  • El ecosistema de herramientas es amplio: pgAdmin, DBeaver, Metabase, PostgREST, y la mayoría de ORM (Entity Framework, Hibernate, SQLAlchemy, ActiveRecord) soportan PostgreSQL.
  • Las principales distribuciones Linux empaquetan PostgreSQL con actualizaciones de seguridad regulares incluidas en el contrato de mantenimiento de la distribución.

Auditoría preliminar: qué migra bien y qué requiere trabajo

Antes de escribir un solo comando de migración, es indispensable una auditoría del esquema. Los elementos que migran sin fricción son las tablas simples con tipos estándar, las restricciones PRIMARY KEY, FOREIGN KEY, UNIQUE y NOT NULL, los índices B-tree clásicos, las vistas sin sintaxis T-SQL propietaria y las secuencias. Los elementos que requieren trabajo explícito son los procedimientos almacenados y funciones T-SQL (sintaxis incompatible, hay que reescribirlos en PL/pgSQL o moverlos a la capa de aplicación), los desencadenadores que usan las tablas virtuales INSERTED y DELETED (equivalente en PostgreSQL: NEW y OLD), las columnas ROWVERSION/TIMESTAMP usadas para la detección de concurrencia optimista, las columnas calculadas, las sugerencias de bloqueo (WITH (NOLOCK), WITH (UPDLOCK)) y las tablas temporales globales (##temp). Un inventario de SQL Server puede obtenerse consultando INFORMATION_SCHEMA y las vistas del sistema sys.objects, sys.procedures, sys.triggers.

Correspondencia de tipos SQL Server → PostgreSQL

Desplace la tabla

Tipo SQL ServerTipo PostgreSQLObservación
DATETIME2TIMESTAMPTZAlmacenar marcas de tiempo con zona horaria; DATETIME2 no lleva zona horaria, definir la convención UTC explícitamente
UNIQUEIDENTIFIERUUIDPostgreSQL soporta el tipo UUID de forma nativa; usar la extensión `uuid-ossp` o `gen_random_uuid()` para la generación
NVARCHAR(MAX) / NTEXTTEXTPostgreSQL almacena todas las cadenas en UTF-8; sin distinción NVARCHAR/VARCHAR ni límite MAX separado
BITBOOLEANLos valores `0`/`1` pasan a ser `false`/`true`; atención a columnas BIT usadas como enteros en cálculos
MONEY / SMALLMONEYNUMERIC(19,4)MONEY en SQL Server es un entero escalado, NUMERIC es exacto; comportamiento idéntico para cálculos financieros
INT IDENTITY(1,1)SERIAL o GENERATED ALWAYS AS IDENTITYSERIAL es un atajo a una secuencia; GENERATED ALWAYS AS IDENTITY es el estándar SQL:2003, preferible para tablas nuevas
ROWVERSION / TIMESTAMPbytea + trigger o xminSin equivalente directo; la columna de sistema `xmin` gestiona la detección de concurrencia optimista en la mayoría de los casos

Incompatibilidades T-SQL comunes y sus equivalentes en PostgreSQL

Varias construcciones T-SQL habituales no tienen equivalente directo en SQL estándar y requieren reescritura.

TOP N sin ORDER BY: SQL Server acepta SELECT TOP 10 * FROM t sin cláusula de orden. PostgreSQL requiere LIMIT 10; si un orden definido es importante, añadir ORDER BY explícitamente.

ISNULL(expr, valor): la función T-SQL ISNULL tiene como equivalente estándar COALESCE(expr, valor). El comportamiento es idéntico para los casos habituales.

GETDATE() y GETUTCDATE(): estas funciones pasan a ser NOW() (marca de tiempo con zona horaria de la sesión) y NOW() AT TIME ZONE 'UTC' o CURRENT_TIMESTAMP respectivamente.

DATEDIFF y DATEADD: PostgreSQL usa aritmética de intervalos: date + INTERVAL '7 days', y date2 - date1 devuelve un INTERVAL.

Sugerencia WITH (NOLOCK): esta sugerencia solicita una lectura sin bloqueo compartido. PostgreSQL no soporta esta sugerencia; el nivel de aislamiento READ COMMITTED (por defecto) cubre la mayoría de casos de uso. Para informes analíticos que toleran datos ligeramente obsoletos, una réplica de lectura es la solución estructural.

PRINT en procedimientos: usar RAISE NOTICE 'mensaje' en PL/pgSQL.

GO como separador de lotes: este separador no existe en PostgreSQL. Los scripts SQL deben reestructurarse en transacciones o bloques DO $$...$$ separados.

Plan de migración en 8 pasos

  1. Inventario y auditoría del esquema: exportar la lista de todas las tablas, columnas con tipos, índices, restricciones, procedimientos almacenados, funciones, desencadenadores y vistas. Identificar y priorizar los objetos que requieren reescritura manual.

  2. Configurar el VPS PostgreSQL de destino: instalar PostgreSQL 16, configurar postgresql.conf (memoria compartida, conexiones, WAL), crear el usuario de aplicación con el mínimo de privilegios necesarios, habilitar TLS para conexiones remotas.

  3. Conversión del esquema y datos con pgloader o manualmente: pgloader mssql://user:pass@host/db postgresql://user:pass@vps/db migra el esquema y los datos con un solo comando. Para esquemas complejos, exportar el DDL de SQL Server y adaptarlo manualmente aplicando la tabla de correspondencia de tipos.

  4. Validar el esquema migrado: verificar el número de tablas, el conteo de filas por tabla, la integridad referencial (FOREIGN KEY) y los índices. Comparar los tipos reales con los esperados mediante information_schema.columns.

  5. Reescribir los procedimientos almacenados y desencadenadores: cada objeto T-SQL debe reescribirse en PL/pgSQL o moverse a la capa de aplicación. Probar cada procedimiento de forma independiente con conjuntos de datos representativos.

  6. Adaptar el código de la aplicación: buscar en el código fuente todas las cadenas SQL literales y las consultas parametrizadas que usen construcciones T-SQL propietarias. Actualizar el driver de conexión (pasar de mssql a pg, psycopg2, npgsql o el driver apropiado para la pila tecnológica).

  7. Pruebas de carga y no regresión: ejecutar la suite completa de pruebas de regresión contra la base de datos PostgreSQL de staging. Usar pgbench para validar el rendimiento bajo carga. Comparar los planes de ejecución de las consultas críticas (EXPLAIN ANALYZE) entre SQL Server y PostgreSQL.

  8. Corte en ventana de mantenimiento: planificar una ventana (noche o fin de semana), poner la aplicación en modo mantenimiento, ejecutar una migración diferencial final (datos insertados desde la migración inicial), apuntar el DSN de la aplicación a PostgreSQL, validar las funciones críticas y levantar el mantenimiento. Mantener SQL Server en modo solo lectura durante 48 horas para permitir una reversión rápida si fuera necesario.

Configurar PostgreSQL 16 en un VPS para producción

Una instalación PostgreSQL por defecto no está ajustada para producción. Los parámetros siguientes en postgresql.conf son un punto de partida para un VPS con 4 GB de RAM.

shared_buffers: establecer al 25% de la RAM disponible, 1GB para 4 GB. Este parámetro determina la memoria asignada a la caché interna de PostgreSQL.

effective_cache_size: establecer al 75% de la RAM total, 3GB. Este parámetro informa al planificador de consultas sobre la memoria disponible para la caché del sistema operativo.

work_mem: de 16MB a 64MB según la complejidad de las consultas de ordenación. Este parámetro se aplica por operación de ordenación y por conexión; un valor demasiado alto en una instancia con muchas conexiones simultáneas agota la memoria.

maintenance_work_mem: 256MB para las operaciones de mantenimiento (VACUUM, CREATE INDEX).

max_connections: 100 es suficiente para la mayoría de aplicaciones que usan un pool de conexiones. Si la aplicación abre una conexión por petición sin pool, usar PgBouncer como intermediario.

Para pg_hba.conf, restringir las conexiones remotas a la dirección IP del servidor de aplicación y exigir TLS: hostssl all appuser 10.0.0.5/32 scram-sha-256.

Habilitar TLS generando un certificado (Let's Encrypt o certificado interno) y configurando ssl = on, ssl_cert_file y ssl_key_file en postgresql.conf.

El riesgo de los procedimientos almacenados y desencadenadores T-SQL

Los procedimientos almacenados T-SQL son el principal punto de fricción en una migración de SQL Server a PostgreSQL. T-SQL es un dialecto propietario con estructuras de control, gestión de cursores y manejo de errores (bloques TRY...CATCH) que no tienen equivalente directo en PL/pgSQL.

Los cursores FAST_FORWARD de SQL Server pasan a ser bucles FOR en PL/pgSQL, pero el rendimiento es similar y en cualquier caso el uso de cursores debe cuestionarse: una consulta basada en conjuntos reemplaza casi siempre a un cursor de forma más eficiente.

El bloque TRY...CATCH pasa a ser BEGIN...EXCEPTION WHEN OTHERS THEN en PL/pgSQL.

Las tablas temporales locales (#temp) corresponden a las tablas temporales de sesión en PostgreSQL, creadas con CREATE TEMPORARY TABLE. Las tablas temporales globales (##temp) no tienen equivalente directo — deben reemplazarse por tablas permanentes con gestión explícita del ciclo de vida.

Los desencadenadores que referencian INSERTED y DELETED (tablas virtuales de SQL Server) deben reescribirse para usar NEW y OLD en PostgreSQL. La estructura de activación también difiere: PostgreSQL distingue entre desencadenadores BEFORE y AFTER, y entre FOR EACH ROW y FOR EACH STATEMENT.

Un enfoque pragmático consiste en mover la lógica de los procedimientos almacenados a la capa de aplicación durante la migración, lo que simplifica las pruebas y reduce el acoplamiento entre la aplicación y la base de datos.

Prueba de carga antes del corte: pgbench y comparación de planes de consulta

Antes del corte a producción, validar el rendimiento con pgbench. Inicializar una base de datos de prueba representativa: pgbench -i -s 50 nombre_bd crea un esquema de referencia con 5 millones de filas. Ejecutar una simulación de carga: pgbench -c 20 -j 4 -T 60 nombre_bd simula 20 conexiones paralelas durante 60 segundos e informa de las transacciones por segundo. Para las consultas críticas de la aplicación, comparar los planes de ejecución entre SQL Server (SET STATISTICS IO ON; SELECT ...) y PostgreSQL (EXPLAIN (ANALYZE, BUFFERS) SELECT ...). Vigilar los escaneos completos de tabla inesperados (índice faltante o estadísticas no calculadas — ejecutar ANALYZE tras la migración) y las uniones NESTED LOOP sobre grandes volúmenes que se beneficiarían de un HASH JOIN. Actualizar las estadísticas tras la migración inicial con ANALYZE nombre_tabla o VACUUM ANALYZE para toda la base de datos.

Resolución de problemas — errores frecuentes en una migración SQL Server → PostgreSQL

Varias categorías de errores aparecen sistemáticamente en las primeras migraciones.

ERROR: column is of type uuid but expression is of type character varying: una columna UNIQUEIDENTIFIER migrada a UUID recibe una cadena con formato UUID sin conversión explícita. Adaptar la consulta de la aplicación para enviar un tipo UUID real, añadir una conversión implícita en PostgreSQL (CREATE CAST), o migrar la columna a TEXT si no se explota la semántica UUID.

ERROR: operator does not exist: boolean = integer: una columna BIT migrada a BOOLEAN se compara con 0 o 1. Reemplazar col = 1 por col = true o simplemente col en las cláusulas WHERE.

ERROR: function isnull(character varying, text) does not exist: reemplazar todas las ocurrencias de ISNULL por COALESCE.

ERROR: syntax error at or near "TOP": la sintaxis SELECT TOP N no existe en PostgreSQL. Reemplazar por SELECT ... LIMIT N.

Desbordamientos silenciosos en columnas MONEY → NUMERIC: verificar que los valores extremos de la tabla de origen caben en la precisión objetivo NUMERIC(19,4). SQL Server MONEY tiene un rango de ±922.337.203.685.477,5807; NUMERIC(19,4) cubre ±999.999.999.999.999,9999, suficiente en casi todos los casos.

Pérdida de datos en columnas DATETIME sin zona horaria: DATETIME y DATETIME2 de SQL Server no almacenan la zona horaria. Si la base de datos era alimentada por aplicaciones en múltiples zonas horarias, las marcas de tiempo migradas a TIMESTAMPTZ sin conversión previa se interpretarán como UTC. Definir una convención (UTC u hora local) y aplicar un AT TIME ZONE durante la migración si es necesario.

Verificar la migración y restablecer el acceso de usuarios

Tras el corte, una lista de verificación estructurada reduce el riesgo de devolver a los usuarios a una base de datos incompleta.

Verificar el conteo de filas para todas las tablas críticas: consultar information_schema.tables con un COUNT(*) por tabla, comparado con las cifras registradas en SQL Server antes de la migración.

Verificar la integridad referencial: SELECT * FROM pg_constraint WHERE contype = 'f' lista todas las restricciones de clave foránea; intentar una inserción que violaría una restricción para confirmar que está activa.

Verificar los índices: SELECT indexname, indexdef FROM pg_indexes WHERE schemaname = 'public' lista todos los índices creados. Comparar con la lista de índices de SQL Server.

Probar las funciones críticas de la aplicación: flujo de compra, autenticación, informes, exportaciones — como mínimo los flujos que tocan las tablas más voluminosas y las consultas más complejas.

Monitorizar los logs de PostgreSQL durante las primeras 24 horas: tail -f /var/log/postgresql/postgresql-*.log. Los errores de tipo (character varying vs uuid), los tiempos de espera de conexión o los planes de consulta subóptimos suelen manifestarse en las primeras horas de tráfico real.

Mantener SQL Server en modo solo lectura durante 48 horas con acceso restringido al equipo técnico. La reversión consiste en apuntar el DSN de la aplicación de vuelta a SQL Server y reproducir los datos escritos en PostgreSQL durante la ventana de corte.

VPS listos para PostgreSQL 16

VPS de ServOrbit configurados para PostgreSQL 16: SSD NVMe, snapshot diario, acceso root completo. Despliega tu base de datos migrada sin restricciones de licencia.

¿Necesita ayuda?

Consulte nuestro centro de ayuda y nuestra FAQ, o contacte con nuestro equipo: llamada, WhatsApp o correo electrónico. Soporte en francés, inglés y árabe.

Escribir por WhatsAppse abre en una pestaña nueva