Programování

Zálohování MySQL a MariaDB: mysqldump, cron a obnova

Spolehlivé zálohy MySQL/MariaDB bez zamykání tabulek: mysqldump s --single-transaction, automatizace cronem, rotace, šifrování a test obnovy.

12. 10. 2026 · 4 min čtení

Záloha databáze je jedna z věcí, které všichni „mají“, dokud je nepotřebují obnovit. Tento návod ukazuje, jak MySQL nebo MariaDB zálohovat tak, aby záloha byla konzistentní, neblokovala web, automaticky se rotovala a – hlavně – šla obnovit.

Logická vs. fyzická záloha

Logická (mysqldump, mariadb-dump) Fyzická (mariabackup, Percona XtraBackup)
Výstup SQL příkazy kopie datových souborů
Vhodné pro do desítek GB velké databáze
Obnova pomalejší (znovu provádí SQL) rychlá
Přenositelnost mezi verzemi i servery stejná hlavní verze

Pro běžný web nebo aplikaci je mysqldump naprosto dostačující. Na MariaDB se od verze 10.5 nástroj jmenuje mariadb-dump; mysqldump zatím funguje jako alias.

Konzistentní dump bez zamykání

mysqldump \
  --single-transaction \
  --quick \
  --routines --triggers --events \
  --default-character-set=utf8mb4 \
  -u backup -p myapp > myapp.sql

Co jednotlivé přepínače dělají:

  • --single-transaction – dump proběhne v jedné transakci, takže data jsou konzistentní k jednomu okamžiku a tabulky InnoDB se nezamykají. Pro tabulky MyISAM to nefunguje.
  • --quick – čte řádky postupně místo načtení celé tabulky do paměti.
  • --routines --triggers --events – zahrne uložené procedury, triggery a plánované události. Triggery jsou ve výchozím stavu zapnuté, ostatní ne.
  • Pro všechny databáze najednou použijte --all-databases.

Uživatel jen pro zálohy

Nepoužívejte pro zálohy root. Vytvořte uživatele s minimálními právy:

CREATE USER 'backup'@'localhost' IDENTIFIED BY 'dlouhe-nahodne-heslo';
GRANT SELECT, SHOW VIEW, TRIGGER, LOCK TABLES, EVENT, PROCESS ON *.* TO 'backup'@'localhost';
FLUSH PRIVILEGES;

Od MySQL 8.0.21 potřebuje mysqldump právo PROCESS kvůli exportu tablespaces; pokud ho dávat nechcete, přidejte do příkazu --no-tablespaces.

Heslo nepište do příkazu (bylo by vidět v ps a historii shellu). Uložte ho do ~/.my.cnf uživatele, pod kterým zálohy běží:

[client]
user=backup
password=dlouhe-nahodne-heslo
chmod 600 ~/.my.cnf

Skript se kompresí a rotací

/usr/local/bin/db-backup.sh:

#!/usr/bin/env bash
set -euo pipefail

BACKUP_DIR=/var/backups/mysql
KEEP_DAYS=14
DATE=$(date +%F_%H-%M)

mkdir -p "$BACKUP_DIR"

for DB in $(mysql -N -e "SHOW DATABASES" | grep -Ev '^(information_schema|performance_schema|mysql|sys)$'); do
  mysqldump --single-transaction --quick --routines --triggers --events "$DB" \
    | gzip -c > "$BACKUP_DIR/${DB}_${DATE}.sql.gz"
done

find "$BACKUP_DIR" -name '*.sql.gz' -mtime +"$KEEP_DAYS" -delete

Díky set -o pipefail skript selže, i když spadne mysqldump uprostřed roury – jinak by gzip vrátil úspěch a vy byste měli poškozenou zálohu, o které nevíte.

sudo chmod 700 /usr/local/bin/db-backup.sh

Automatizace cronem

sudo crontab -e
30 2 * * * /usr/local/bin/db-backup.sh >> /var/log/db-backup.log 2>&1

Záloha poběží každý den ve 2:30. Při spuštění jako root se použije /root/.my.cnf.

Záloha mimo server

Záloha na stejném disku vás neochrání před výpadkem disku, smazáním serveru ani ransomwarem. Kopírujte ji jinam, ideálně šifrovaně. Například pomocí rclone na S3 kompatibilní úložiště:

rclone copy /var/backups/mysql remote:moje-zalohy/mysql --max-age 24h

Nebo ji před odesláním zašifrujte:

gpg --symmetric --cipher-algo AES256 myapp_2026-10-12_02-30.sql.gz

Klíč (heslo) uchovávejte mimo zálohovaný server.

Obnova

gunzip < myapp_2026-10-12_02-30.sql.gz | mysql myapp

Databáze myapp musí existovat (CREATE DATABASE myapp CHARACTER SET utf8mb4;). Pokud jste dumpovali s --databases nebo --all-databases, soubor obsahuje i CREATE DATABASE a USE.

Obnova jedné tabulky z velkého dumpu je nepříjemná; jednodušší je obnovit celý dump do dočasné databáze a tabulku z ní zkopírovat:

INSERT INTO myapp.customers SELECT * FROM restore_tmp.customers WHERE id = 42;

Testujte obnovu

Jednou za čas (klidně automaticky) obnovte poslední zálohu na testovací server nebo do Docker kontejneru a ověřte počty řádků:

docker run -d --name restore-test -e MARIADB_ROOT_PASSWORD=test mariadb:11
docker exec -i restore-test mariadb -uroot -ptest -e "CREATE DATABASE myapp"
gunzip < myapp_latest.sql.gz | docker exec -i restore-test mariadb -uroot -ptest myapp
docker exec restore-test mariadb -uroot -ptest -e "SELECT COUNT(*) FROM myapp.orders"

Počkejte pár sekund po startu kontejneru, než bude databáze připravená.

Kontrolní seznam

  • [ ] --single-transaction a InnoDB tabulky
  • [ ] dedikovaný uživatel, heslo v ~/.my.cnf s právy 600
  • [ ] set -o pipefail ve skriptu
  • [ ] rotace starých záloh
  • [ ] kopie mimo server, šifrovaná
  • [ ] pravidelný test obnovy

Pro body v čase (obnova „na 14:37“) je potřeba ještě binární log (log_bin) a mysqlbinlog – to už je téma pro samostatný článek.