MySQL backup — automatyzacja i rotacja kopii zapasowych
Opublikowano: 9 kwietnia 2026 · Kategoria: Bazy danych
Backup bazy danych to nie jednorazowe zadanie — to proces, który musi działać automatycznie, być weryfikowany i przechowywany poza serwerem głównym. Ten artykuł pokazuje jak zbudować kompletny system backupu MySQL: od mysqldump przez rotację, upload do S3/Backblaze B2, po monitoring i testowanie przywracania.
mysqldump — podstawy i ważne opcje
mysqldump to standardowe narzędzie do tworzenia logicznych kopii baz MySQL/MariaDB.
Generuje plik SQL z instrukcjami CREATE TABLE i INSERT, który można przywrócić na dowolnym serwerze
z kompatybilną wersją MySQL.
# Backup jednej bazy danych (InnoDB — bez blokowania tabel) mysqldump \ --single-transaction \ --quick \ --lock-tables=false \ -u root -p nazwa_bazy > /backup/nazwa_bazy.sql # Backup wszystkich baz danych mysqldump \ --single-transaction \ --all-databases \ --events \ --routines \ --triggers \ -u root -p > /backup/all_databases.sql # Backup z natychmiastową kompresją (pipe do gzip) mysqldump --single-transaction -u root -p nazwa_bazy | gzip > /backup/nazwa_bazy.sql.gz # Przywracanie z pliku SQL mysql -u root -p nazwa_bazy < /backup/nazwa_bazy.sql # Przywracanie z archiwum gzip gunzip < /backup/nazwa_bazy.sql.gz | mysql -u root -p nazwa_bazy
Tip: Utwórz dedykowanego użytkownika MySQL tylko do backupów z minimalnymi
uprawnieniami: GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER ON *.* TO
'backup_user'@'localhost' IDENTIFIED BY 'haslo';
— przechowuj dane logowania w pliku ~/.my.cnf aby uniknąć hasła w skrypcie.
Skrypt backup z rotacją 7/4/12
Standardowa strategia rotacji to: 7 backupów dziennych, 4 tygodniowe, 12 miesięcznych. Poniższy skrypt implementuje tę strategię w Bashu:
#!/bin/bash # /usr/local/bin/mysql-backup.sh # Backup MySQL z rotacją 7/4/12 i powiadomieniem email set -euo pipefail # --- KONFIGURACJA --- DB_USER="backup_user" DB_PASS="twoje_haslo" BACKUP_DIR="/backup/mysql" LOG_FILE="/var/log/mysql-backup.log" ALERT_EMAIL="[email protected]" KEEP_DAILY=7 KEEP_WEEKLY=4 KEEP_MONTHLY=12 # --- FUNKCJE --- log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $1" | tee -a "$LOG_FILE" } alert() { echo "$1" | mail -s "BACKUP ERROR: MySQL backup failed" "$ALERT_EMAIL" } # --- GŁÓWNA LOGIKA --- TODAY=$(date +%Y-%m-%d) DAY_OF_WEEK=$(date +%u) # 1=Pon, 7=Nie DAY_OF_MONTH=$(date +%d) mkdir -p "$BACKUP_DIR"/{daily,weekly,monthly} log "Starting MySQL backup..." # Lista baz danych (pomiń systemowe) DATABASES=$(mysql -u"$DB_USER" -p"$DB_PASS" -e "SHOW DATABASES;" 2>/dev/null | \ grep -Ev "(Database|information_schema|performance_schema|sys)") # Backup każdej bazy for DB in $DATABASES; do FILENAME="${DB}_${TODAY}.sql.gz" log "Dumping: $DB" mysqldump \ --single-transaction \ --quick \ --lock-tables=false \ -u"$DB_USER" -p"$DB_PASS" \ "$DB" 2>/dev/null | gzip > "$BACKUP_DIR/daily/$FILENAME" # Tygodniowy (każda niedziela) if [ "$DAY_OF_WEEK" -eq 7 ]; then cp "$BACKUP_DIR/daily/$FILENAME" "$BACKUP_DIR/weekly/$FILENAME" log "Weekly backup saved: $FILENAME" fi # Miesięczny (1. dzień miesiąca) if [ "$DAY_OF_MONTH" -eq "01" ]; then cp "$BACKUP_DIR/daily/$FILENAME" "$BACKUP_DIR/monthly/$FILENAME" log "Monthly backup saved: $FILENAME" fi done # Rotacja: usuń stare backupy find "$BACKUP_DIR/daily" -name "*.sql.gz" -mtime +"$KEEP_DAILY" -delete find "$BACKUP_DIR/weekly" -name "*.sql.gz" -mtime +$(( KEEP_WEEKLY * 7 )) -delete find "$BACKUP_DIR/monthly" -name "*.sql.gz" -mtime +$(( KEEP_MONTHLY * 31 )) -delete log "Backup completed. Disk usage: $(du -sh $BACKUP_DIR | cut -f1)"
# Nadaj uprawnienia i dodaj do crona sudo chmod +x /usr/local/bin/mysql-backup.sh # Cron: backup codziennie o 2:00 w nocy crontab -e # Dodaj: 0 2 * * * /usr/local/bin/mysql-backup.sh >> /var/log/mysql-backup.log 2>&1
Backup na zewnętrzne storage — rclone + S3/Backblaze B2
Backup przechowywany tylko na serwerze głównym nie jest prawdziwym backupem — awaria dysku
niszczy i dane, i kopię. Użyj rclone do automatycznego uploadu na zewnętrzne storage:
# Instalacja rclone
curl https://rclone.org/install.sh | sudo bash
# Konfiguracja (interaktywna)
rclone config
# Wybierz: n (nowy remote)
# Nazwa: backblaze-b2
# Typ: b2 (Backblaze B2)
# Podaj: account ID, application key
# Bucket: twoj-bucket-backup
# Test połączenia
rclone lsd backblaze-b2:twoj-bucket-backup
# Upload katalogu backup
rclone sync /backup/mysql backblaze-b2:twoj-bucket-backup/mysql \
--transfers 4 \
--log-level INFO \
--log-file /var/log/rclone-backup.log # Dodaj rclone sync do skryptu backup (po rotacji lokalnej)
# W mysql-backup.sh:
log "Uploading to Backblaze B2..."
rclone sync "$BACKUP_DIR" backblaze-b2:twoj-bucket-backup/mysql \
--transfers 4 \
--quiet \
--log-file "$LOG_FILE" || alert "rclone upload failed!"
log "Upload complete." | Storage | Cena/GB/mies. | Egress | Uwagi |
|---|---|---|---|
| AWS S3 | ~$0.023 | $0.09/GB | Najdroższy, best-in-class niezawodność, najszersza integracja |
| Backblaze B2 | ~$0.006 | Darmowy do Cloudflare | Najtańszy, rclone-native, idealny dla backupów |
| Wasabi | ~$0.0068 | Darmowy | Min. 90 dni przechowywania per obiekt — uwaga przy częstych nadpisaniach |
Percona XtraBackup — hot backup bez locka
Dla baz danych powyżej kilku GB, gdzie mysqldump trwa zbyt długo, Percona XtraBackup oferuje fizyczne kopie plików InnoDB bez blokowania tabel:
# Instalacja Percona XtraBackup (Ubuntu 22.04)
wget https://downloads.percona.com/downloads/Percona-XtraBackup-8.0/latest/binary/debian/jammy/x86_64/percona-xtrabackup-80_8.0.36-31-1.jammy_amd64.deb
sudo dpkg -i percona-xtrabackup-80_*.deb
# Pełny backup
xtrabackup \
--backup \
--target-dir=/backup/xtrabackup/full \
--user=root \
--password=twoje_haslo
# Incremental backup (tylko zmiany od ostatniego backupu)
xtrabackup \
--backup \
--incremental-basedir=/backup/xtrabackup/full \
--target-dir=/backup/xtrabackup/inc1 \
--user=root \
--password=twoje_haslo
# Przygotowanie do przywracania (prepare)
xtrabackup --prepare --target-dir=/backup/xtrabackup/full
# Przywracanie (MySQL musi być zatrzymany!)
sudo systemctl stop mysql
xtrabackup --copy-back --target-dir=/backup/xtrabackup/full
sudo chown -R mysql:mysql /var/lib/mysql
sudo systemctl start mysql Testowanie restore — obowiązkowe
Backup bez testu przywracania nie jest wiarygodny. Zautomatyzuj test restore raz w tygodniu:
#!/bin/bash
# /usr/local/bin/test-mysql-restore.sh
BACKUP_FILE=$(ls -t /backup/mysql/daily/nazwa_bazy_*.sql.gz | head -1)
TEST_DB="restore_test_${RANDOM}"
echo "Testing restore: $BACKUP_FILE"
# Utwórz testową bazę
mysql -u root -p -e "CREATE DATABASE $TEST_DB;"
# Przywróć backup
gunzip < "$BACKUP_FILE" | mysql -u root -p "$TEST_DB"
# Weryfikuj tabele
TABLE_COUNT=$(mysql -u root -p -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$TEST_DB';" -sN)
echo "Tables restored: $TABLE_COUNT"
# Usuń bazę testową
mysql -u root -p -e "DROP DATABASE $TEST_DB;"
echo "Restore test: PASSED ($TABLE_COUNT tables)"