MySQL backups for small teams: mysqldump, MySQL Shell and point-in-time recovery
A practical backup plan for MySQL 8.4 and 9.7: when to use mysqldump or MySQL Shell's parallel dump and load utilities, how to keep binary logs for point-in-time recovery, and a checklist for testing restores.
Updated 8 min read

In this article
Most small teams discover the state of their MySQL backups on the worst possible day: a migration dropped a column, a bug deleted rows, or a disk failed. At that point the questions are simple. Do we have a copy? How old is it? How long will a restore take? Can we get back to five minutes before the mistake rather than last night?
This guide answers those questions for a typical application database of a few gigabytes to a few hundred gigabytes running MySQL 8.4 LTS or 9.7 LTS. It compares the three tools that ship with MySQL Community (mysqldump, the MySQL Shell dump and load utilities, and the binary log), gives working commands for each, and ends with a checklist for testing restores, which is the part most backup plans skip. Commands and defaults were checked in the MySQL 9.7 Reference Manual and MySQL Shell manual in October 2026.
Decide what you need to recover from
Before choosing tools, write down two numbers for the database:
- Recovery point objective (RPO): how much data you can afford to lose. "Last night's backup" means up to 24 hours of orders, sign-ups or edits.
- Recovery time objective (RTO): how long the application can be down while you restore.
A nightly dump alone gives an RPO of a day. Adding the binary log lets you replay changes up to a chosen moment, which brings RPO down to minutes. RTO depends mostly on restore speed, which is where the tools differ most.
| Tool | Type | Strengths | Limits |
|---|---|---|---|
mysqldump | Logical (SQL statements) | Everywhere, simple, one portable file, easy to inspect | Single-threaded; the manual says it is not intended as a fast or scalable solution for large data, and restores can be very slow |
MySQL Shell util.dumpInstance / dumpSchemas / dumpTables and util.loadDump | Logical (chunked, compressed files) | Parallel dump and load (4 threads by default), zstd compression, resumable loads, cloud storage targets | Needs MySQL Shell; output is a directory, not one file; loading needs local_infile on the target |
Binary log + mysqlbinlog | Change log | Point-in-time recovery between full backups | Not a backup on its own; must be copied off the server and kept at least as long as your oldest full backup |
Physical backup tools (MySQL Enterprise Backup, file-system or volume snapshots, and managed-service snapshots) are faster for very large databases but are outside the scope of this guide. If you use a managed MySQL service, its automated backups and point-in-time restore are usually the first line of defence; a periodic logical dump you control is still a useful second copy.
Option 1: mysqldump for small databases
For InnoDB tables, the essential flag is --single-transaction. It starts a transaction before dumping, so you get a consistent snapshot without locking tables while the application keeps writing. The manual's one caveat: do not run DDL (ALTER, CREATE, DROP, RENAME, TRUNCATE) on the tables while the dump runs, so schedule dumps away from deployments that migrate the schema.
mysqldump \
--single-transaction \
--routines --triggers --events \
--set-gtid-purged=AUTO \
--source-data=2 \
--databases app \
| zstd -q -o /backups/app-$(date +%Y%m%d-%H%M).sql.zstWhat each option contributes:
--routinesand--eventsinclude stored routines and scheduled events; triggers are included by default, and--triggersjust makes that explicit.--source-data=2writes the binary log coordinates as a comment at the top of the dump. You need them to start point-in-time recovery from the right position. (It replaces the deprecated--master-data.)--set-gtid-purged=AUTOincludes the GTID state if GTIDs are enabled.
Use a dedicated backup account with only the privileges needed (SELECT, SHOW VIEW, TRIGGER, plus PROCESS unless you pass --no-tablespaces, and RELOAD or FLUSH_TABLES in some GTID configurations), and keep its password in an option file readable only by the backup user rather than on the command line. On Windows PowerShell, use --result-file instead of > redirection, which would produce a UTF-16 file MySQL cannot load.
To restore into an empty server:
zstd -dc /backups/app-20261007-0200.sql.zst | mysql --defaults-extra-file=/etc/mysql/restore.cnf
Option 2: MySQL Shell dump and load utilities
Once a dump takes more than a few minutes or a restore threatens your RTO, MySQL Shell's utilities are the better logical option. They split tables into chunks (about 64 MB each by default), dump and load them in parallel, compress with zstd by default and write a directory of DDL, data and metadata files. Use the MySQL Shell release that matches or is newer than your server; the current Shell manual is for 26.7.
// In mysqlsh, connected to the source server (JavaScript mode)
util.dumpSchemas(["app"], "/backups/app-20261007", {
threads: 4,
consistent: true, // default: consistent snapshot across threads
showProgress: true
})
// For every schema plus user accounts and grants:
util.dumpInstance("/backups/instance-20261007", { threads: 8 })Run the dump with {dryRun: true} first to see what would be dumped and catch compatibility problems without writing anything.
Loading uses util.loadDump(). It relies on LOAD DATA LOCAL INFILE, so the target server's global local_infile setting must be ON for the duration of the import; switch it off again afterwards.
// On the target server
\sql SET GLOBAL local_infile = ON;
\js
util.loadDump("/backups/app-20261007", { threads: 8, dryRun: true }) // check first
util.loadDump("/backups/app-20261007", { threads: 8 })
\sql SET GLOBAL local_infile = OFF;Two behaviours worth knowing. Loads are resumable: progress is recorded in a load-progress.<server_uuid>.json file in the dump directory, so rerunning the same command after an interruption skips completed steps. And users are not loaded by default; pass loadUsers: true when restoring an instance dump that should recreate accounts.
Option 3: the binary log for point-in-time recovery
Binary logging is enabled by default in current MySQL releases (log_bin is ON) and uses row-based format by default. The binary log records every change, so a full backup plus the binary logs written after it lets you replay the database to any point in between, typically to just before a destructive statement.
Two settings decide whether that works in practice:
- Retention.
binlog_expire_logs_secondsdefaults to 2,592,000 seconds (30 days). Binary logs must cover at least the interval back to your oldest full backup that you would restore from. - Location. Logs that live only on the database server disappear with it. Copy closed binary log files to separate storage regularly, for example every few minutes, or stream them live from another host with
mysqlbinlog --read-from-remote-server --host=db1 --raw --stop-never binlog.000130. That process does not reconnect on its own after a server restart or network outage, so run it under a supervisor that restarts it.
A point-in-time recovery walkthrough
Suppose a bad deployment ran DROP TABLE invoices at about 14:06. Restore last night's full backup to a new server first. Then find the exact position of the destructive event. The manual recommends using the date-time options only to locate the event, and positions to apply the log:
# 1. Locate the event (date-times only to narrow the search)
mysqlbinlog --start-datetime="2026-10-07 14:00:00" \
--stop-datetime="2026-10-07 14:10:00" --verbose \
/var/lib/mysql/binlog.000123 | grep -C 15 "DROP TABLE"
# 2. Replay from the dump's recorded position up to just before the DROP
mysqlbinlog --start-position=<pos_from_dump> --stop-position=<pos_before_drop> \
/var/lib/mysql/binlog.000123 | mysql -u root -p
# 3. Optionally replay everything after the DROP event
mysqlbinlog --start-position=<pos_after_drop> /var/lib/mysql/binlog.000123 \
| mysql -u root -pWhen several binary log files are involved, pass them all to a single mysqlbinlog invocation piped into one mysql connection, as the manual shows, rather than one connection per file. Do not rely on the binary log position InnoDB prints after a restore; the manual warns it can miss DDL and non-InnoDB changes.

Storage, retention and security
- Keep copies off the server and off the account. A backup in the same cloud account as the database shares its failure and compromise risks. Store at least one copy in a separate account or provider, with deletion protection or object lock where available.
- Encrypt backups at rest and in transit; a dump contains every customer record and password hash in the database.
- Retain in layers, for example daily backups for two weeks, weekly for two months and monthly for a year, adjusted to your legal and contractual obligations.
- Back up configuration too: the server option file, user accounts and grants (
util.dumpInstanceincludes users by default), and the exact server version. - Monitor the job: alert when a backup fails, when its size changes sharply, or when the newest backup is older than expected.
Restore-testing checklist
A backup that has never been restored is a hope, not a backup. Run this at least monthly, and after any change to the backup job:
- Provision a clean server with the same MySQL version and configuration as production.
- Restore the most recent full backup and record how long it took. Compare it with your RTO.
- Replay binary logs to a chosen timestamp and confirm a known recent change is present.
- Check row counts on key tables against production at the backup's time.
- Run
CHECK TABLEon the largest tables, and start the application against the restored copy to confirm it works. - Confirm users, grants, routines, triggers and events came back.
- Write down the exact commands used, so the person on call during an incident does not have to work them out.
- Destroy the test server and its data afterwards; it holds production data.
Summary
For a small database, a nightly mysqldump --single-transaction plus copied binary logs gives you minutes of RPO with very little machinery. As data grows, move the full backup to MySQL Shell's parallel dump and load utilities to keep restores within your RTO. Whatever you choose, keep copies off the server, encrypt them and, above all, rehearse the restore on a schedule.
Frequently asked questions
Is mysqldump safe to run while the application is writing?
For InnoDB tables, yes, with
--single-transaction: it dumps a consistent snapshot without locking tables. Avoid schema changes while it runs.How long are MySQL binary logs kept?
By default
binlog_expire_logs_secondsis 2,592,000 seconds, or 30 days. Make sure the retention covers the gap back to the oldest full backup you would restore from, and copy logs off the server.Why does util.loadDump fail with a local_infile error?
The loader uses
LOAD DATA LOCAL INFILE, so the target server's globallocal_infilemust beONduring the import. Turn it off again afterwards.Does util.loadDump restore user accounts?
Not by default. Set
loadUsers: truewhen loading an instance dump that includes users and grants.
Your next customer could start right here.
Introduce your tool, product or business to readers exploring technology.
Explore placements and request a private quote
Sources
- MySQL 9.7 Reference Manual: mysqldump — Oracle (MySQL documentation)
- MySQL Shell manual: Instance, Schema and Table Dump Utilities — Oracle (MySQL documentation)
- MySQL Shell manual: Dump Loading Utility — Oracle (MySQL documentation)
- MySQL 9.7 Reference Manual: Point-in-Time Recovery Using Binary Log — Oracle (MySQL documentation)
- MySQL 9.7 Reference Manual: Point-in-Time Recovery Using Event Positions — Oracle (MySQL documentation)
- MySQL 9.7 Reference Manual: Using mysqlbinlog to Back Up Binary Log Files — Oracle (MySQL documentation)
- MySQL 9.7 Reference Manual: Binary Logging Options and Variables — Oracle (MySQL documentation)
Written with substantial AI assistance. Facts checked against cited sources. Editorial policy.
Comments
No comments yet. Be the first to join the conversation.
Leave a comment
Every comment is read by a moderator before it appears. Your email address is never published; it is used only to contact you about this comment.
Related tools
- Directory profile
Datadog
Monitoring and observability platform for infrastructure, application performance, logs and errors, with 1,000+ integrations and a free tier for up to five hosts.
A tool connected to a service selected for this article. Compare its fit for your project; inclusion is not an endorsement.
- Directory profile
Snyk
Developer security platform that scans open-source dependencies, code, containers and infrastructure as code for vulnerabilities.
A tool connected to a service selected for this article. Compare its fit for your project; inclusion is not an endorsement.
More from Cloudsoft Zone

ComparisonMySQL
MySQL 8.4 LTS, 9.7 LTS or 26.7 Innovation: choosing a version and upgrading
MySQL now ships LTS and Innovation releases with calendar version numbers. Compare 8.4 LTS, 9.7 LTS and 26.7 Innovation, see the supported upgrade paths, and follow a checklist for moving an application from 8.4 to 9.7.
By Cloudsoft Zone

Resource guideWordPress
WordPress security hardening checklist for 2026 (WordPress 7.1)
A practical WordPress 7.1 hardening checklist based on the official handbook: updates, two-step logins, wp-config.php constants, file permissions, database privileges, firewalls, WP-CLI checksum checks and tested backups.
By Cloudsoft Zone

TutorialWordPress
How to update WordPress safely with WP-CLI: backup, update, verify, roll back
A repeatable WordPress update routine with WP-CLI: check pending updates, export the database and archive files, update core, plugins, themes and translations in order, verify checksums, and restore if something breaks.
By Cloudsoft Zone
