Backup de Bases de Datos MySQL/PostgreSQL: Guía Completa para Entornos de Producción
Introducción
Las bases de datos son el corazón de las aplicaciones modernas, almacenando datos críticos del negocio, información de usuarios, transacciones y estado operacional. A diferencia de los archivos estáticos, las bases de datos requieren consideraciones especiales de backup debido a su naturaleza dinámica, requisitos de consistencia y relaciones complejas entre datos. Un backup de base de datos corrupto o incompleto puede ser peor que no tener backup—la restauración podría fallar o resultar en corrupción de datos que se propaga por tu aplicación.
Esta guía completa cubre estrategias profesionales de backup de bases de datos para MySQL/MariaDB y PostgreSQL, los dos sistemas de bases de datos relacionales de código abierto más populares. Exploraremos backups lógicos y físicos, recuperación punto-en-el-tiempo, backups basados en replicación, estrategias de automatización, procedimientos de restauración y escenarios del mundo real implementando la regla de backup 3-2-1 para protección de bases de datos.
Ya sea que estés gestionando una pequeña base de datos de aplicación o infraestructura de bases de datos a escala empresarial, comprender técnicas apropiadas de backup asegura integridad de datos, continuidad de negocio y cumplimiento regulatorio.
Comprendiendo Tipos de Backup de Bases de Datos
Backups Lógicos vs Físicos
Backups lógicos (usando mysqldump, pg_dump):
- Exportar datos como sentencias SQL o formatos personalizados
- Legible por humanos, portable entre plataformas
- Puede hacer backup de bases de datos, tablas o filas específicas
- Más lento para bases de datos grandes
- Más fácil para restauración selectiva
- Generalmente tamaños de archivo más grandes
Backups físicos (copias de sistema de archivos, snapshots):
- Copiar archivos de base de datos reales
- Mucho más rápido para bases de datos grandes
- Backup completo de servidor o tablespace
- Específico de plataforma (no se puede mover entre arquitecturas fácilmente)
- Requiere snapshot consistente
- Tamaños de backup más pequeños
Requisitos de Consistencia de Backup
Por qué importa la consistencia: Las bases de datos mantienen relaciones entre tablas. Un backup inconsistente captura datos a mitad de transacción, resultando en violaciones de integridad referencial, índices corruptos o fallos de aplicación al restaurar.
Asegurar consistencia:
MySQL/MariaDB:
--single-transaction: Usa aislamiento de transacción para InnoDB- Bloqueo de tabla para tablas MyISAM
- Detener escrituras durante backup (para consistencia crítica)
PostgreSQL:
- pg_dump usa aislamiento de snapshot automáticamente
- Las transacciones en ejecución no afectan consistencia de backup
- MVCC (Control de Concurrencia Multi-Versión) proporciona consistencia
Estrategias de Backup MySQL/MariaDB
mysqldump - Backups Lógicos
La herramienta estándar para backups lógicos de MySQL:
Sintaxis básica de mysqldump:
mysqldump [opciones] nombre_base_datos > backup.sql
Backup completo de servidor de base de datos:
# Todas las bases de datos
mysqldump --all-databases > all-databases.sql
# Todas las bases de datos con rutinas, triggers, eventos
mysqldump --all-databases \
--routines \
--triggers \
--events \
> all-databases-complete.sql
Backup de base de datos única:
# Base de datos específica
mysqldump database_name > database_name.sql
# Múltiples bases de datos
mysqldump --databases db1 db2 db3 > multiple-databases.sql
Backup consistente de InnoDB (recomendado):
mysqldump --all-databases \
--single-transaction \
--quick \
--lock-tables=false \
--routines \
--triggers \
--events \
> backup.sql
Explicación de opciones críticas:
--single-transaction: Usa lectura consistente para InnoDB (sin bloqueo de tabla)--quick: Recupera filas una a la vez (reduce uso de memoria)--lock-tables=false: No bloquear tablas (seguro con --single-transaction)--routines: Incluir procedimientos almacenados/funciones--triggers: Incluir triggers--events: Incluir eventos programados
Backup comprimido:
mysqldump --all-databases \
--single-transaction \
--routines \
--triggers \
--events \
| gzip > backup-$(date +%Y%m%d).sql.gz
Dividir bases de datos grandes:
# Backup de cada base de datos por separado
for db in $(mysql -e "SHOW DATABASES;" | grep -Ev "Database|information_schema|performance_schema|mysql|sys"); do
echo "Haciendo backup de base de datos: $db"
mysqldump --single-transaction \
--routines --triggers --events \
"$db" | gzip > "$db-$(date +%Y%m%d).sql.gz"
done
Script de Backup de Producción MySQL
#!/bin/bash
# /usr/local/bin/mysql-backup.sh
# Backup de producción MySQL con manejo de errores y verificación
set -euo pipefail
# Configuración
BACKUP_DIR="/backup/mysql"
BACKUP_DATE=$(date +%Y%m%d-%H%M%S)
BACKUP_PATH="$BACKUP_DIR/$BACKUP_DATE"
LOG_FILE="/var/log/mysql-backup.log"
RETENTION_DAYS=30
ADMIN_EMAIL="[email protected]"
# Credenciales MySQL (preferir .my.cnf para seguridad)
MYSQL_USER="backup"
MYSQL_PASSWORD="secure-password"
# O usar: --defaults-extra-file=/root/.my.cnf
# Registro
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
error_exit() {
log "ERROR: $1"
echo "Backup de MySQL falló: $1" | mail -s "Backup de MySQL FALLÓ" "$ADMIN_EMAIL"
exit 1
}
# Crear directorio de backup
mkdir -p "$BACKUP_PATH"
log "Iniciando backup de MySQL a $BACKUP_PATH"
# Obtener lista de bases de datos (excluir bases de datos del sistema)
DATABASES=$(mysql -u "$MYSQL_USER" -p"$MYSQL_PASSWORD" -e "SHOW DATABASES;" \
| grep -Ev "Database|information_schema|performance_schema|mysql|sys")
# Backup de cada base de datos
for db in $DATABASES; do
log "Haciendo backup de base de datos: $db"
mysqldump -u "$MYSQL_USER" -p"$MYSQL_PASSWORD" \
--single-transaction \
--quick \
--lock-tables=false \
--routines \
--triggers \
--events \
"$db" | gzip > "$BACKUP_PATH/$db.sql.gz"
if [ ${PIPESTATUS[0]} -ne 0 ]; then
error_exit "Falló backup de base de datos: $db"
fi
# Verificar que archivo de backup fue creado
if [ ! -f "$BACKUP_PATH/$db.sql.gz" ]; then
error_exit "Archivo de backup no encontrado: $db.sql.gz"
fi
# Verificar que tamaño de archivo es razonable (>1KB)
SIZE=$(stat -c%s "$BACKUP_PATH/$db.sql.gz" 2>/dev/null || stat -f%z "$BACKUP_PATH/$db.sql.gz")
if [ "$SIZE" -lt 1024 ]; then
log "ADVERTENCIA: Archivo de backup sospechosamente pequeño: $db.sql.gz ($SIZE bytes)"
fi
done
# Backup de todas las bases de datos juntas (para conveniencia)
log "Creando archivo de backup completo"
mysqldump -u "$MYSQL_USER" -p"$MYSQL_PASSWORD" \
--all-databases \
--single-transaction \
--routines \
--triggers \
--events \
| gzip > "$BACKUP_PATH/all-databases.sql.gz"
# Backup de configuración MySQL
log "Haciendo backup de configuración MySQL"
cp -a /etc/mysql "$BACKUP_PATH/mysql-config"
# Crear manifiesto de backup
cat > "$BACKUP_PATH/MANIFEST.txt" << EOF
Manifiesto de Backup MySQL
Fecha: $(date)
Servidor: $(hostname)
Versión MySQL: $(mysql -V)
Bases de datos respaldadas:
$(echo "$DATABASES" | tr '\n' ', ')
Archivos de backup:
$(ls -lh "$BACKUP_PATH"/*.sql.gz)
Tamaño total de backup:
$(du -sh "$BACKUP_PATH")
EOF
# Limpiar backups antiguos
log "Limpiando backups de más de $RETENTION_DAYS días"
find "$BACKUP_DIR" -maxdepth 1 -type d -mtime +$RETENTION_DAYS -exec rm -rf {} \;
# Notificación de éxito
log "Backup de MySQL completado exitosamente"
{
echo "Backup de MySQL completado exitosamente"
echo ""
cat "$BACKUP_PATH/MANIFEST.txt"
} | mail -s "Backup de MySQL Exitoso - $(hostname)" "$ADMIN_EMAIL"
exit 0
Backups de Binary Log para Recuperación Punto-en-el-Tiempo
Habilitar recuperación punto-en-el-tiempo usando binary logs:
Habilitar binary logging (/etc/mysql/my.cnf):
[mysqld]
server-id = 1
log_bin = /var/log/mysql/mysql-bin.log
binlog_format = ROW
expire_logs_days = 7
max_binlog_size = 100M
Backup de binary logs:
#!/bin/bash
# /usr/local/bin/mysql-binlog-backup.sh
BINLOG_BACKUP_DIR="/backup/mysql-binlogs"
DATE=$(date +%Y%m%d)
mkdir -p "$BINLOG_BACKUP_DIR/$DATE"
# Vaciar logs para iniciar nuevo binlog
mysql -e "FLUSH BINARY LOGS;"
# Copiar binary logs
cp /var/log/mysql/mysql-bin.* "$BINLOG_BACKUP_DIR/$DATE/"
# Mantener 30 días de binary logs
find "$BINLOG_BACKUP_DIR" -type d -mtime +30 -exec rm -rf {} \;
Procedimiento de recuperación punto-en-el-tiempo:
# 1. Restaurar backup completo
gunzip < all-databases.sql.gz | mysql
# 2. Aplicar binary logs hasta punto específico en el tiempo
mysqlbinlog --stop-datetime="2026-01-11 14:30:00" \
/backup/mysql-binlogs/*/mysql-bin.* | mysql
# O detener en posición específica
mysqlbinlog --stop-position=12345 mysql-bin.000001 | mysql
Backups Físicos con Percona XtraBackup
Para bases de datos grandes, los backups físicos son mucho más rápidos:
Instalar Percona XtraBackup:
# Ubuntu/Debian
wget https://repo.percona.com/apt/percona-release_latest.generic_all.deb
sudo dpkg -i percona-release_latest.generic_all.deb
sudo apt update
sudo apt install percona-xtrabackup-80
# CentOS/RHEL
sudo yum install https://repo.percona.com/yum/percona-release-latest.noarch.rpm
sudo yum install percona-xtrabackup-80
Backup completo:
# Crear backup completo
xtrabackup --backup \
--target-dir=/backup/mysql-physical/full-$(date +%Y%m%d)
# Preparar backup para restauración
xtrabackup --prepare \
--target-dir=/backup/mysql-physical/full-20260111
Backup incremental:
# Backup completo (base)
xtrabackup --backup \
--target-dir=/backup/mysql-physical/base
# Backup incremental 1
xtrabackup --backup \
--target-dir=/backup/mysql-physical/inc1 \
--incremental-basedir=/backup/mysql-physical/base
# Preparar para restauración
xtrabackup --prepare --apply-log-only \
--target-dir=/backup/mysql-physical/base
xtrabackup --prepare --apply-log-only \
--target-dir=/backup/mysql-physical/base \
--incremental-dir=/backup/mysql-physical/inc1
Estrategias de Backup PostgreSQL
pg_dump - Backups Lógicos
Herramienta de backup lógico estándar de PostgreSQL:
Todas las bases de datos:
pg_dumpall > all-databases.sql
# Comprimido
pg_dumpall | gzip > all-databases.sql.gz
# Como usuario postgres
sudo -u postgres pg_dumpall | gzip > all-databases.sql.gz
Base de datos única:
# Formato SQL plano
pg_dump database_name > database_name.sql
# Formato personalizado (comprimido, soporta restauración paralela)
pg_dump -Fc database_name > database_name.dump
# Formato de directorio (dump y restauración paralela)
pg_dump -Fd database_name -f database_name_dump/
# SQL comprimido
pg_dump database_name | gzip > database_name.sql.gz
Opciones críticas de pg_dump:
-Fc: Formato personalizado (comprimido, restauración flexible)-Fd: Formato de directorio (operaciones paralelas)-Fp: Formato SQL plano (legible por humanos)-j N: Dump paralelo con N trabajos (solo formato de directorio)--clean: Incluir sentencias DROP--if-exists: Usar IF EXISTS con DROP--no-owner: No restaurar propiedad--no-privileges: No restaurar privilegios
Backup paralelo para bases de datos grandes:
# 4 trabajadores paralelos
pg_dump -Fd database_name -j 4 -f database_name_dump/
Script de Backup de Producción PostgreSQL
#!/bin/bash
# /usr/local/bin/postgresql-backup.sh
# Script de backup de producción PostgreSQL
set -euo pipefail
# Configuración
BACKUP_DIR="/backup/postgresql"
BACKUP_DATE=$(date +%Y%m%d-%H%M%S)
BACKUP_PATH="$BACKUP_DIR/$BACKUP_DATE"
LOG_FILE="/var/log/postgresql-backup.log"
RETENTION_DAYS=30
PG_USER="postgres"
ADMIN_EMAIL="[email protected]"
# Registro
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
error_exit() {
log "ERROR: $1"
echo "Backup de PostgreSQL falló: $1" | mail -s "Backup de PostgreSQL FALLÓ" "$ADMIN_EMAIL"
exit 1
}
# Crear directorio de backup
mkdir -p "$BACKUP_PATH"
log "Iniciando backup de PostgreSQL a $BACKUP_PATH"
# Backup de todas las bases de datos (globales + todas las bases de datos)
log "Haciendo backup de todas las bases de datos con pg_dumpall"
sudo -u "$PG_USER" pg_dumpall | gzip > "$BACKUP_PATH/all-databases.sql.gz"
if [ ${PIPESTATUS[0]} -ne 0 ]; then
error_exit "pg_dumpall falló"
fi
# Obtener lista de bases de datos
DATABASES=$(sudo -u "$PG_USER" psql -t -c "SELECT datname FROM pg_database WHERE datistemplate = false AND datname != 'postgres'")
# Backup de cada base de datos en formato personalizado
for db in $DATABASES; do
db=$(echo $db | xargs) # Recortar espacios en blanco
log "Haciendo backup de base de datos: $db"
sudo -u "$PG_USER" pg_dump -Fc "$db" > "$BACKUP_PATH/$db.dump"
if [ $? -ne 0 ]; then
log "ADVERTENCIA: Falló backup de base de datos: $db"
fi
# Verificar archivo de backup
if [ ! -f "$BACKUP_PATH/$db.dump" ]; then
log "ADVERTENCIA: Archivo de backup no encontrado: $db.dump"
fi
done
# Backup de configuración PostgreSQL
log "Haciendo backup de configuración PostgreSQL"
sudo cp -a /etc/postgresql "$BACKUP_PATH/postgresql-config"
sudo -u "$PG_USER" cp -a /var/lib/postgresql/*/main/postgresql.conf "$BACKUP_PATH/" 2>/dev/null || true
sudo -u "$PG_USER" cp -a /var/lib/postgresql/*/main/pg_hba.conf "$BACKUP_PATH/" 2>/dev/null || true
# Crear manifiesto
cat > "$BACKUP_PATH/MANIFEST.txt" << EOF
Manifiesto de Backup PostgreSQL
Fecha: $(date)
Servidor: $(hostname)
Versión PostgreSQL: $(sudo -u postgres psql --version)
Bases de datos respaldadas:
$(echo "$DATABASES")
Archivos de backup:
$(ls -lh "$BACKUP_PATH"/)
Tamaño total de backup:
$(du -sh "$BACKUP_PATH")
EOF
# Limpiar backups antiguos
log "Limpiando backups de más de $RETENTION_DAYS días"
find "$BACKUP_DIR" -maxdepth 1 -type d -mtime +$RETENTION_DAYS -exec rm -rf {} \;
log "Backup de PostgreSQL completado exitosamente"
# Notificación de éxito
{
echo "Backup de PostgreSQL completado exitosamente"
echo ""
cat "$BACKUP_PATH/MANIFEST.txt"
} | mail -s "Backup de PostgreSQL Exitoso - $(hostname)" "$ADMIN_EMAIL"
exit 0
Archivado WAL para Recuperación Punto-en-el-Tiempo
Configurar archivado de Write-Ahead Log (WAL):
Configurar archivado WAL (/etc/postgresql/14/main/postgresql.conf):
wal_level = replica
archive_mode = on
archive_command = 'test ! -f /backup/postgresql-wal/%f && cp %p /backup/postgresql-wal/%f'
archive_timeout = 300 # Forzar cambio de WAL cada 5 minutos
Crear directorio de archivo WAL:
sudo mkdir -p /backup/postgresql-wal
sudo chown postgres:postgres /backup/postgresql-wal
sudo chmod 700 /backup/postgresql-wal
Reiniciar PostgreSQL:
sudo systemctl restart postgresql
Backup base para PITR:
#!/bin/bash
# Crear backup base para recuperación punto-en-el-tiempo
BACKUP_DIR="/backup/postgresql-pitr"
DATE=$(date +%Y%m%d-%H%M%S)
sudo -u postgres pg_basebackup -D "$BACKUP_DIR/base-$DATE" -Ft -z -P
echo "Backup base creado: $BACKUP_DIR/base-$DATE"
Procedimiento de recuperación punto-en-el-tiempo:
# 1. Detener PostgreSQL
sudo systemctl stop postgresql
# 2. Hacer backup del directorio de datos actual
sudo mv /var/lib/postgresql/14/main /var/lib/postgresql/14/main.old
# 3. Extraer backup base
sudo -u postgres tar -xzf /backup/postgresql-pitr/base-20260111/base.tar.gz \
-C /var/lib/postgresql/14/main
# 4. Crear configuración de recuperación
sudo -u postgres cat > /var/lib/postgresql/14/main/recovery.signal << EOF
restore_command = 'cp /backup/postgresql-wal/%f %p'
recovery_target_time = '2026-01-11 14:30:00'
EOF
# 5. Iniciar PostgreSQL (recuperación comienza automáticamente)
sudo systemctl start postgresql
# 6. Verificar recuperación
sudo -u postgres psql -c "SELECT pg_is_in_recovery();"
Backups Físicos con pg_basebackup
Herramienta de backup físico integrada:
Uso básico:
# Crear backup físico
sudo -u postgres pg_basebackup -D /backup/pg-physical -Ft -z -P
# Opciones:
# -D: Directorio destino
# -Ft: Formato tar
# -z: compresión gzip
# -P: Reporte de progreso
Compresión paralela:
sudo -u postgres pg_basebackup -D - -Ft | \
pigz -p 4 > /backup/pg-physical-$(date +%Y%m%d).tar.gz
Automatización y Programación
Timer de Systemd para Backups de Bases de Datos
Servicio de backup MySQL (/etc/systemd/system/mysql-backup.service):
[Unit]
Description=Servicio de Backup MySQL
After=mysql.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/mysql-backup.sh
User=root
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Timer de backup MySQL (/etc/systemd/system/mysql-backup.timer):
[Unit]
Description=Backup Diario de MySQL
Requires=mysql-backup.service
[Timer]
OnCalendar=daily
OnCalendar=*-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.target
Habilitar:
sudo systemctl daemon-reload
sudo systemctl enable --now mysql-backup.timer
Programación Basada en Cron
# /etc/cron.d/database-backups
# Backup diario de MySQL a las 2 AM
0 2 * * * root /usr/local/bin/mysql-backup.sh >> /var/log/mysql-backup-cron.log 2>&1
# Backup diario de PostgreSQL a las 2:30 AM
30 2 * * * root /usr/local/bin/postgresql-backup.sh >> /var/log/postgresql-backup-cron.log 2>&1
# Backup de binary log de MySQL cada 6 horas
0 */6 * * * root /usr/local/bin/mysql-binlog-backup.sh >> /var/log/mysql-binlog.log 2>&1
# Limpieza de WAL de PostgreSQL semanalmente
0 3 * * 0 root find /backup/postgresql-wal -type f -mtime +7 -delete
Procedimientos de Restauración
Restauración MySQL
Restaurar backup completo:
# Desde SQL sin comprimir
mysql < all-databases.sql
# Desde SQL comprimido con gzip
gunzip < all-databases.sql.gz | mysql
# Base de datos específica
mysql database_name < database_name.sql
Restaurar con usuario/contraseña:
mysql -u root -p < all-databases.sql
# Con contraseña en comando (menos seguro)
mysql -u root -pTuContraseña < all-databases.sql
Restaurar a nombre de base de datos diferente:
# Crear nueva base de datos
mysql -e "CREATE DATABASE nuevo_nombre_base_datos;"
# Restaurar (editar SQL para cambiar nombre de base de datos)
sed 's/nombre_base_datos_antiguo/nuevo_nombre_base_datos/g' backup.sql | mysql
Restauración PostgreSQL
Restaurar desde pg_dumpall:
# Eliminar bases de datos existentes primero (si se está recreando)
sudo -u postgres psql -c "DROP DATABASE database_name;"
# Restaurar
gunzip < all-databases.sql.gz | sudo -u postgres psql
Restaurar base de datos única:
# Formato personalizado
sudo -u postgres pg_restore -d database_name database_name.dump
# Con opciones
sudo -u postgres pg_restore \
--clean \
--if-exists \
-d database_name \
database_name.dump
# Restauración paralela (4 trabajos)
sudo -u postgres pg_restore -j 4 -d database_name database_name.dump
Restaurar a base de datos diferente:
# Crear base de datos destino
sudo -u postgres createdb nuevo_nombre_base_datos
# Restaurar
sudo -u postgres pg_restore -d nuevo_nombre_base_datos nombre_base_datos_antiguo.dump
Escenarios del Mundo Real
Escenario 1: Protección de Base de Datos E-commerce
Requisitos:
- Base de datos MySQL de alta transacción
- RPO de 15 minutos
- Capacidad de recuperación punto-en-el-tiempo
Implementación:
# Backup completo diario a las 2 AM
0 2 * * * /usr/local/bin/mysql-full-backup.sh
# Backup de binary log cada 15 minutos
*/15 * * * * /usr/local/bin/mysql-binlog-backup.sh
# Sincronización offsite por hora
0 * * * * rsync -az /backup/mysql/ backup-server:/backups/mysql-production/
Escenario 2: Entorno Multi-Base de Datos
Requisitos:
- MySQL y PostgreSQL
- Retención diferente para cada uno
- Verificación automatizada
Script de backup completo:
#!/bin/bash
# /usr/local/bin/backup-all-databases.sh
# Backup de MySQL
/usr/local/bin/mysql-backup.sh
# Backup de PostgreSQL
/usr/local/bin/postgresql-backup.sh
# Verificar que ambos se completaron
if [ -f /backup/mysql/*/MANIFEST.txt ] && [ -f /backup/postgresql/*/MANIFEST.txt ]; then
echo "Todos los backups de bases de datos completados" | mail -s "Backup BD Exitoso" [email protected]
else
echo "Verificación de backup de base de datos falló" | mail -s "Backup BD FALLÓ" [email protected]
fi
Conclusión
Los backups de bases de datos requieren enfoques especializados para asegurar consistencia, integridad y recuperabilidad. Ya sea usando backups lógicos con mysqldump/pg_dump para flexibilidad o backups físicos para rendimiento, implementar estrategias robustas de backup de bases de datos protege el activo más crítico de tu organización: sus datos.
Puntos clave:
- Asegurar consistencia: Usar opciones apropiadas (--single-transaction, etc.)
- Implementar PITR: Binary logs/archivado WAL para recuperación punto-en-el-tiempo
- Automatizar de manera confiable: Programar backups regulares con monitoreo
- Probar restauración: Ejercicios regulares de restauración son esenciales
- Asegurar backups: Cifrar dumps de bases de datos sensibles
- Seguir 3-2-1: Copias de backup de bases de datos local, remota y offsite
- Documentar procedimientos: Mantener documentación detallada de restauración
Estrategias apropiadas de backup de bases de datos, combinadas con automatización, monitoreo y pruebas regulares, aseguran continuidad de negocio y protección de datos frente a fallos de hardware, errores humanos o desastres.


