Cómo migrar una base MySQL entre servidores sin pérdida de datos

Migra una base MySQL entre servidores Linux usando mysqldump, transferencia segura y verificación de integridad — sin downtime descontrolado ni registros perdidos.

Migrar una base MySQL entre servidores parece trivial hasta el primer intento en producción. Un mysqldump ejecutado en el momento equivocado puede capturar la mitad de una transacción, dejar datos inconsistentes o bloquear la base por minutos. Peor: en el apuro, es común olvidar triggers, stored procedures, eventos o permisos — y descubrirlo solo cuando la aplicación se rompe en el servidor nuevo.

Este tutorial está dirigido a sysadmins y desarrolladores que están moviendo una base MySQL entre dos servidores Linux — ya sea por upgrade de hardware, cambio de proveedor o consolidación de infraestructura. Cubrimos el camino clásico (dump + transferencia + restore) y cuándo vale la pena ir por la replicación asíncrona para eliminar el downtime casi por completo.

Tiempo estimado de ejecución: 30-60 minutos para bases de hasta 10 GB usando dump tradicional. Por encima de eso, planifica una ventana mayor o considera replicación. Todo el procedimiento se ejecuta por SSH; se asume que tienes acceso root o sudo en los dos servidores.

Prerrequisitos

Lo que necesitas antes de empezar

Acceso SSH con sudo en los dos servidores (origen y destino), MySQL o MariaDB instalado en ambos, espacio libre en el destino equivalente a al menos 2× el tamaño actual de la base y firewall liberado entre los hosts (puerto 3306 si vas a usar replicación, puerto 22 para transferencia vía SCP).

Confirma las versiones antes de seguir. Un dump de una versión más nueva restaurado en una versión más antigua rara vez funciona — sintaxis y tipos de datos divergen.

MySQL origen 8.0.36
MySQL destino 8.0.36 o superior
Espacio libre destino >= 2x tamaño de la base
Puerto SSH 22

Verifica la versión exacta de cada lado con mysql --version antes de continuar. Para bases con más de 50 GB, lee primero la sección sobre replicación asíncrona al final — el dump tradicional se vuelve impracticable a ese volumen.

Preparación en el servidor de origen

Antes de generar el dump, es crucial entender qué hay que migrar además de las tablas. Las bases reales tienen usuarios, permisos, eventos programados, procedures y triggers que viven fuera de los archivos .ibd de la base objetivo.

01

Lista las bases y mide el tamaño real:

mysql -uroot -p -e "SELECT table_schema AS db, ROUND(SUM(data_length + index_length)/1024/1024, 2) AS size_mb FROM information_schema.tables GROUP BY table_schema;"

Anota el tamaño de la base que vas a migrar — lo necesitas para dimensionar el espacio en el destino y estimar el tiempo de dump (~1-2 minutos por GB en disco rápido).

02

Verifica qué engines están en uso:

mysql -uroot -p -e "SELECT table_schema, engine, COUNT(*) FROM information_schema.tables WHERE table_schema = 'mibase' GROUP BY table_schema, engine;"

Si aparece MyISAM en el resultado, considera convertirlas a InnoDB antes de migrar. MyISAM no soporta --single-transaction, forzando al dump a usar lock de tabla que bloquea escrituras.

03

Exporta los usuarios y permisos en un archivo separado:

mysql -uroot -p -e "SELECT CONCAT('SHOW GRANTS FOR ''', user, '''@''', host, ''';') FROM mysql.user WHERE user NOT IN ('mysql.sys','mysql.session','mysql.infoschema','root');" -s --skip-column-names | mysql -uroot -p -s --skip-column-names | sed 's/$/;/' > grants.sql

El dump estándar de mysqldump no trae los usuarios — viven en mysql.user, fuera de la base de la aplicación. Sin este archivo, restauras los datos pero las conexiones de la aplicación fallan con “Access denied”.

Generando el dump consistente

Esta es la etapa más sensible. Un dump mal hecho captura datos parcialmente actualizados y genera inconsistencias invisibles hasta que la aplicación se rompe con claves foráneas inválidas.

04

Genera el dump con flags de consistencia:

mysqldump -uroot -p \
  --single-transaction \
  --routines \
  --triggers \
  --events \
  --master-data=2 \
  --hex-blob \
  --default-character-set=utf8mb4 \
  mibase > mibase_$(date +%Y%m%d_%H%M%S).sql

Cada flag cubre un caso específico: --single-transaction garantiza snapshot consistente sin lock; --routines --triggers --events incluyen procedures, triggers y eventos programados; --master-data=2 graba la posición del binlog como comentario (útil para replicación posterior); --hex-blob evita corrupción de campos BLOB durante la transferencia.

05

Comprime el dump para acelerar la transferencia:

gzip -9 mibase_*.sql

La compresión gzip reduce dumps SQL en 70-85% típicamente. En bases grandes, esto transforma una transferencia de 40 minutos en 8 minutos — vale el tiempo de CPU.

No uses mysqldump sin --single-transaction en producción

Sin ese flag, el dump captura cada tabla en un momento distinto. Si la aplicación está escribiendo, terminas con un pedido en la tabla orders que referencia un producto inexistente en products. El restore lo acepta, pero la aplicación se rompe al intentar cargar el pedido.

Transfiriendo el dump al servidor destino

Para dumps de hasta 10 GB, SCP es simple y seguro. Por encima de eso, considera rsync con reanudación o transferencia en chunks paralelos.

06

Transfiere vía SCP desde el servidor de origen:

scp mibase_*.sql.gz [email protected]:/tmp/

Si la conexión es inestable o el archivo supera los 5 GB, prefiere rsync --partial --progress — reanuda desde donde se detuvo si se corta a la mitad.

07

En el servidor de destino, valida el checksum:

# En el origen:
sha256sum mibase_*.sql.gz

# En el destino:
sha256sum /tmp/mibase_*.sql.gz

Compara los hashes. Una diferencia significa corrupción en la transferencia — repite el SCP/rsync antes de restaurar. Restaurar un dump corrupto genera errores aleatorios a mitad del camino y exige rehacer todo.

Restore en el servidor de destino

Antes de volcar el SQL, prepara el ambiente: confirma charset, ajusta buffers y deshabilita checks que ralentizan el restore.

08

Crea la base vacía con el charset correcto:

mysql -uroot -p -e "CREATE DATABASE mibase CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"

El charset equivocado es fuente clásica de ”?????” en lugar de acentos. Si el origen usa utf8mb4, el destino tiene que usar utf8mb4 — no utf8 (que es de 3 bytes y rompe emoji).

09

Restaura el dump con flags de performance:

gunzip < /tmp/mibase_*.sql.gz | mysql -uroot -p \
  --max_allowed_packet=512M \
  --init-command="SET autocommit=0; SET unique_checks=0; SET foreign_key_checks=0;" \
  mibase

Deshabilitar unique_checks y foreign_key_checks durante el restore acelera 3-5×. El dump es internamente consistente, así que no hay riesgo. Los checks vuelven al default en la próxima conexión.

10

Restaura usuarios y permisos:

mysql -uroot -p < grants.sql
mysql -uroot -p -e "FLUSH PRIVILEGES;"

FLUSH PRIVILEGES fuerza a MySQL a releer la tabla de permisos. Sin esto, algunos clientes siguen recibiendo “Access denied” incluso con los grants ya insertados.

Verificación de integridad

Restaurar sin error no significa que esté completo. Siempre valida conteos y estructura antes de apuntar la aplicación.

11

Compara el conteo de filas tabla por tabla:

# En el origen:
mysql -uroot -p -e "SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema='mibase' ORDER BY table_name;" > origen_counts.txt

# En el destino:
mysql -uroot -p -e "SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema='mibase' ORDER BY table_name;" > destino_counts.txt

diff origen_counts.txt destino_counts.txt

table_rows en information_schema es una estimación para InnoDB — puede divergir levemente. Para conteo exacto en tablas críticas, ejecuta SELECT COUNT(*) FROM tabla en ambos lados.

12

Verifica objetos auxiliares:

mysql -uroot -p mibase -e "SHOW TRIGGERS\G SHOW PROCEDURE STATUS WHERE Db='mibase'\G SHOW EVENTS\G"

Triggers, procedures y eventos solo vienen en el dump si usaste las flags --triggers --routines --events. Si alguno está faltando, recolócalo manualmente vía dump específico (mysqldump --no-data --no-create-info --triggers ...).

Replicación para reducir downtime

Para bases por encima de 50 GB o aplicaciones que no toleran ventana de downtime, configura el destino como réplica del origen vía CHANGE MASTER TO ... MASTER_LOG_FILE=... MASTER_LOG_POS=... usando la posición grabada por --master-data=2. Cuando Seconds_Behind_Master llega a cero, redireccionas la aplicación y promueves la réplica. El downtime real queda en segundos.

Resolución de problemas

Error “MySQL server has gone away” durante el restore

El dump tiene statements mayores que el max_allowed_packet configurado en el destino. Auméntalo temporalmente a 512M o 1G vía --max_allowed_packet en el comando o en /etc/mysql/my.cnf antes de intentar de nuevo.

El charset vuelve como “latin1” aunque la base esté en utf8mb4

El cliente MySQL en el destino está conectándose con un charset distinto al de la base. Fuérzalo con --default-character-set=utf8mb4 en el comando de restore. Confírmalo después con SHOW VARIABLES LIKE 'character_set%'.

Foreign keys rotas tras el restore

Sucedió porque no usaste --single-transaction en el dump o porque mezclaste tablas InnoDB y MyISAM. Verifica con SHOW ENGINE INNODB STATUS y considera rehacer el dump con la flag correcta — corregir manualmente es lento y propenso a errores.

Próximos pasos

Con la migración completada, considera automatizar backups recurrentes en el nuevo servidor para evitar tener que improvisar cuando necesites otra migración o un restore de emergencia. Documenta la configuración de innodb_buffer_pool_size y ajústala para reflejar la RAM disponible en el servidor nuevo — mantener el default de 128 MB en un servidor con 16 GB de RAM desperdicia performance.

Si estás consolidando bases en producción, un VPS Hostini con SSD NVMe ofrece IOPS suficientes para MySQL bajo carga sin necesidad de tuning agresivo de caché. La latencia consistente también ayuda en escenarios de replicación asíncrona entre regiones.

Próximos temas para profundizar: configuración de replicación MySQL master-replica, tuning de innodb_buffer_pool_size basado en workload real, backups incrementales con Percona XtraBackup y monitoreo de queries lentas vía slow log.

Preguntas frecuentes

¿Puedo migrar MySQL entre versiones diferentes (por ejemplo, 5.7 a 8.0)?

Sí, pero solo upgrade (5.7 → 8.0), nunca downgrade. Usa mysqldump en el servidor origen y restore en el destino — el formato SQL es compatible. Tras el restore, ejecuta mysql_upgrade en el destino 5.7→8.0 para actualizar las tablas del sistema. Prueba la autenticación: MySQL 8 usa caching_sha2_password por defecto, lo que puede romper drivers antiguos.

¿Cuál es la diferencia entre mysqldump y mysqlpump?

mysqldump es single-threaded y estable desde versiones antiguas. mysqlpump es multi-threaded y más rápido en bases grandes, pero tiene limitaciones con triggers y routines en algunas versiones. Para migraciones críticas de hasta 50 GB, mysqldump con --single-transaction es más predecible. Por encima de eso, considera Percona XtraBackup para evitar lock de lectura.

¿Cómo migrar sin ningún downtime?

Configura el servidor destino como réplica del origen usando replicación asíncrona. Cuando el destino alcanza al origen (Seconds_Behind_Master = 0), haces el switchover redirigiendo la aplicación al nuevo servidor y promoviendo la réplica a primaria. La ventana real de downtime queda en segundos — solo el tiempo de cambiar el endpoint.

¿mysqldump bloquea la base durante el dump?

Con --single-transaction en tablas InnoDB, no bloquea — usa MVCC para leer un snapshot consistente sin bloquear escrituras. Para MyISAM, el dump usa LOCK TABLES y bloquea escrituras durante la lectura. Mezclar InnoDB y MyISAM en el mismo dump compromete la consistencia: migra el esquema a InnoDB antes.

¿Por qué el tamaño del .sql es distinto al tamaño real de la base?

El dump es texto SQL puro, sin índices materializados ni páginas binarias. Normalmente queda en 30-60% del tamaño de los archivos .ibd. Tras el restore, el tamaño en disco vuelve a lo normal porque MySQL reconstruye índices y estadísticas. No confundas tamaño del dump con tamaño real de la base.

¿Cómo verificar que ninguna fila se perdió en la migración?

Cuenta filas tabla por tabla en ambos servidores con SELECT COUNT(*) y compara. Para integridad más profunda, genera checksums con pt-table-checksum (de Percona Toolkit) o compara CHECKSUM TABLE en cada tabla. Si los conteos coinciden pero hay sospecha de datos truncados, compara también SUM() de columnas numéricas críticas.

Temas:
Próximos pasos Cloud Ryzen con NVMe y protección DDoS siempre activa.Pon en producción en un VPS Hostini →
¿Te resultó útil este tutorial?
Hablar por WhatsApp