Databases 42 min read

How to Recover Accidentally Dropped MySQL Data in 48 Hours – A Complete Step‑by‑Step Guide

This guide walks you through a full disaster‑recovery workflow for MySQL, covering emergency read‑only switching, pinpointing the deletion time via binlog, preparing a recovery instance, restoring the latest full backup with Xtrabackup or mysqldump, applying incremental binlog changes, verifying data integrity, and safely switching traffic back to the restored database.

Ops Community
Ops Community
Ops Community
How to Recover Accidentally Dropped MySQL Data in 48 Hours – A Complete Step‑by‑Step Guide

Implementation Steps Overview

The recovery architecture follows a five‑stage process: stop writes, locate the deletion point, choose a recovery strategy (full backup + binlog or replica promotion), verify restored data, and switch traffic.

Step 1 – Emergency Stop (First Action)

Goal: Immediately prevent further writes and protect the current binlog.

Set the instance to read‑only: SET GLOBAL read_only=1; SET GLOBAL super_read_only=1; Verify active connections and kill non‑essential writers.

Flush logs to start a new binlog file: FLUSH LOGS; Copy the current binlog files to a safe directory.

Record the current binlog file name and position.

Step 2 – Locate Deletion Time and Binlog Position

Goal: Precisely identify the binlog entry that performed the DROP/DELETE.

# List binlog files
mysql -uroot -p -e "SHOW BINARY LOGS;"
# Filter by time range (example)
mysqlbinlog --start-datetime="2024-01-15 14:30:00" \
    --stop-datetime="2024-01-15 14:40:00" \
    -v /var/lib/mysql/mysql-bin.000122 | grep -i "DROP TABLE\|DELETE FROM orders"
# Verify exact position
mysqlbinlog --start-datetime="2024-01-15 14:35:00" \
    --stop-datetime="2024-01-15 14:36:00" \
    /var/lib/mysql/mysql-bin.000122 | grep "^# at "

Step 3 – Prepare Recovery Environment (Standalone Instance)

Goal: Build an isolated MySQL server to avoid impacting production.

Install MySQL on a separate host.

Set a strong root password.

Adjust InnoDB parameters for large‑transaction recovery (e.g., max_allowed_packet=512M, innodb_flush_log_at_trx_commit=0).

Restart MySQL and verify configuration.

Step 4 – Restore Full Backup

Goal: Bring the database back to the latest backup taken before the deletion.

Two common methods are shown:

Physical backup with Percona Xtrabackup :

# Install Xtrabackup
yum install -y percona-xtrabackup-80
# Prepare backup
xtrabackup --prepare --target-dir=/tmp/xtrabackup
# Copy back to data directory
xtrabackup --copy-back --target-dir=/tmp/xtrabackup --datadir=/var/lib/mysql
chown -R mysql:mysql /var/lib/mysql
systemctl start mysqld

Logical backup with mysqldump :

# Decompress dump
gunzip < /data/backup/all_databases_2024-01-15.sql.gz > /tmp/restore.sql
# Import
mysql -uroot -pNewP@ssw0rd123! < /tmp/restore.sql

Step 5 – Apply Binlog Incremental Changes

Goal: Replay all changes from the backup point up to just before the accidental DROP, then skip the DROP and continue applying subsequent events.

# Apply binlog from backup point to just before deletion
mysqlbinlog --start-position=12345678 /data/emergency_backup/mysql-bin.000120 | mysql -uroot -pNewP@ssw0rd123! -f
# Apply following binlog files up to the position before the DROP
mysqlbinlog --stop-position=87654320 /data/emergency_backup/mysql-bin.000122 | mysql -uroot -pNewP@ssw0rd123! -f
# Skip the DROP and continue with later binlogs
mysqlbinlog --start-position=87654500 /data/emergency_backup/mysql-bin.000122 | mysql -uroot -pNewP@ssw0rd123! -f
# Apply any remaining binlog files
for binlog in mysql-bin.000123 mysql-bin.000124; do
    mysqlbinlog /data/emergency_backup/$binlog | mysql -uroot -pNewP@ssw0rd123! -f
done

Step 6 – Data Verification and Business Testing

Goal: Ensure the restored data matches expectations before serving traffic.

Row‑count comparison between restored and backup snapshots.

Checksum verification with pt-table-checksum.

Key business queries (e.g., recent orders) to confirm continuity.

Create a read‑only test user for business validation.

Step 7 – Switch Traffic Back to Restored Database

Goal: Gradually route production traffic to the recovered instance.

Option A – Promote the recovered instance as primary (recommended).

Update DNS or application configuration to point to the new host.

Monitor connection counts, QPS, and error logs.

Option B – Re‑import the recovered table back to the original primary if needed.

Minimal Principle

The core mechanism relies on a combination of full backups and incremental binlog replay. ROW‑format binlogs are essential because they record before/after images for precise undo.

48‑Hour Time Breakdown

Locate deletion (2‑4 h)

Prepare environment (1‑2 h)

Restore full backup (4‑12 h, depending on size)

Apply binlog increments (8‑24 h)

Data verification (2‑4 h)

Traffic switch (1‑2 h)

Observability

Linux native tools (e.g., mysqladmin processlist, iostat) and Prometheus exporters can monitor recovery progress, binlog generation rate, and disk I/O.

Common Faults and Troubleshooting

Binlog not enabled – fall back to the latest full backup.

Old backups – assess acceptable data loss.

Insufficient disk space – clean temporary files or expand storage.

Table‑not‑found errors – ensure CREATE TABLE statements are applied before DML.

Replication lag – use pt-table-checksum to reconcile.

Post‑recovery business errors – clear caches, re‑run business test cases.

Detailed Fault Cases

Case 1: Binlog in STATEMENT format – unable to reconstruct row‑level changes. Solution: switch to ROW format or recover from a replica that has ROW binlogs.

Case 2: Duplicate‑key errors during binlog replay – caused by overlapping backup point and binlog start position. Solution: start replay exactly at the recorded xtrabackup_binlog_info position or use -f to force continuation.

Change & Rollback Playbook

Gray‑Scale Strategy

Validate restored data with read‑only access.

Gradually shift 10 % of traffic using DNS or a proxy (e.g., ProxySQL).

Monitor metrics; increase traffic to 50 % then 100 %.

Finally set the original primary to read‑only.

Rollback Conditions

Data verification fails.

Critical business functions break.

Severe performance degradation.

Rollback steps: immediately point DNS/app config back to the original primary, verify service health, and investigate root cause.

Backup Scripts Before Recovery

#!/bin/bash
# backup_before_recovery.sh – snapshot current state
BACKUP_DIR=/data/emergency_backup/$(date +%Y%m%d_%H%M%S)
mkdir -p $BACKUP_DIR
cp -p /etc/my.cnf $BACKUP_DIR/
mysql -e "SHOW MASTER STATUS\G" > $BACKUP_DIR/master_status.txt
cp -p /var/lib/mysql/mysql-bin.* $BACKUP_DIR/
mysqldump -u root -p --no-data --databases test > $BACKUP_DIR/schema.sql
mysql -e "SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema='test';" > $BACKUP_DIR/table_stats.txt

Best Practices

Enable binlog with binlog_format=ROW and retain at least 7 days.

Schedule daily physical backups (Xtrabackup) and real‑time binlog archiving.

Run monthly restore drills.

Apply least‑privilege policies – deny DROP/TRUNCATE for application users.

Use delayed replica (e.g., 1 hour) as a safety net.

Audit DDL/DML with MySQL Enterprise Audit or MariaDB audit plugin.

FAQ

Q: Binlog also deleted? – Check replicas or remote binlog archives; otherwise rely on the most recent full backup.

Q: DELETE without WHERE? – Use binlog2sql to generate reverse SQL and apply it.

Q: How to confirm completeness? – Compare row counts, checksums (pt‑table‑checksum), key aggregates, and run business validation.

Q: Can recovery be interrupted? – Yes, but you must restart from the full backup or the last known good binlog position.

Q: How long after deletion can data be recovered? – Depends on backup frequency and binlog retention.

Q: Prevent accidental deletions? – Enforce permission controls, SQL review tools, two‑person approval, delayed replicas, and regular backup verification.

Q: Xtrabackup vs. mysqldump? – Xtrabackup is faster, non‑blocking, and suitable for large databases; mysqldump is slower and better for small or single‑table exports.

Appendix: Key Scripts

Script 1 – Daily Xtrabackup Full Backup

#!/bin/bash
set -euo pipefail
BACKUP_ROOT="/data/backup/mysql"
RETENTION_DAYS=7
MYSQL_USER="backup"
MYSQL_PASSWORD="BackupPass123!"
XTRABACKUP_BIN="/usr/bin/xtrabackup"
BACKUP_DATE=$(date +%Y-%m-%d_%H-%M-%S)
BACKUP_DIR="$BACKUP_ROOT/$BACKUP_DATE"
LOG_FILE="$BACKUP_ROOT/xtrabackup_$BACKUP_DATE.log"
mkdir -p "$BACKUP_DIR"
echo "[$(date)] Starting full backup..." | tee -a "$LOG_FILE"
$XTRABACKUP_BIN --backup \
    --user=$MYSQL_USER \
    --password=$MYSQL_PASSWORD \
    --target-dir=$BACKUP_DIR \
    --parallel=4 2>&1 | tee -a "$LOG_FILE"
if [ $? -eq 0 ]; then
    echo "[$(date)] Backup succeeded: $BACKUP_DIR" | tee -a "$LOG_FILE"
else
    echo "[$(date)] Backup failed!" | tee -a "$LOG_FILE"
    exit 1
fi
# Compress
tar -czf "$BACKUP_DIR.tar.gz" -C "$BACKUP_ROOT" "$(basename $BACKUP_DIR)"
rm -rf "$BACKUP_DIR"
# Cleanup old backups
find "$BACKUP_ROOT" -name "*.tar.gz" -mtime +$RETENTION_DAYS -delete
find "$BACKUP_ROOT" -name "xtrabackup_*.log" -mtime +$RETENTION_DAYS -delete

Script 2 – Binlog Real‑Time Archiving

#!/bin/bash
set -euo pipefail
BINLOG_DIR="/var/lib/mysql"
ARCHIVE_ROOT="/data/binlog_archive/$(date +%Y%m%d)"
mkdir -p "$ARCHIVE_ROOT"
CURRENT_BINLOG=$(mysql -uroot -pRootPass123! -e "SHOW MASTER STATUS\G" | grep File | awk '{print $2}')
for binlog in $BINLOG_DIR/mysql-bin.*; do
    name=$(basename $binlog)
    if [ "$name" = "$CURRENT_BINLOG" ]; then
        continue
    fi
    cp -p "$binlog" "$ARCHIVE_ROOT/"
done
# Optional: delete archives older than 30 days
find /data/binlog_archive -type f -mtime +30 -delete

Script 3 – One‑Click Disaster Recovery

#!/bin/bash
set -euo pipefail
BACKUP_FILE="/data/backup/mysql/2024-01-15_02-00-00.tar.gz"
BINLOG_DIR="/data/emergency_backup"
DROP_POSITION=87654321
DROP_BINLOG="mysql-bin.000122"
MYSQL_USER="root"
MYSQL_PASSWORD="NewP@ssw0rd123!"
RECOVERY_DIR="/var/lib/mysql"
# Restore full backup
systemctl stop mysqld
rm -rf "$RECOVERY_DIR"/*
mkdir -p /tmp/xtrabackup
tar -zxf "$BACKUP_FILE" -C /tmp/
xtrabackup --prepare --target-dir=/tmp/xtrabackup
xtrabackup --copy-back --target-dir=/tmp/xtrabackup --datadir=$RECOVERY_DIR
chown -R mysql:mysql $RECOVERY_DIR
systemctl start mysqld
sleep 5
# Apply binlog up to deletion point
BACKUP_INFO=$(cat /tmp/xtrabackup/xtrabackup_binlog_info)
START_BINLOG=$(echo $BACKUP_INFO | awk '{print $1}')
START_POS=$(echo $BACKUP_INFO | awk '{print $2}')
mysqlbinlog --start-position=$START_POS \
    --stop-position=$DROP_POSITION \
    $BINLOG_DIR/$DROP_BINLOG | mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -f
# Skip DROP and apply remaining binlogs
AFTER_POS=$((DROP_POSITION + 200))
mysqlbinlog --start-position=$AFTER_POS $BINLOG_DIR/$DROP_BINLOG | mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -f
# Verify row count
COUNT=$(mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -e "SELECT COUNT(*) FROM test.orders;" | tail -1)
echo "Recovered orders count: $COUNT"

Extended Reading

MySQL Binlog Documentation – https://dev.mysql.com/doc/refman/8.0/en/binary-log.html

Percona Xtrabackup – https://www.percona.com/doc/percona-xtrabackup/

Meituan Tech: "MySQL Data Recovery in Production" – https://tech.meituan.com/2018/11/01/mysql-flashback.html

Alibaba Cloud DBA: "Production Accident – Full Record of Table Drop Recovery" – https://developer.aliyun.com/article/

binlog2sql (flashback tool) – https://github.com/danfengcao/binlog2sql

MyFlash – https://github.com/Meituan-Dianping/MyFlash

Percona Toolkit – https://www.percona.com/software/database-tools/percona-toolkit

MySQLbinlogdisaster recoveryData RecoveryBackupxtrabackup
Ops Community
Written by

Ops Community

A leading IT operations community where professionals share and grow together.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.