--- title: "Cómo migrar de MySQL a PostgreSQL con pgLoader" description: "Migra una base de datos MySQL a PostgreSQL en Ubuntu 24.04 con pgLoader: análisis del esquema, archivo de carga, verificación y adaptación de consultas." category: "Operaciones de Base de Datos" language: es source: https://cubepath.com/es/docs/operaciones-de-base-de-datos/guia-de-migracion-de-mysql-a-postgresql updated: 2026-09-25 --- # Cómo migrar de MySQL a PostgreSQL con pgLoader 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 `appdb` y está en `your_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: ```bash 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`: ```bash tail -n 1 appdb_*.sql ``` ```output -- 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: ```bash 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: ```bash 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 en `boolean`, y `AUTO_INCREMENT` en `serial` o `bigserial`. - Los enteros `UNSIGNED` pasan al tipo entero inmediatamente mayor para que quepan todos los valores. - `ENUM` se convierte en un tipo enumerado de PostgreSQL. Las columnas `SET` no tienen equivalente directo: revisa cómo quedan tras la migración. - Las fechas `0000-00-00` no existen en PostgreSQL y se cargan como `NULL`. - `ON UPDATE CURRENT_TIMESTAMP` no 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: ```bash 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: ```bash 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: ```bash psql "host=127.0.0.1 dbname=appdb user=appuser" -c "SELECT current_user;" ``` ```output current_user -------------- appuser (1 row) ``` ## Paso 4: Instalar pgLoader pgLoader está en los repositorios de Ubuntu 24.04: ```bash sudo apt install -y pgloader ``` Verifica la instalación: ```bash 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`): ```sql 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`: ```bash 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: ```bash nano ~/appdb.load ``` ```text LOAD DATABASE FROM mysql://pgloader:your_mysql_password@your_mysql_host/appdb INTO postgresql://appuser:your_strong_password@127.0.0.1/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: - `FROM` e `INTO`: 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 en `public`, 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: ```bash chmod 600 ~/appdb.load ``` ## Paso 7: Ejecutar la migración Lanza pgLoader con el archivo de carga y guarda la salida en un registro: ```bash 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: ```output 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: ```bash mysql -h your_mysql_host -u pgloader -p -e "SELECT COUNT(*) FROM appdb.pedidos;" ``` En PostgreSQL: ```bash psql "host=127.0.0.1 dbname=appdb user=appuser" -c "SELECT count(*) FROM pedidos;" ``` Revisa que los tipos se han convertido como esperabas: ```bash 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: ```bash 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: ```bash 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: 1. Pon la aplicación en modo mantenimiento para que no haya escrituras en MySQL. 2. Ejecuta de nuevo `pgloader ~/appdb.load` para cargar los datos finales. 3. Repite las comprobaciones del paso 8. 4. 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`.