MySQL Backup: mysqldump, mariabackup und Binlog-Recovery im Praxistest (2026)

MySQL Backup: mysqldump, mariabackup und Binlog-Recovery im Praxistest (2026)

Ein MySQL Backup erstellst du in den meisten Fällen mit einem einzigen Befehl: mysqldump --single-transaction --routines --events meinedb > meinedb.sql. Das erzeugt eine konsistente Sicherung einer Datenbank, während deine Anwendung weiterläuft. Bei MariaDB heißt das Werkzeug mariadb-dump, die Optionen sind dieselben. Wiederhergestellt wird mit mysql meinedb < meinedb.sql. Für große Datenbanken und wenn du auf eine bestimmte Sekunde zurückspringen willst, gibt es die zweite Methode: eine physische Kopie mit mariabackup oder Percona XtraBackup plus das Binärlog, also Point-in-Time-Recovery.

Das ist die kurze Antwort. Die lange ist wichtiger, weil bei MySQL-Backups die meisten Dinge leise schiefgehen. Ein Backup-Skript meldet Erfolg und hat eine 401 Byte große Datei geschrieben. Ein Dump ohne die richtige Option enthält Kontostände, die sich nicht mehr addieren. Und ein halber Dump, versehentlich eingespielt, hat in unserem Test eine Tabelle mit fünf Millionen Zeilen auf null Zeilen geleert. Wir haben das am 4. November 2026 auf unserem eigenen Server mit MariaDB 11.8.9 und MySQL 8.4.13 nachgestellt, mit einer Testdatenbank aus fünf Millionen Bestellungen.

Dieser Artikel zeigt dir, welche Methode wofür taugt, wie du ein MySQL Backup wiederherstellst, wie du es automatisierst und woran du erkennst, dass es im Ernstfall funktioniert. Alle Zahlen stammen aus unseren eigenen Läufen. Wenn du PostgreSQL verwendest, findest du dasselbe Programm in unserem Artikel zum PostgreSQL Backup.

Video (Englisch): SimpleBackups zeigt den Weg vom manuellen mysqldump über Cron bis zur Automatisierung (Mai 2026). Hinweis: Der Kanal gehört einem Anbieter von Backup-Diensten. Die Messungen weiter unten sind unabhängig davon entstanden.

MySQL Backup: die drei Methoden im Überblick

Für MySQL und MariaDB gibt es drei grundsätzlich verschiedene Wege, eine Datenbank zu sichern. In einem ordentlichen Setup kommen meistens zwei davon zusammen.

1. Logisches Backup mit mysqldump bzw. mariadb-dump. Das Werkzeug verbindet sich wie ein normaler Client mit dem Server, liest alle Tabellen und schreibt SQL-Befehle heraus, mit denen sich die Datenbank wieder aufbauen lässt: CREATE TABLE, INSERT, Trigger, auf Wunsch Prozeduren und Events. Das Ergebnis ist eine Textdatei, die du lesen, durchsuchen und auf einem anderen Server einspielen kannst. Das meinen die meisten Leute, wenn sie „MySQL Backup“ sagen.

2. Physisches Backup mit mariabackup oder XtraBackup. Diese Werkzeuge kopieren die Datendateien von InnoDB direkt, während der Server läuft, und zeichnen parallel das Redo-Log mit. Im zweiten Schritt („prepare“) wird die Kopie in einen konsistenten Zustand gebracht. Das Ergebnis ist ein startfähiges Datenverzeichnis. Das geht viel schneller als ein Dump, funktioniert aber nur mit derselben Datenbank-Familie und einer passenden Version.

3. Binärlog (Binlog) für Point-in-Time-Recovery. Ist das Binlog eingeschaltet, protokolliert der Server jede Änderung. Kombiniert mit einem Voll-Backup kannst du jede Änderung danach nachspielen, bis zu einem frei gewählten Punkt. So kommst du auf den Stand eine Sekunde vor einem versehentlichen DELETE ohne WHERE.

Daneben gibt es Dateisystem-Snapshots (LVM, ZFS, Cloud-Volumes) und die Variante, bei gestopptem Server das Datenverzeichnis zu kopieren. Beides geht, braucht aber Sorgfalt. Ein cp -r /var/lib/mysql bei laufendem Server ist kein Backup, sondern eine Lotterie. Genau dieses Problem haben wir bei SQLite gemessen: Die Kopie im laufenden Betrieb enthielt 0 statt 1.000 Zeilen, nachzulesen in SQLite vs. PostgreSQL.

Links eine Datenbank, die sich in einen langen Stapel Papierbögen abrollt, rechts eine exakte Blockkopie derselben Datenbank

mysqldump / mariadb-dumpmariabackup / XtraBackupBinlog
Artlogisch (SQL-Text)physisch (Datendateien)Änderungsprotokoll
Sicherteine, mehrere oder alle Datenbankenden ganzen Serveralle Änderungen seit dem Backup
Backup unserer Test-DB5,4 s4,3 släuft dauerhaft mit
Restore unserer Test-DB43,9 s3,2 s (Server starten)38,8 s Nachspielen
Andere Version / anderer Serverja, meistensnur passende Versionnur in Verbindung mit Voll-Backup
Einzelne Tabelle zurückholeneinfachaufwendigmöglich, aber fummelig
Gut fürkleine bis mittlere DBs, Umzügegroße DBs, schneller RestoreRückkehr zu einer bestimmten Sekunde

Unser Testaufbau

Damit die Zahlen einordbar sind: Getestet haben wir mit MariaDB 11.8.9 und MySQL 8.4.13 in den offiziellen Docker-Images auf unserem eigenen Server (12 vCPUs, NVMe-SSD), jeweils in Wegwerf-Containern, die wir danach gelöscht haben. Die MariaDB-Testdatenbank shop enthielt eine Tabelle orders mit 5 Millionen Zeilen (801 MB inklusive Indizes), eine Tabelle customers mit 500.000 Zeilen und zwei kleine Tabellen konto_a und konto_b mit je 1.000 Kontoständen von je 1.000.

Die Kontotabellen sind der Konsistenztest. Eine Lastprozedur bucht in einer Schleife je einen Euro von einem zufälligen Konto in konto_a auf ein zufälliges Konto in konto_b, beides in einer Transaktion. Die Summe aller Konten bleibt deshalb immer genau 2.000.000. Ein Backup, das irgendwann während der Buchungen entsteht, muss ebenfalls auf genau 2.000.000 kommen, sonst hat es einen Zustand gesichert, den es nie gab. Die Prozedur schaffte rund 6.300 Buchungen pro Sekunde.

mysqldump und mariadb-dump: das Standard-Backup

Der richtige Befehl

mysqldump --single-transaction --routines --events --triggers \
  meinedb > meinedb.sql

Bei MariaDB heißt das Werkzeug seit Version 10.5 mariadb-dump, mysqldump ist auf den meisten Systemen noch als Alias vorhanden. Die Optionen im Einzelnen:

  • --single-transaction ist die wichtigste Option überhaupt. Der Dump startet eine Transaktion mit festem Snapshot und liest alle InnoDB-Tabellen aus genau diesem Moment, ohne andere Zugriffe zu blockieren.
  • --routines sichert gespeicherte Prozeduren und Funktionen. Die sind nicht standardmäßig dabei: Ohne die Option fand sich in unserem Dump keine einzige der Prozeduren, mit ihr war sie da.
  • --events sichert geplante Events des Event-Schedulers, ebenfalls nicht standardmäßig.
  • --triggers ist zwar Standard, schadet aber nicht, wenn du es ausschreibst.

Alle Datenbanken auf einmal sicherst du mit --all-databases, mehrere bestimmte mit --databases db1 db2. Mit --databases bekommt der Dump zusätzlich CREATE DATABASE- und USE-Zeilen, wodurch er beim Einspielen in die ursprüngliche Datenbank zurückschreibt. Das ist praktisch, kann aber auch genau die falsche Datenbank treffen, wenn du eigentlich in eine Kopie einspielen wolltest.

Wie schnell und wie groß?

Für unsere 840 MB große Testdatenbank haben wir drei Varianten gemessen:

VarianteDauerGröße
unkomprimiert (> datei.sql)5,4 s815 MB
mit gzip (| gzip)15,6 s187 MB
mit zstd (| zstd -3)5,4 s163 MB

Der Dump selbst ist schnell. gzip ist der Flaschenhals und verdreifacht die Laufzeit, weil es nur einen CPU-Kern nutzt. zstd komprimiert in unserem Test besser und kostet praktisch keine Zeit. Wenn zstd auf deinem Server installiert ist, gibt es kaum einen Grund, noch gzip zu nehmen. Unsere Testdaten enthalten viele wiederholte Zeichenketten, echte Datenbanken komprimieren meist etwas schlechter.

Der Restore der unkomprimierten Datei dauerte 43,9 Sekunden, also rund achtmal so lange wie das Backup. Das ist das typische Verhältnis bei logischen Backups: Beim Einspielen muss MySQL jede Zeile einfügen und alle Indizes neu aufbauen. Bei einer 50-GB-Datenbank bist du damit schnell bei mehreren Stunden Ausfallzeit. Danach stimmten Zeilenzahl und Summe aller Bestellbeträge exakt mit dem Original überein (5.000.000 Zeilen, 750.007.035,36), und die Prozedur war auch wieder da.

Konsistenz unter Last: der Unterschied zwischen zwei Optionen

Jetzt der wichtigste Test. Während die Lastprozedur Geld zwischen den Konten hin- und herbuchte, haben wir je dreimal einen Dump gezogen und danach die Kontostände im Dump aufsummiert.

OptionSumme im Dump (3 Läufe)richtig?
--single-transaction2.000.000 / 2.000.000 / 2.000.000 3 von 3
--skip-lock-tables (ohne Transaktion, ohne Sperre)2.033.835 / 2.032.735 / 2.034.479 0 von 3

Zwei Waagen, links im Gleichgewicht, rechts gekippt mit herunterfallenden Münzen

Ohne Transaktion liest der Dump konto_a zu einem Zeitpunkt und konto_b einige Sekunden später, dazwischen liegen tausende Buchungen. Im Dump stehen dann über 33.000 Euro, die es nie gab. Nichts im Dump verrät das, und die Datei sieht völlig normal aus. Bei einem Onlineshop wären das Bestellungen ohne Kunden oder Lagerbestände, die nicht zu den Bestellungen passen.

Und was passiert, wenn du gar keine Option angibst? Dann greift der Standard --lock-tables: Der Dump sperrt alle Tabellen einer Datenbank für Schreibzugriffe, solange er läuft. Das Ergebnis ist innerhalb dieser Datenbank konsistent (unsere Summe stimmte), aber die Anwendung steht. Ein einzelnes UPDATE auf ein Konto dauerte während eines Standard-Dumps 2,82 Sekunden, mit --single-transaction waren es 0,086 Sekunden. Über 20 Sekunden Last schaffte die Prozedur mit Standard-Sperre 93.043 Buchungen, mit --single-transaction 126.804, ein Viertel weniger Durchsatz. Bei einem Dump, der eine halbe Stunde läuft, ist dein Shop eine halbe Stunde lang praktisch nur lesbar.

Eine Einschränkung: --single-transaction wirkt nur bei InnoDB-Tabellen. Alte MyISAM-Tabellen haben keine Transaktionen und werden trotzdem inkonsistent gelesen. Prüfe mit SELECT table_schema, table_name FROM information_schema.tables WHERE engine='MyISAM' AND table_schema NOT IN ('mysql','information_schema','performance_schema','sys');, ob du noch welche hast. Außerdem sollte während des Dumps niemand ALTER TABLE oder TRUNCATE ausführen, das bricht den Snapshot.

Benutzer und Rechte sind nicht im Datenbank-Dump

Ein Dump der Datenbank shop enthält die Tabellen, aber nicht die Datenbankbenutzer. Wir haben einen Benutzer shopapp mit Rechten auf shop angelegt und danach gesucht: Im Dump mit --databases shop kam er null Mal vor. Spielst du so ein Backup auf einem neuen Server ein, sind alle Daten da, aber deine Anwendung kann sich nicht anmelden.

Bei MariaDB gibt es dafür seit 10.11 eine eigene Option, die Benutzer und Rechte als portable SQL-Befehle sichert:

mariadb-dump --system=users > benutzer.sql

Das ergab bei uns CREATE USER und GRANT-Zeilen inklusive Passwort-Hash. Bei MySQL ist der saubere Weg MySQL Shell (util.dumpUsers bzw. util.dumpInstance) oder ein Skript, das SHOW GRANTS für jeden Benutzer ausgibt. Ein Dump von --all-databases enthält zwar die Systemdatenbank mysql, lässt sich aber nur auf derselben Hauptversion sauber einspielen.

Die Fallen, die keine Fehlermeldung werfen

Die Pipe meldet Erfolg, obwohl nichts gesichert wurde

Das ist der Klassiker in fast jedem Backup-Skript aus dem Netz:

mysqldump meinedb | gzip > backup.sql.gz

Wir haben uns absichtlich beim Datenbanknamen vertippt (shpo statt shop). mariadb-dump meldete „Unknown database“, aber der Exit-Code der ganzen Zeile war 0, also Erfolg. Grund: Die Shell meldet den Exit-Code des letzten Befehls der Pipe, und gzip hat erfolgreich nichts komprimiert. Ergebnis: eine 401 Byte große Datei, die jede Nacht brav rotiert wird. Wer das Backup nie öffnet, merkt es erst, wenn er es braucht.

Die Lösung ist eine Zeile am Anfang des Skripts:

set -o pipefail

Damit lieferte derselbe Befehl Exit-Code 2, und dein Cronjob oder Monitoring kann reagieren. Dasselbe Ergebnis hatten wir übrigens bei PostgreSQL mit pg_dump, es ist eine Eigenschaft der Shell, nicht der Datenbank.

Ein halber Dump leert deine Datenbank

Ein Dump, der mittendrin abbricht (Festplatte voll, Verbindung weg, Timeout), sieht auf den ersten Blick wie ein normales Backup aus. Wir haben einen Dump nach zwei Sekunden abgebrochen: 310 MB, die letzte Zeile mitten in einem INSERT. Dann haben wir diesen halben Dump in eine Kopie der Datenbank eingespielt, die vorher vollständig war, 5 Millionen Bestellungen.

Der Import brach mit Exit-Code 1 ab, so weit, so gut. Danach enthielt die Tabelle orders aber 0 Zeilen. Der Dump beginnt für jede Tabelle mit DROP TABLE IF EXISTS, legt sie leer neu an und füllt sie dann in Blöcken innerhalb einer Transaktion. Der Abbruch kam vor dem COMMIT, die Daten des laufenden Blocks wurden verworfen, die Tabelle selbst war aber schon weg.

Zwei Regeln folgen daraus:

  1. Prüfe jeden Dump auf seine letzte Zeile. Ein vollständiger Dump endet mit -- Dump completed on …. Fehlt diese Zeile, ist er unvollständig: tail -n 1 backup.sql | grep -q "Dump completed" || echo "UNVOLLSTÄNDIG". Bei komprimierten Dumps entsprechend mit zstdcat oder zcat.
  2. Spiele zum Testen nie über die Produktivdatenbank ein, sondern in eine neue, leere Datenbank oder einen separaten Server.

—force macht aus Fehlern Erfolge

Spielst du denselben Dump zweimal ein oder kollidieren Daten, bricht der mysql-Client beim ersten Fehler ab. In unserem Test: „Duplicate entry ‘1’ for key ‘PRIMARY’“, Exit-Code 1. Das ist gutes Verhalten, und hier ist MySQL strenger als psql, das ohne Zusatzoption einfach weiterläuft.

Viele Anleitungen empfehlen aber mysql --force, damit der Import „durchläuft“. Mit --force kam derselbe Fehler, und der Exit-Code war 0. Wenn du --force in einem Skript verwendest, sagt dir der Exit-Code nichts mehr. Dann musst du die Fehlerausgabe auswerten, sonst hast du einen Restore, der zur Hälfte geklappt hat.

MariaDB-Dumps passen nicht unbedingt in MySQL

MariaDB und MySQL waren einmal dasselbe Programm, inzwischen haben sie sich deutlich auseinanderentwickelt. Unseren MariaDB-11.8-Dump haben wir testweise in MySQL 8.4 eingespielt. Ergebnis gleich in Zeile 26: „Unknown collation: ‘utf8mb4_uca1400_ai_ci’“, Exit-Code 1. MariaDB nutzt seit 11.x eine Sortierregel, die MySQL nicht kennt. Wer von MariaDB auf MySQL umziehen will (oder umgekehrt), muss die Zeichensätze im Dump anpassen und sollte den Umzug vorher mit einer Kopie durchspielen. Für die Unterschiede zwischen den Systemen lohnt sich ein Blick in unseren Vergleich PostgreSQL vs. MySQL.

Das Passwort gehört nicht in die Befehlszeile

mysqldump -uroot -pGeheim funktioniert, und der Client maskiert das Passwort inzwischen in der Prozessliste (bei uns erschien es als -px xxxx). Trotzdem landet es in der Shell-History, in Cron-Logs und in Skripten, die im Git-Repository liegen. Besser ist eine Optionsdatei, die nur der Backup-Benutzer lesen kann:

# /root/.my.cnf  (chmod 600)
[client]
user=backup
password=EinLangesZufallsPasswort

Der Backup-Benutzer braucht für mysqldump nur Leserechte: GRANT SELECT, SHOW VIEW, TRIGGER, EVENT, LOCK TABLES, PROCESS ON *.* TO 'backup'@'localhost'; (bei MySQL 8 je nach Version zusätzlich RELOAD oder FLUSH_TABLES). Root für Backups zu verwenden ist bequem, aber unnötig.

Physisches Backup mit mariabackup und XtraBackup

Wenn der Dump zu lange dauert oder der Restore zu langsam ist, kommt das physische Backup ins Spiel. Für MariaDB ist das mariabackup (neuer Name mariadb-backup), für MySQL Percona XtraBackup. Beide stammen vom selben Code ab und funktionieren gleich: Datendateien kopieren, Redo-Log mitschneiden, danach „vorbereiten“.

# 1. Backup ziehen (Server läuft weiter)
mariabackup --backup --target-dir=/backup/voll

# 2. Vorbereiten: macht die Kopie konsistent
mariabackup --prepare --target-dir=/backup/voll

# 3. Wiederherstellen: Server stoppen, Datenverzeichnis leeren, zurückkopieren
systemctl stop mariadb
mariabackup --copy-back --target-dir=/backup/voll
chown -R mysql:mysql /var/lib/mysql
systemctl start mariadb

Unsere Messung

Unter derselben Last wie beim Dump (rund 6.300 Buchungen pro Sekunde):

  • Backup: 4,3 Sekunden, Exit-Code 0, „completed OK!“
  • Prepare: 0,23 Sekunden
  • Restore: 3,2 Sekunden, gemessen vom Start eines neuen Servers auf der vorbereiteten Kopie bis zur ersten erfolgreichen Abfrage
  • Kontrollsumme nach dem Restore: exakt 2.000.000, alle 5 Millionen Bestellungen da
  • Die Last lief während des Backups praktisch ungebremst weiter (152.484 Buchungen in 25 Sekunden)

Der Unterschied liegt also nicht beim Backup (4,3 s statt 5,4 s), sondern beim Restore: 3,2 Sekunden statt 43,9 Sekunden. Bei großen Datenbanken ist das der Unterschied zwischen ein paar Minuten und einem halben Tag Ausfall.

Der Preis: Das Backup ist größer. Unsere Kopie belegte 2,1 GB gegenüber 815 MB beim unkomprimierten Dump, weil Indizes und freier Platz in den Tabellendateien mitkopiert werden. Mit --stream=mbstream | zstd kannst du direkt komprimiert schreiben.

Inkrementelle Backups

Physische Backups können inkrementell sein, also nur die Seiten sichern, die sich seit dem letzten Backup geändert haben:

mariabackup --backup --target-dir=/backup/inc1 --incremental-basedir=/backup/voll

Nach zehn Sekunden Last war unser Voll-Backup 1,6 GB groß, das inkrementelle 50 MB, gezogen in rund 1,4 Sekunden. Zum Wiederherstellen wird erst das Voll-Backup vorbereitet und dann jedes Inkrement der Reihe nach angewendet (--prepare --incremental-dir=…). Das klappte bei uns ohne Fehler. Fehlt ein Glied in der Kette, ist alles danach wertlos, deshalb sollte jede Kette regelmäßig mit einem neuen Voll-Backup beginnen.

Die Falle mit den Dateirechten

Bei MySQL 8.4 mit XtraBackup 8.4 im Docker-Container scheiterte unser erster Versuch sofort: „Operating system error number 13“ und „Failed to list redo log files“. Grund: Im XtraBackup-Image hat der Benutzer mysql die UID 1001, im MySQL-Image gehören die Datendateien der UID 999. Das Tool durfte die Dateien schlicht nicht lesen. Als root gestartet lief das Backup durch (3,7 s, 509 MB für die 2-Millionen-Zeilen-Datenbank, Prepare 2,4 s). Auf einem normalen Server ohne Container passiert das selten, in Docker-Setups ständig.

Wichtig bei XtraBackup: Die Version muss zur MySQL-Version passen. Für MySQL 8.4 brauchst du XtraBackup 8.4, für 8.0 XtraBackup 8.0. Bei MariaDB gilt dasselbe: Nimm mariabackup aus derselben Hauptversion wie dein Server. Ein mariabackup aus 11.8.8 konnte unser Backup von 11.8.9 problemlos vorbereiten, bei Sprüngen zwischen Hauptversionen solltest du das nicht erwarten.

Point-in-Time-Recovery mit dem Binlog

Ein tägliches Backup heißt: Im schlimmsten Fall fehlen die Änderungen eines Tages. Und das typische Unglück ist kein Festplattenausfall, sondern ein Mensch oder ein Skript, das die falschen Daten löscht. Dagegen hilft das Binärlog.

Eine leuchtende Zeitleiste aus Blöcken, ein Block rot markiert, ein Rückspulpfeil stoppt kurz davor

Binlog einschalten

Bei MySQL 8 ist das Binlog standardmäßig an, bei MariaDB nicht. In der Konfiguration:

[mysqld]
log_bin = binlog
server_id = 1
binlog_format = ROW
binlog_expire_logs_seconds = 1209600   # 14 Tage

Unser Unfall-Test

Nach dem physischen Backup haben wir weitergearbeitet: zwei neue Bestellungen eingefügt, dann den Unfall gespielt, DELETE FROM orders WHERE status='storniert', was 1.250.000 Zeilen löschte, und danach noch eine Bestellung eingefügt. Ziel: alles zurück, außer dem DELETE.

Das physische Backup notiert selbst, an welcher Stelle im Binlog es steht. Bei uns stand in der Datei mariadb_backup_binlog_info: binlog.000005 339635677. Ab dort spielt man die Binlogs nach. Die Stelle des Unfalls findest du mit mysqlbinlog bzw. mariadb-binlog, indem du nach dem Zeitpunkt oder dem großen Delete_rows-Ereignis suchst. Dann spielst du alles bis kurz davor ab, überspringst die Unglückstransaktion und spielst den Rest ab:

mariadb-binlog --start-position=339635677 binlog.000005 binlog.000006 | mariadb
mariadb-binlog --stop-position=339 binlog.000007 | mariadb
mariadb-binlog --start-position=159398849 binlog.000007 | mariadb

Ergebnis nach 38,8 Sekunden Nachspielen: 5.000.003 Bestellungen, alle 1.250.000 gelöschten wieder da, beide Bestellungen von vor dem Unfall und die von danach ebenfalls. Die Kontosumme stimmte mit 2.000.000, also wurden auch alle Buchungen der Lastprozedur nach dem Backup korrekt nachgespielt.

Statt Positionen kannst du auch --stop-datetime="2026-11-04 08:10:57" verwenden. Das ist bequemer, aber bei vielen Transaktionen pro Sekunde ungenau. Mit GTIDs (bei MySQL 8 empfehlenswert) geht es eleganter über --exclude-gtids.

Binlogs werden größer als die Datenbank

Ein Detail, das viele überrascht: Das einzelne DELETE erzeugte 159 MB Binlog, weil im ROW-Format jede gelöschte Zeile einzeln protokolliert wird. Das Befüllen der Testdatenbank und ein Test-Restore hatten vorher zusammen schon über 2 GB Binlog erzeugt, mehr als doppelt so viel wie die Datenbank selbst. Ohne binlog_expire_logs_seconds läuft dir die Festplatte voll. Und: Binlogs auf derselben Platte wie die Datenbank helfen nicht bei einem Plattendefekt. Für echte Point-in-Time-Recovery müssen sie laufend auf einen anderen Rechner, etwa mit mysqlbinlog --read-from-remote-server --raw --stop-never auf einem Backup-Server.

Schneller dumpen mit MySQL Shell und mydumper

mysqldump arbeitet mit einem einzigen Thread. Für größere Datenbanken gibt es parallele Alternativen. Bei MySQL ist das MySQL Shell mit util.dumpSchemas() bzw. util.dumpInstance(). Wir haben es auf MySQL 8.4 mit unserer 2-Millionen-Zeilen-Tabelle verglichen:

mysqldumpMySQL Shell (8 Threads)
Backup1,8 s, 308 MB (unkomprimiert)unter 1 s, 60 MB (zstd)
Restore17 s5–6 s
Ergebnisidentischidentisch (Summe 300.000.317,84)

Der Restore ist dreimal so schnell, weil MySQL Shell parallel lädt. Ein Haken: Zum Laden muss auf dem Server local_infile eingeschaltet sein, sonst bricht util.loadDump ab. Für MariaDB und MySQL gleichermaßen gibt es mydumper/myloader, ein Open-Source-Werkzeug nach demselben Prinzip. Das alte mysqlpump ist in MySQL 8.4 übrigens entfernt worden.

MySQL Backup automatisieren: ein Skript, das Fehler meldet

Ein Backup, das du von Hand ziehen musst, wird irgendwann vergessen. Hier ein Skript, das alle Fallen von oben berücksichtigt:

#!/usr/bin/env bash
# /usr/local/bin/mysql-backup.sh
set -euo pipefail

ZIEL=/backup/mysql
DATUM=$(date +%F_%H%M)
DB=meinedb
DATEI="$ZIEL/${DB}_${DATUM}.sql.zst"

mkdir -p "$ZIEL"

# Benutzer/Passwort kommen aus /root/.my.cnf
mysqldump --single-transaction --routines --events --triggers \
  "$DB" | zstd -3 -q > "$DATEI.tmp"

# Vollständigkeit prüfen: letzte Zeile muss "Dump completed" sein
zstdcat "$DATEI.tmp" | tail -n 1 | grep -q "Dump completed" \
  || { echo "Dump unvollständig: $DATEI" >&2; exit 1; }

mv "$DATEI.tmp" "$DATEI"

# Benutzer und Rechte separat (MariaDB 10.11+)
mariadb-dump --system=users | zstd -3 -q > "$ZIEL/users_${DATUM}.sql.zst"

# Alte Backups nach 14 Tagen löschen (-mmin ist genauer als -mtime)
find "$ZIEL" -name '*.sql.zst' -mmin +20160 -delete

Ein paar Details, die wichtig sind: set -euo pipefail lässt das Skript bei jedem Fehler abbrechen, auch innerhalb der Pipe. Die Datei wird erst als .tmp geschrieben und nur nach erfolgreicher Prüfung umbenannt, so liegt nie ein halber Dump unter dem richtigen Namen. Und find … -delete steht ganz am Ende, damit es nur läuft, wenn das neue Backup geklappt hat. Wie tückisch die Reihenfolge der Optionen bei find ist, haben wir in unserer Linux-Befehle-Liste gemessen.

Eingeplant wird das Skript per Cronjob, zum Beispiel jede Nacht um 3:15 Uhr:

15 3 * * * /usr/local/bin/mysql-backup.sh || echo "MySQL-Backup fehlgeschlagen" | mail -s "Backup-Fehler" admin@example.org

Wie Cronjobs genau funktionieren und warum sie in einer anderen Umgebung laufen als deine Shell, erklären wir in Cronjob erstellen. Eine Alternative mit besserem Logging ist ein systemd-Timer, siehe systemd-Service erstellen.

Video: HardReset_info Deutsch zeigt Schritt für Schritt ein Backup-Skript mit Cron unter Linux (Februar 2026). Es geht um Dateibackups mit tar, das Prinzip lässt sich direkt auf das Skript oben übertragen.

MySQL Backup in Docker

Läuft deine Datenbank in einem Container, funktioniert alles genauso, nur über docker exec:

docker exec meine-db sh -c 'exec mariadb-dump --single-transaction --routines --events -uroot -p"$MARIADB_ROOT_PASSWORD" meinedb' \
  | zstd -3 -q > meinedb.sql.zst

Das sh -c sorgt dafür, dass die Umgebungsvariable mit dem Passwort im Container ausgewertet wird, nicht auf deinem Host. Für den Restore läuft es umgekehrt mit docker exec -i. Ein Backup des Docker-Volumes mit tar bei laufendem Container ist dagegen dieselbe Lotterie wie cp -r und kein konsistentes Backup. Mehr zu Containern und Volumes in Was ist Docker?

Ein Backup ist erst ein Backup, wenn der Restore geklappt hat

Alle Messungen oben laufen auf denselben Punkt hinaus: Ein Backup-Skript, das Exit-Code 0 meldet, beweist nichts. Die 401-Byte-Datei hatte Exit-Code 0. Der Import mit --force hatte Exit-Code 0. Der Dump ohne Transaktion war fehlerfrei und trotzdem falsch.

Der einzige echte Test ist der Restore. Mach ihn regelmäßig, zum Beispiel einmal im Monat, so:

  1. Neuen leeren Server oder Container starten (gleiche Version wie Produktion).
  2. Das letzte Backup einspielen und die Zeit stoppen.
  3. Zeilenzahlen der wichtigsten Tabellen mit Produktion vergleichen. Kontrollsummen wie CHECKSUM TABLE oder SELECT COUNT(*), SUM(betrag) helfen.
  4. Die Anwendung testweise gegen die wiederhergestellte Datenbank starten. Kann sie sich anmelden? Funktionieren Prozeduren und Trigger?
  5. Die gemessene Restore-Dauer notieren. Das ist deine echte Ausfallzeit im Ernstfall.

Die Dauer ist dabei mindestens so wichtig wie das Ergebnis. Unser Dump brauchte 5 Sekunden für das Backup, aber 44 Sekunden für den Restore. Hochgerechnet auf eine 100-GB-Datenbank wären das über eine Stunde, während der dein Shop nicht läuft. Wenn das zu lang ist, brauchst du ein physisches Backup.

Welche Backup-Strategie passt zu dir?

Drei Kopien einer Datenbank an drei Orten: ein Server, eine externe Festplatte, eine Cloud

Kleines Projekt, Blog, WordPress (bis wenige GB): Täglicher mysqldump --single-transaction --routines --events mit zstd, 14 Tage aufbewahren, Kopie auf einen anderen Rechner. Möglicher Datenverlust: bis zu einem Tag.

Onlineshop oder Anwendung mit zahlenden Kunden: Nächtliches physisches Backup mit mariabackup oder XtraBackup, Binlog eingeschaltet und laufend auf einen zweiten Server gestreamt. Zusätzlich ein wöchentlicher logischer Dump als versionsunabhängige Rückfallebene. Möglicher Datenverlust: Sekunden bis Minuten.

Große Datenbank, schneller Restore gefordert: Physische Voll-Backups plus Inkremente, parallele Dumps mit MySQL Shell oder mydumper für einzelne Datenbanken, Replikat als Standby. Aber: Eine Replikation ist kein Backup. Ein versehentliches DELETE landet in Millisekunden auch auf dem Replikat.

Für alle gilt die 3-2-1-Regel: drei Kopien, zwei verschiedene Medien, eine außer Haus. Ein Backup auf derselben Platte wie die Datenbank schützt vor einem falschen DROP TABLE, aber nicht vor einem Plattendefekt, einem gehackten Server oder einem Brand im Rechenzentrum. Wie du das mit restic umsetzt, zeigen wir in der 3-2-1-Backup-Strategie. Ein RAID ersetzt ebenfalls kein Backup.

Häufig gestellte Fragen

Wie erstelle ich ein MySQL Backup?

Mit mysqldump --single-transaction --routines --events datenbankname > backup.sql. Bei MariaDB funktioniert derselbe Befehl, das Werkzeug heißt dort auch mariadb-dump. Die Option --single-transaction sorgt dafür, dass das Backup konsistent ist und die Datenbank währenddessen weiter benutzt werden kann. Für sehr große Datenbanken ist ein physisches Backup mit mariabackup oder Percona XtraBackup schneller.

Was ist der Unterschied zwischen mysqldump und mariabackup?

mysqldump erzeugt ein logisches Backup als SQL-Text, das sich auf anderen Servern und meist auch anderen Versionen einspielen lässt. mariabackup kopiert die Datendateien physisch. In unserem Test waren beide beim Backup ähnlich schnell (5,4 s und 4,3 s), beim Restore war mariabackup aber über zehnmal schneller (3,2 s statt 43,9 s). Dafür ist das physische Backup größer und an die Version gebunden.

Wie stelle ich ein MySQL Backup wieder her?

Einen Dump spielst du mit mysql datenbankname < backup.sql ein, bei komprimierten Dateien mit zstdcat backup.sql.zst | mysql datenbankname. Die Zieldatenbank muss existieren, außer der Dump wurde mit --databases erstellt. Prüfe vorher, ob der Dump vollständig ist (letzte Zeile „Dump completed“), und spiele zum Testen nie über die Produktivdatenbank ein.

Wie sichere ich alle MySQL-Datenbanken auf einmal?

Mit mysqldump --all-databases --single-transaction --routines --events > alle.sql. Das sichert auch die Systemdatenbank mit den Benutzern, die sich aber nur auf derselben Hauptversion sauber einspielen lässt. Übersichtlicher ist oft eine Schleife, die jede Datenbank einzeln in eine eigene Datei sichert, plus ein separater Export der Benutzer.

Kann ich mysqldump im laufenden Betrieb verwenden?

Ja, mit --single-transaction. In unserem Test mit rund 6.300 Schreibtransaktionen pro Sekunde stimmten alle Kontrollsummen im Dump exakt. Ohne diese Option sperrt mysqldump standardmäßig die Tabellen: Ein einzelnes Update wartete dann 2,8 Sekunden statt 0,09 Sekunden. Mit --skip-lock-tables ohne Transaktion war der Dump inkonsistent.

Sind Benutzer und Passwörter im mysqldump enthalten?

Nicht, wenn du nur eine bestimmte Datenbank sicherst. Benutzer liegen in der Systemdatenbank mysql. Bei MariaDB sicherst du sie mit mariadb-dump --system=users, bei MySQL mit MySQL Shell (util.dumpUsers) oder über SHOW GRANTS. Ohne diesen Schritt kann sich deine Anwendung nach einem Umzug nicht anmelden.

Wie oft sollte ich ein MySQL Backup machen?

So oft, wie du Datenverlust verkraften kannst. Ein tägliches Backup heißt, dass im schlimmsten Fall die Änderungen eines Tages fehlen. Wenn das für dich zu viel ist, brauchst du das Binlog, das laufend auf einen anderen Server kopiert wird. Damit kannst du bis auf Sekunden genau wiederherstellen.

Was ist Point-in-Time-Recovery bei MySQL?

Point-in-Time-Recovery stellt die Datenbank auf einen bestimmten Zeitpunkt wieder her. Dafür brauchst du ein Voll-Backup und alle Binlogs seitdem. Du spielst das Backup ein und dann die Änderungen aus dem Binlog bis kurz vor dem Unglück. In unserem Test haben wir so 1.250.000 versehentlich gelöschte Zeilen zurückgeholt, ohne die Änderungen davor und danach zu verlieren.

Kann ich einen MariaDB-Dump in MySQL einspielen?

Nicht unbedingt. Unser Dump aus MariaDB 11.8 scheiterte in MySQL 8.4 an der Sortierregel utf8mb4_uca1400_ai_ci, die MySQL nicht kennt. Für einen Umzug zwischen den Systemen musst du Zeichensätze und teils Syntax anpassen und solltest das vorher mit einer Kopie testen.

Wie komprimiere ich ein MySQL Backup am besten?

Mit zstd: mysqldump … | zstd -3 > backup.sql.zst. In unserem Test schrumpfte der Dump von 815 MB auf 163 MB, ohne dass das Backup länger dauerte. gzip kam auf 187 MB und verdreifachte die Laufzeit, weil es nur einen CPU-Kern nutzt. Wichtig ist in beiden Fällen set -o pipefail, sonst bemerkt dein Skript Fehler von mysqldump nicht.

Ist eine Replikation ein Backup?

Nein. Ein Replikat übernimmt jede Änderung innerhalb von Millisekunden, also auch ein versehentliches DELETE oder ein DROP TABLE. Es schützt vor Hardwareausfall, nicht vor Fehlern. Dafür brauchst du zusätzlich echte Backups, am besten mit Binlog für Point-in-Time-Recovery.

Fazit: Der Befehl ist leicht, die Prüfung ist die Arbeit

Ein MySQL Backup ist ein Einzeiler. mysqldump --single-transaction --routines --events mit zstd ist für die meisten Projekte genau richtig: schnell, konsistent, klein und auf fast jedem Server einspielbar. Wird die Datenbank groß oder muss der Restore schnell gehen, kommen mariabackup oder XtraBackup dazu, und mit dem Binlog kannst du auf die Sekunde zurückspringen.

Die eigentliche Arbeit liegt woanders, und unsere Messungen zeigen das deutlicher als jede Theorie: eine Pipe, die eine 401-Byte-Datei als Erfolg meldet. Ein Dump ohne Transaktion, in dem 33.000 Euro auftauchen, die es nie gab. Ein halber Dump, der eine volle Tabelle leert. --force, das aus jedem Fehler einen Erfolg macht. Nichts davon weckt dich nachts mit einer Fehlermeldung.

Deshalb: set -o pipefail, --single-transaction, die letzte Zeile prüfen, Benutzer separat sichern, eine Kopie außer Haus. Und vor allem: einmal im Monat einen echten Restore machen und die Zeit stoppen. Ein Backup, das du nie wiederhergestellt hast, ist kein Backup, sondern eine Hoffnung.