Restoring a MySQL database to a specific point in time is a surprisingly delicate dance, and it’s not about simply picking a date and time from a calendar. The real trick is understanding that you’re not restoring a backup; you’re replaying transactions on top of a full backup.
Let’s say you’ve got a critical production database and a rogue DELETE statement just wiped out your entire users table. You need to get that table back, but you can’t afford to lose the transactions that happened after your last full backup and before the disaster.
Here’s a typical scenario:
The Setup:
- Full Backup:
backup_full.sqltaken at2023-10-27 08:00:00. - Binary Logs (Binlogs): MySQL has been actively writing transaction logs since the full backup. These are named sequentially, e.g.,
mysql-bin.000001,mysql-bin.000002, etc. - Disaster:
DELETE FROM users;executed at2023-10-27 10:35:15. - Target Restore Time:
2023-10-27 10:35:00(just before the badDELETE).
The Process:
-
Ensure Binlogs are Enabled: This is non-negotiable for point-in-time recovery. If they weren’t enabled, you can only restore to the exact moment of your last full backup.
- Diagnosis: Check your
my.cnformy.inifile forlog_binandbinlog_format. - Fix: If
log_binis commented out or not present, add/uncommentlog_bin = /var/log/mysql/mysql-bin.logand setbinlog_format = ROW(ROW is generally preferred for PITR). Restart MySQL. - Why it works: The
log_bindirective tells MySQL to record every committed transaction in sequentially named files.binlog_format=ROWensures that each row affected by a transaction is logged, making it precise for replaying.
- Diagnosis: Check your
-
Identify the Correct Binlog Files and Position: You need to know which binlog files contain the transactions between your full backup and your target restore time.
-
Diagnosis:
- First, find the binary log file and position corresponding to your full backup. When
mysqldumpcreates a backup, it typically outputs the binlog position it was at. If you didn’t capture this, you’ll need to look at the binlog files themselves. - Let’s assume your full backup was taken at
2023-10-27 08:00:00and yourmysql-bin.logfiles start frommysql-bin.000001. - Use
mysqlbinlogto inspect the contents of your binlogs.mysqlbinlog --start-datetime="2023-10-27 08:00:00" /var/log/mysql/mysql-bin.000001 /var/log/mysql/mysql-bin.000002 ... | less - Scroll through the output. You’re looking for events that occurred after your full backup. The output will show timestamps and SQL statements.
- Locate the transaction just before your target restore time (
2023-10-27 10:35:00). Note theend_log_posfor that transaction. Let’s say it’s12345inmysql-bin.000002.
- First, find the binary log file and position corresponding to your full backup. When
-
Fix: You’ll use these values in the restore command. The crucial pieces are the binlog file(s) and the position after the last transaction you want to keep. So, if your target is
10:35:00and the last transaction before that ends at position12345inmysql-bin.000002, you’ll usemysql-bin.000002and position12345.
-
-
Prepare the Target Environment: You need a clean MySQL instance or a database where you can apply the backup and binlogs.
- Diagnosis: Ensure you have a MySQL server running. If you’re restoring to an existing database, you’ll need to stop writes to it immediately to prevent further divergence from your desired point in time.
- Fix:
- Option A (New Database/Server): Set up a fresh MySQL instance. This is safer.
- Option B (Existing Database): Stop all application writes to the database. You can do this by stopping application servers, or by revoking write privileges for the relevant users:
FLUSH TABLES WITH READ LOCK; -- Run this from a separate mysql client -- This locks all tables and prevents writes until you UNLOCK TABLES.
-
Restore the Full Backup: Load your
backup_full.sqlinto the target environment.- Diagnosis: The restore might fail if the database already exists and has data.
- Fix:
- If restoring to a new database:
mysql -u root -p production < backup_full.sql - If restoring to an existing (but locked) database, you might need to drop tables first or use a temporary database name, then rename. For simplicity, assume a new database or a dropped/recreated one.
- If restoring to a new database:
- Why it works: This brings the database to the state it was in at
2023-10-27 08:00:00.
-
Apply Binary Logs Up to the Target Time: This is where the point-in-time magic happens. You’ll use
mysqlbinlogto read the transactions from the binlogs and pipe them into themysqlclient.- Diagnosis: If you apply the wrong binlog files or the wrong positions, you’ll either miss data or apply the bad transaction.
- Fix:
Or, if you identified a specific stop position:mysqlbinlog --stop-datetime="2023-10-27 10:35:00" /var/log/mysql/mysql-bin.000001 /var/log/mysql/mysql-bin.000002 | mysql -u root -p productionmysqlbinlog --stop-position=12345 /var/log/mysql/mysql-bin.000002 | mysql -u root -p production- Crucially, if you had to manually identify the start position from your full backup:
# Find the position in mysql-bin.000001 that's just after 08:00:00 # Let's say it's position 500 mysqlbinlog --start-position=500 --stop-datetime="2023-10-27 10:35:00" /var/log/mysql/mysql-bin.000001 /var/log/mysql/mysql-bin.000002 | mysql -u root -p production
- Crucially, if you had to manually identify the start position from your full backup:
- Why it works:
mysqlbinlogreads the recorded transactions. The--stop-datetimeor--stop-positionflags tell it to stop just before that exact moment or position. Piping this output tomysqlexecutes those transactions sequentially on your target database, effectively replaying history up to your desired point.
-
Release the Lock (if applicable): If you used
FLUSH TABLES WITH READ LOCK, you need to release it.- Diagnosis: The database will remain read-only until the lock is released.
- Fix:
UNLOCK TABLES;- Why it works: This command removes the global read lock, allowing writes again.
-
Verify: Check your
userstable. It should contain the data it had at2023-10-27 10:35:00, before theDELETEstatement.
The next error you’ll hit is if your binlog files are missing or corrupted, preventing you from replaying transactions beyond your last full backup.