A dump is a text file of SQL statements. Whether it restores depends on how it was written, and the defaults are not the right ones for a live site. Most import failures are decided at dump time, weeks earlier.

The command

mysqldump \
  --single-transaction \
  --quick \
  --routines --triggers --events \
  --default-character-set=utf8mb4 \
  --set-gtid-purged=OFF \
  dbname | gzip > db-$(date +\%F).sql.gz

What each one prevents

  • --single-transaction - a consistent snapshot with no table locks. Without it, a busy site is frozen for the length of the dump.
  • --quick - streams row by row instead of buffering a large table into memory.
  • --routines --triggers --events - not included by default. A restore without them looks complete and quietly loses every stored procedure and every scheduled event.
  • --default-character-set=utf8mb4 - Arabic and emoji survive the round trip.
--single-transaction is consistent for InnoDB only. A MyISAM table in the same database is dumped without that guarantee, so convert them or accept an inconsistent copy of those tables.

Verify the file, not the exit code

gzip -t db-2026-09-05.sql.gz               # the archive is intact
zcat db-2026-09-05.sql.gz | tail -5       # ends with "Dump completed"
zcat db-2026-09-05.sql.gz | grep -c "CREATE TABLE"

A dump cut off by a disk filling up is still a valid gzip for most of its length. The completion line at the end is the proof that mysqldump reached the end - and it costs one command to check.

Restore it somewhere else, on a schedule

mysql -e "CREATE DATABASE restore_test CHARACTER SET utf8mb4"
zcat db-2026-09-05.sql.gz | mysql restore_test
mysql restore_test -e "SELECT COUNT(*) FROM orders"
Compare that row count against production. A dump that imports without error and arrives with half the rows is the failure nobody catches, because the import said nothing at all.