pgLoader es una herramienta de código abierto que lee una base de datos MySQL y la carga en PostgreSQL en un solo paso: crea las tablas con tipos equivalentes, copia los datos, reconstruye índices y claves foráneas y ajusta las secuencias. En esta guía migrarás una base de datos MySQL a un servidor PostgreSQL con Ubuntu 24.04, revisarás los puntos que pgLoader no convierte por ti, verificarás los datos y prepararás el cambio de la aplicación con un plan de vuelta atrás.
Requisitos previos
- Un servidor con Ubuntu 24.04 LTS para PostgreSQL, por ejemplo un VPS de CubePath, con un usuario no root con privilegios
sudo. pgLoader se ejecutará en este mismo servidor. - Una base de datos MySQL 8.0 accesible desde ese servidor por el puerto 3306. En los ejemplos se llama
appdby está enyour_mysql_host. - Acceso de administrador a MySQL para crear un usuario de migración.
- Espacio en disco en el servidor PostgreSQL de al menos el doble del tamaño de los datos de MySQL.
Paso 1: Hacer una copia de seguridad de MySQL
pgLoader solo lee de MySQL, pero antes de cualquier migración necesitas una copia de la que partir si algo sale mal. En el servidor MySQL, crea un volcado consistente sin bloquear las tablas InnoDB:
mysqldump --single-transaction --routines --triggers --events \
-u root -p appdb > appdb_$(date +%F).sql
Comprueba que el volcado termina correctamente; la última línea debe ser un comentario Dump completed:
tail -n 1 appdb_*.sql
-- Dump completed on 2026-09-25 10:04:11
Paso 2: Analizar el esquema de MySQL
pgLoader convierte automáticamente tablas, columnas, índices y claves foráneas, pero no traduce procedimientos almacenados, funciones, triggers ni vistas. Localízalos antes de empezar para planificar su reescritura en PL/pgSQL:
mysql -u root -p -e "
SELECT ROUTINE_TYPE, ROUTINE_NAME FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = 'appdb';
SELECT TRIGGER_NAME, EVENT_OBJECT_TABLE FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA = 'appdb';
SELECT TABLE_NAME FROM information_schema.VIEWS WHERE TABLE_SCHEMA = 'appdb';"
Busca también columnas con tipos que se comportan distinto en PostgreSQL:
mysql -u root -p -e "
SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, EXTRA
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'appdb'
AND (DATA_TYPE IN ('enum', 'set', 'year') OR COLUMN_TYPE LIKE '%unsigned%' OR EXTRA LIKE '%on update%')
ORDER BY TABLE_NAME;"
Lo que debes saber de cada caso:
TINYINT(1)se convierte enboolean, yAUTO_INCREMENTenserialobigserial.- Los enteros
UNSIGNEDpasan al tipo entero inmediatamente mayor para que quepan todos los valores. ENUMse convierte en un tipo enumerado de PostgreSQL. Las columnasSETno tienen equivalente directo: revisa cómo quedan tras la migración.- Las fechas
0000-00-00no existen en PostgreSQL y se cargan comoNULL. ON UPDATE CURRENT_TIMESTAMPno existe en PostgreSQL; revisa esas columnas después de migrar y, si la aplicación depende de ello, recrea el comportamiento con un trigger.
Anota además el número de filas de las tablas principales para compararlo al final.
Paso 3: Preparar PostgreSQL
En el servidor de destino, instala PostgreSQL 16 desde los repositorios de Ubuntu:
sudo apt update
sudo apt install -y postgresql
Crea el rol de la aplicación y una base de datos vacía de su propiedad. Sustituye your_strong_password por una contraseña robusta:
sudo -u postgres psql -c "CREATE ROLE appuser WITH LOGIN PASSWORD 'your_strong_password';"
sudo -u postgres createdb --owner=appuser --encoding=UTF8 appdb
Comprueba que puedes conectarte con ese usuario:
psql "host=127.0.0.1 dbname=appdb user=appuser" -c "SELECT current_user;"
current_user
--------------
appuser
(1 row)
Paso 4: Instalar pgLoader
pgLoader está en los repositorios de Ubuntu 24.04:
sudo apt install -y pgloader
Verifica la instalación:
pgloader --version
La salida muestra la versión de pgLoader y la de SBCL con la que está compilado.
Paso 5: Crear un usuario de lectura en MySQL
Crea en MySQL un usuario con permisos de solo lectura sobre la base de datos, limitado a la IP del servidor PostgreSQL (your_server_ip):
CREATE USER 'pgloader'@'your_server_ip' IDENTIFIED WITH mysql_native_password BY 'your_mysql_password';
GRANT SELECT, SHOW VIEW ON appdb.* TO 'pgloader'@'your_server_ip';
Se usa mysql_native_password porque las versiones de pgLoader empaquetadas no admiten el método caching_sha2_password, que es el predeterminado en MySQL 8. Si tu servidor es MySQL 8.4, este plugin viene desactivado y debes habilitarlo con mysql_native_password=ON en la sección [mysqld] de su configuración.
Desde el servidor PostgreSQL, comprueba que llegas a MySQL. Si no tienes el cliente, instálalo con sudo apt install -y mysql-client:
mysql -h your_mysql_host -u pgloader -p -e "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'appdb';"
Paso 6: Escribir el archivo de carga
Un archivo de comandos permite repetir la migración con las mismas opciones tantas veces como necesites. Créalo en tu directorio personal:
nano ~/appdb.load
LOAD DATABASE
FROM mysql://pgloader:your_mysql_password@your_mysql_host/appdb
INTO postgresql://appuser:[email protected]/appdb
WITH include drop, create tables, create indexes, reset sequences,
foreign keys, workers = 4, concurrency = 1
SET PostgreSQL PARAMETERS
maintenance_work_mem to '512MB',
work_mem to '64MB'
ALTER SCHEMA 'appdb' RENAME TO 'public';
Qué hace cada parte:
FROMeINTO: cadenas de conexión de origen y destino. Si una contraseña contiene caracteres como@,:o/, codifícalos en formato URL (por ejemplo@como%40).include drop, create tables: borra y recrea las tablas en cada ejecución, así puedes repetir la migración de prueba desde cero.reset sequences: ajusta las secuencias al valor máximo de cada columna autoincremental.ALTER SCHEMA ... RENAME TO 'public': pgLoader crea por defecto un esquema con el nombre de la base de datos MySQL. Con esta línea las tablas quedan enpublic, que es donde la mayoría de aplicaciones las buscan.
Para excluir tablas que no quieras migrar, como cachés o sesiones, añade antes de ALTER SCHEMA una línea EXCLUDING TABLE NAMES MATCHING 'sessions', ~/^cache_/.
El archivo contiene contraseñas, así que restringe sus permisos:
chmod 600 ~/appdb.load
Paso 7: Ejecutar la migración
Lanza pgLoader con el archivo de carga y guarda la salida en un registro:
pgloader ~/appdb.load 2>&1 | tee ~/pgloader.log
Al terminar, pgLoader muestra un resumen por tabla. Revisa la columna errors, que debe estar a 0 en todas las filas:
table name errors rows bytes total time
----------------------- --------- --------- --------- --------------
public.clientes 0 210944 38.2 MB 4.812s
public.pedidos 0 4812330 702.4 MB 58.301s
public.lineas 0 1920410 210.9 MB 21.442s
----------------------- --------- --------- --------- --------------
Total import time ✓ 6943684 951.5 MB 1m32.120s
Si alguna tabla muestra errores, busca el detalle en ~/pgloader.log, corrige la causa y vuelve a ejecutar: gracias a include drop el resultado siempre parte de cero.
Paso 8: Verificar los datos
Compara el número exacto de filas de las tablas importantes en ambos motores. En MySQL:
mysql -h your_mysql_host -u pgloader -p -e "SELECT COUNT(*) FROM appdb.pedidos;"
En PostgreSQL:
psql "host=127.0.0.1 dbname=appdb user=appuser" -c "SELECT count(*) FROM pedidos;"
Revisa que los tipos se han convertido como esperabas:
psql "host=127.0.0.1 dbname=appdb user=appuser" -c "\d pedidos"
Comprueba también que las secuencias continúan desde el último identificador, insertando una fila de prueba en una tabla con clave autoincremental dentro de una transacción que después deshaces:
psql "host=127.0.0.1 dbname=appdb user=appuser" -c "BEGIN; INSERT INTO clientes (nombre) VALUES ('prueba') RETURNING id; ROLLBACK;"
El id devuelto debe ser mayor que el máximo existente. Ajusta la columna nombre a una de tus tablas.
Paso 9: Adaptar las consultas de la aplicación
Aunque los datos estén bien, la aplicación fallará si usa sintaxis propia de MySQL. Las diferencias más habituales:
| MySQL | PostgreSQL |
|---|---|
IFNULL(a, b) | COALESCE(a, b) |
GROUP_CONCAT(x SEPARATOR ',') | string_agg(x, ',') |
LIMIT 10, 20 | LIMIT 20 OFFSET 10 |
`columna` | "columna" |
INSERT ... ON DUPLICATE KEY UPDATE | INSERT ... ON CONFLICT (id) DO UPDATE SET ... |
NOW() - INTERVAL 1 DAY | now() - interval '1 day' |
| Comparación de texto sin distinguir mayúsculas | Distingue mayúsculas: usa ILIKE o lower() |
Localiza estos patrones en el código con grep, por ejemplo desde la raíz del proyecto:
grep -rnE "IFNULL|GROUP_CONCAT|ON DUPLICATE KEY|LIMIT [0-9]+ *, *[0-9]+" --include="*.php" --include="*.py" --include="*.js" .
Si usas un ORM, cambiar el driver y la cadena de conexión suele bastar para la mayoría de consultas; céntrate en las consultas SQL escritas a mano. Después ejecuta la batería de pruebas de la aplicación contra la base de datos PostgreSQL.
Paso 10: Planificar el cambio y la vuelta atrás
Cuando las pruebas pasen, planifica el corte definitivo:
- Pon la aplicación en modo mantenimiento para que no haya escrituras en MySQL.
- Ejecuta de nuevo
pgloader ~/appdb.loadpara cargar los datos finales. - Repite las comprobaciones del paso 8.
- Cambia la cadena de conexión de la aplicación a PostgreSQL y sal del modo mantenimiento.
MySQL no se modifica en ningún momento, así que la vuelta atrás consiste en restaurar la cadena de conexión anterior. Esta opción deja de ser segura en cuanto la aplicación escribe datos nuevos en PostgreSQL, por lo que conviene decidir de antemano cuánto tiempo mantener MySQL como alternativa.
Solución de problemas
MySQL Error: Authentication plugin 'caching_sha2_password' is not supported. El usuario de MySQL usa el método de autenticación por defecto. Cámbialo con ALTER USER 'pgloader'@'your_server_ip' IDENTIFIED WITH mysql_native_password BY 'your_mysql_password';.
Heap exhausted, game over. pgLoader se queda sin memoria con tablas de filas muy grandes. Reduce el trabajo en paralelo y el número de filas en memoria añadiendo prefetch rows = 10000 a la cláusula WITH y bajando workers a 2.
Las tablas aparecen en un esquema llamado appdb. Falta la línea ALTER SCHEMA 'appdb' RENAME TO 'public'; o el nombre no coincide exactamente con el de la base de datos MySQL.
Caracteres extraños en textos con acentos. Alguna tabla de MySQL usa latin1 con datos que en realidad son UTF-8. Comprueba el juego de caracteres de cada tabla con SHOW CREATE TABLE y corrígelo en MySQL antes de repetir la migración.
Conclusión
Has migrado una base de datos MySQL a PostgreSQL con pgLoader, revisado los tipos y objetos que requieren atención manual, verificado los datos y preparado un corte con vuelta atrás. Como siguientes pasos, reescribe en PL/pgSQL los procedimientos y triggers que localizaste en el paso 2, configura copias de seguridad de PostgreSQL con pg_dump y revisa las consultas más lentas con la extensión pg_stat_statements.
