MySQL · lesson 8 of 8
Intermediate
6- 2 Composite Indexes: The Filesort a Single-Column Index Can't Avoid
- 3 A Missing Join Condition Turns 4,000 Rows Into 8 Million
- 4 Isolation Levels: The Same Transaction Reading Two Different Values
- 5 A Real Deadlock: Error 1213, and the Lock-Order Fix
- 6 Stored Procedures and Triggers: Code That Runs Without Being Asked
- 8 Backup and Recovery: What a Restore Actually Gives Back
Also part of MySQL
1Also part of: Databases
Backup and Recovery: What a Restore Actually Gives Back
A real mysqldump, a real DELETE with no WHERE clause, and a real restore — which undoes the mistake but also erases legitimate work done after the backup, exposing exactly what a full logical restore can't do alone.
By SkillByExample · Published
mysqldump is one command. What it actually restores — and what it quietly does not — only
shows up once a real mistake and a real restore happen back to back.
A real backup, with its exact position recorded
$ mysqldump -uroot -proot --single-transaction --source-data=2 bookshop > bookshop_backup.sql
-- inside bookshop_backup.sql
CHANGE REPLICATION SOURCE TO SOURCE_LOG_FILE='binlog.000002', SOURCE_LOG_POS=14580;
--source-data=2 writes that line as a comment — it’s the binary log coordinate that
corresponds exactly to this dump’s snapshot, captured automatically rather than read off a
status command by hand.
A real mistake
DELETE FROM accounts;
SELECT COUNT(*) FROM accounts;
COUNT(*)
0
No WHERE clause, no error, no confirmation prompt — three rows gone in one statement, exactly
the kind of accident a backup exists for.
The restore — and what it doesn’t give back
$ mysql -uroot -proot -e "DROP DATABASE bookshop; CREATE DATABASE bookshop;"
$ mysql -uroot -proot bookshop < bookshop_backup.sql
SELECT * FROM accounts;
SELECT * FROM books;
id name balance
1 Alice 500.00
2 Bob 300.00
3 Carol 1000.00
id title genre stock
1 Dune SciFi 45
2 Foundation SciFi 30
3 Neuromancer SciFi 116
accounts is back — the mistake is undone. But between taking this backup and running the
DELETE, a real row had been legitimately added to books: a fourth title, Snow Crash. It
is not in this result. The restore didn’t distinguish “the row I need back” from “the row I
didn’t” — it replaced the whole database with exactly what existed at dump time, so anything
written after the dump and before the restore is gone regardless of whether it was the mistake
or not.
What actually closes that gap
The dump’s recorded position — binlog.000002 at 14580 — is exactly where a point-in-time
recovery would start. The real command is:
$ mysqlbinlog --start-position=14580 --stop-position=<position just before the mistake> \
binlog.000002 | mysql -uroot -proot bookshop
That replays every change recorded between the backup and the mistake — including the
Snow Crash insert — while stopping short of the DELETE itself. This lesson documents that
command rather than running it: the mysql:8.4 image used to build every example on this site
does not include the mysqlbinlog binary (mysqldump is present at /usr/bin/mysqldump;
mysqlbinlog is not, and installing MySQL’s client package inside the container found no
matching package in the image’s configured repositories). A full server install or MySQL’s own
client-tools package provides it in a normal deployment.
Practice
1. Confirm the dump file recorded a binary log position rather than requiring you to look it up separately.
$ grep CHANGE bookshop_backup.sql
CHANGE REPLICATION SOURCE TO SOURCE_LOG_FILE='binlog.000002', SOURCE_LOG_POS=14580;
The coordinate travels with the backup file itself — anyone restoring it later has exactly what a point-in-time recovery needs, without depending on separately-kept notes from when the dump was taken.
2. Take a schema-only backup of books and check what it captures without any row data.
$ mysqldump -uroot -proot --no-data bookshop books > books_schema_only.sql
CREATE TABLE `books` (
`id` int NOT NULL AUTO_INCREMENT,
`title` varchar(200) NOT NULL,
`genre` varchar(50) NOT NULL,
`stock` int NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
Structure, types, and the current AUTO_INCREMENT counter are all present — zero rows are.
AUTO_INCREMENT=4 here reflects that ids 1 through 3 were already used at dump time, which is
metadata about the table, not about any specific row.
3. Explain why restoring dropped and recreated the whole bookshop database instead of restoring only the accounts table.
The mistake was scoped to one table, but mysqldump bookshop (with no table name) captured
every table in the database, and restoring it replays every CREATE TABLE and INSERT
statement in the file — there’s no “restore just the part that changed” step in a plain SQL
dump. Restoring only accounts would have meant dumping and restoring that single table
specifically (mysqldump bookshop accounts), which would have left books, order_items,
and stock_log completely untouched instead of being reset to their dump-time state as a side
effect.
This closes out the practical MySQL series: measuring what a query actually does with
EXPLAIN, indexing to change what it does, joins that silently multiply instead of matching,
transaction isolation, locking and deadlocks, procedures and triggers that run without being
asked, replication, and now the backup that gives some of it back — and the real gap this
lesson exposed in doing so.