MariaDB / MySQL Guide

mysqldump / mariadb-dump: back up and restore a MariaDB or MySQL database

The options to always use, the commands to export one database, a few tables or the whole server, restoring, and the errors everyone hits sooner or later. A practical guide, written for production.

The one command to remember

If you keep only one line, keep this one. It produces a consistent dump of an InnoDB database without blocking writes, includes stored procedures and events, and limits the risk of mangling binary data or non-ASCII characters.

mariadb-dump --single-transaction --routines --events \
  --default-character-set=utf8mb4 --hex-blob \
  mydb > mydb-$(date +%F).sql

On MariaDB, use mariadb-dump: the old mysqldump name is deprecated since MariaDB 11.0 and missing from the official Docker image, so whether it exists depends on the installed packages. On MySQL, replace mariadb-dump with mysqldump and mariadb with mysql. The difference between the two binaries is covered in mariadb-dump vs mydumper.

The options that matter

--single-transaction

Opens a consistent-read transaction: InnoDB tables are exported as they were when the dump started, without locking. No ALTER TABLE, CREATE TABLE, DROP TABLE, RENAME TABLE or TRUNCATE TABLE should run meanwhile. MyISAM and Aria tables are not covered: they are neither locked nor consistent, and need an explicit locking strategy.

--routines --events

Procedures, functions and scheduled events are not exported by default. Triggers are. A dump without these options restores the tables, but not always the application.

--hex-blob

Writes binary columns (BLOB, BINARY, VARBINARY, BIT…) in hexadecimal. Prevents corruption when the file goes through an editor, an encoding change or a misconfigured transfer.

--default-character-set=utf8mb4

Forces the connection character set. Without it, a differently configured client can turn emojis and some characters into question marks, with no visible error.

--quick

Reads rows one at a time instead of loading the table into memory. Already on by default through --opt: no need to add it, but don't turn it off.

--master-data=2 --gtid (MariaDB) / --source-data=2 (MySQL 8.0.26+)

Writes the binlog position (and the GTID on MariaDB) as a comment at dump time. Essential to build a replica or do a point-in-time restore with binlogs. Combined with --single-transaction, it takes a global lock at startup, normally brief but which waits for long-running queries to finish: while it waits, all writes are blocked.

Exporting: common cases

One database

mariadb-dump --single-transaction --routines --events mydb > mydb.sql

A few tables from a database

mariadb-dump --single-transaction mydb customers orders > customers-orders.sql

Schema only, no data

mariadb-dump --no-data --routines --events mydb > mydb-schema.sql

Every database on the server

mariadb-dump --single-transaction --routines --events --all-databases > all.sql

With replication coordinates (MariaDB)

# MariaDB: binlog position + GTID written as a header comment
mariadb-dump --single-transaction --master-data=2 --gtid --all-databases > all.sql

With replication coordinates (MySQL)

# MySQL 8.0.26+: --source-data replaces --master-data
mysqldump --single-transaction --source-data=2 --all-databases > all.sql

With --databases or --all-databases, the dump contains CREATE DATABASE and USE statements: it restores without naming a database. With a bare database name, you must create the target database and name it on restore.

Compressing and shipping elsewhere

SQL dumps compress very well. Compress on the fly rather than afterwards: the uncompressed file never lands on the server's disk.

On-the-fly zstd compression

mariadb-dump --single-transaction --routines --events mydb \
  | zstd -T0 > mydb-$(date +%F).sql.zst

Straight to a backup server, no local file

mariadb-dump --single-transaction --routines --events mydb \
  | zstd -T0 | ssh backup@srv-backup "cat > /backups/mydb-$(date +%F).sql.zst"

Copy a database to another server in a single stream

mariadb-dump --single-transaction --routines --events --databases mydb \
  | ssh admin@new-server "mariadb"

Restoring a dump

Simple restore (the target database must exist)

mariadb mydb < mydb.sql

From a compressed file

zstd -dc mydb-2026-09-28.sql.zst | mariadb mydb
# or, for a gzip file:
zcat mydb.sql.gz | mariadb mydb

One database from an --all-databases dump

# Filters on the current database (USE mydb): fine for standard dumps,
# not a strict isolation (a statement naming another database still runs)
mariadb --one-database mydb < all.sql

A single table, through a scratch database

# Restore the whole dump into a scratch database (never into production),
# then re-export only the table you need
mariadb -e "CREATE DATABASE mydb_restore"
mariadb mydb_restore < mydb.sql
mariadb-dump --single-transaction mydb_restore customers > customers.sql

Never replay a dump straight into production to recover one table: its DROP TABLE overwrites the current table. Restore into a scratch database, then copy back the rows you need. Carving the table out of the file with sed is tempting but unreliable (lost header, trailing routines if it's the last table). If you often restore single tables, dump them separately or move to mydumper, which writes one file per table.

Speeding up an import

The dump header already disables unique and foreign key checks: no need to do it by hand. What really slows an import down is single-threaded replay and index rebuilding.

  • Temporarily relax durability with innodb_flush_log_at_trx_commit = 2, and set it back to 1 right after. Only on a server where a crash during the import doesn't matter: worst case, you restart the import.
  • Size innodb_buffer_pool_size and the redo log for the import's write load, not the usual workload.
  • If the target is a primary with replicas and the import must not be replicated, run SET sql_log_bin = 0 in the import session. Otherwise, check the dump header: a MySQL dump with GTIDs itself contains SET @@SESSION.sql_log_bin=0, which --set-gtid-purged=OFF avoids when the import must be replicated.
  • When the restore takes longer than your recovery objective allows, the real lever is a different tool: parallel restore with mydumper / myloader, or a physical backup with mariabackup.
-- For the duration of the import only, then back to 1
SET GLOBAL innodb_flush_log_at_trx_commit = 2;
-- ... import ...
SET GLOBAL innodb_flush_log_at_trx_commit = 1;

Common errors

ERROR at line 1: Unknown command '\-'

Since MariaDB 10.5.25, 10.6.18, 10.11.8 and 11.4.2 (May 2024 security fix), mariadb-dump writes /*!999999\- enable the sandbox mode */ as its first line. An older MariaDB client or MySQL's mysql client rejects it. Restore with a recent MariaDB client, or strip the line: tail -n +2 dump.sql | mysql db.

Unknown table 'COLUMN_STATISTICS' in information_schema (1109)

You're using MySQL 8's mysqldump against a MariaDB server (or an older MySQL). Use the MariaDB client, or add --column-statistics=0.

Access denied; you need (at least one of) the PROCESS privilege(s)

Since MySQL 8.0.21, mysqldump needs the PROCESS privilege to dump tablespaces. Add --no-tablespaces if you don't need them (the usual case), or grant the privilege to the backup account.

Warning: A partial dump from a server that has GTIDs…

On MySQL with GTIDs, the dump includes a SET @@GLOBAL.gtid_purged covering the whole server, even for a partial dump. For a plain data copy to a server that isn't a replica, use --set-gtid-purged=OFF.

The user specified as a definer ('x'@'y') does not exist (1449)

Views, triggers and procedures keep their original DEFINER. Create that account on the target server, or strip the DEFINER clauses from the dump before replaying it (command below). This sed is purely textual: it would also alter data containing the pattern, and recreated objects will belong to the account restoring them.

Got a packet bigger than 'max_allowed_packet' bytes (1153)

A statement in the dump (often a multi-row INSERT, or a row with a large BLOB) exceeds the accepted packet size. Raise max_allowed_packet on the server and on the client (mariadb --max-allowed-packet=512M).

Strip DEFINER clauses from a dump

sed -E 's/DEFINER=`[^`]+`@`[^`]+`//g' mydb.sql > mydb-no-definer.sql

Automating: a backup script with retention

One database per file, on-the-fly compression, dumps 14 days old or more purged, only when every backup succeeded. Credentials live in an option file readable by root only, never on the command line (visible in the process list).

# /root/.my.cnf
[client]
user=backup
password=********
#!/usr/bin/env bash
# /usr/local/bin/backup-mariadb.sh — credentials read from /root/.my.cnf (chmod 600)
set -euo pipefail

DEST=/backups/mariadb
DATE=$(date +%F-%H%M)
mkdir -p "$DEST"

# List the databases first: stop if the query fails or returns nothing
DBS=$(mariadb -N -B -e "SHOW DATABASES" \
  | grep -Ev '^(information_schema|performance_schema|sys)$')
[ -n "$DBS" ] || { echo "no database to dump" >&2; exit 1; }

while IFS= read -r DB; do
  OUT="$DEST/$DB-$DATE.sql.zst"
  if ! mariadb-dump --single-transaction --routines --events \
      --default-character-set=utf8mb4 --hex-blob "$DB" \
      | zstd -T0 -q > "$OUT"; then
    rm -f "$OUT"; echo "dump failed: $DB" >&2; exit 1
  fi
done <<< "$DBS"

# Only reached when every dump succeeded.
# -mtime +13 = files 14 full days old or more
find "$DEST" -name '*.sql.zst' -mtime +13 -delete

set -o pipefail is essential: without it, a dump that fails halfway goes unnoticed, because the exit code is that of zstd, which succeeded. As for privileges, the backup account needs SELECT, SHOW VIEW, TRIGGER, EVENT and LOCK TABLES (TRIGGER and EVENT also allow changing those objects: it's not a purely read-only account), SELECT on mysql.proc for --routines on MariaDB, and more depending on options: RELOAD plus BINLOG MONITOR (MariaDB) or REPLICATION CLIENT (MySQL) for --master-data, PROCESS on MySQL 8.0.21+ (or --no-tablespaces), RELOAD or FLUSH_TABLES on MySQL 8.0.32+ with GTIDs and --single-transaction.

And keep a copy off the server: a dump stored on the database's own disk disappears with it.

Test the restore, and know how long it takes

A dump that has never been reloaded is not a backup. Restore regularly onto an isolated instance, compare row counts on the main tables, run a few application queries. You'll know whether the file is usable and, above all, how long it takes to bring it back online.

That time grows fast with database size, because every INSERT is replayed and every index rebuilt. We give orders of magnitude by size, and explain when to switch to physical backups, in mysqldump vs mariabackup: real restore times.

Frequently asked questions

How do I export a MySQL or MariaDB database from the command line?

mariadb-dump --single-transaction --routines --events db_name > db_name.sql (or mysqldump on MySQL). --single-transaction guarantees a consistent export of InnoDB tables without blocking writes.

How do I import a SQL dump?

mariadb db_name < db_name.sql (or mysql on MySQL), the target database must exist. For a compressed file: zcat file.sql.gz | mariadb db_name.

Does mysqldump lock the database during export?

Not with --single-transaction on InnoDB tables: writes continue. Without that option, --lock-tables (on by default) locks tables database by database. With --single-transaction, MyISAM tables are not locked, but their consistency isn't guaranteed.

Why are my stored procedures missing from the dump?

Procedures, functions and events are not exported by default. Add --routines and --events. Triggers are included by default.

Can I restore a single database from a full-server dump?

Yes, with mariadb --one-database db_name < all.sql: the client only replays statements run while the current database (USE) is db_name. Enough for a standard dump, but not a strict isolation.

Do your dumps actually restore?

A MariaDB audit includes a review of your backup strategy and a measured restore test.

Start your managed MariaDB project

Let's discuss your database needs. Our DBA team advises you on the optimal architecture for your use case.

RDEM Systems SAS — SIREN 820 338 671 — 5 B rue des Noyers, 95300 Pontoise