Databases

MySQL Logical Backup and Restore: mysqldump, Faster Imports, and Recovering from Accidental Deletes

Exporting with mysqldump is easy; the hard parts are whether the dump restores cleanly, whether the restores fast enough, and whether an accidental delete comes back. This guide covers consistency flags, single-transaction isolation, import-acceleration knobs, and a binlog-based recovery flow.

By LaoHand Team·8 min read·Updated 2026-09-06

Set the strategy first: logical vs physical backup, when to choose which

mysqldump is a logical backup: it exports SQL, which is portable across versions and platforms and restores partially, but a full restore is slower than a physical backup (xtrabackup tearing real files). For small databases and triage scenarios logical is enough; online databases tens of gigabytes and larger usually need a physical snapshot.

Below is the complete logical chain, assuming you know the schema has no conflicting stored procedures or pass --routines explicitly. The golden rule of backup is: a daily full dump plus binlog archiving so the restore point resolves down to the minute.

# 单库全量并带 事件/触发器/存储过程
mysqldump -u root -p --single-transaction --routines --events \
  --databases me_db > /backup/me_db.$(date +%F).sql
# 校验导出非空且有 HEADER
grep -c "CREATE TABLE" /backup/me_db.$(date +%F).sql
head -5 /backup/me_db.$(date +%F).sql

Consistency is the point: --single-transaction and the MyISAM trade-off

By default mysqldump locks tables; under InnoDB for a lock-free consistent snapshot pass --single-transaction: it exports every table inside one REPEATABLE READ transaction so you see one consistent point in time. That relies on InnoDB MVCC and does not apply to MyISAM, which still needs --lock-tables.

Beware mixed engines: --single-transaction guarantees only InnoDB consistency, MyISAM tables still get briefly locked. If your tables are all InnoDB (the recommended state), export with single-transaction confidently and add --master-data=2 to record the binlog position for later point-restore.

InnoDB 推荐
mysqldump -u root -p --single-transaction --master-data=2 \
  --routines me_db > me_db.$(date +%F).sql
# 混用引擎时至少带上锁
mysqldump -u root -p --lock-tables --routines me_db > me_db_lock.sql
# 看记录下的 binlog 位点
grep -m1 "CHANGE MASTER" me_db.$(date +%F).sql

Recovery step one: pre-import discipline and acceleration flags

Before importing do three things: ensure the target database exists (CREATE DATABASE if missing), disable foreign key checks, and disable autocommit. For InnoDB also consider temporarily enlarging the buffer pool so the huge in-batch index work speeds up.

Import with mysql < dump.sql rather than splitting into thousands of single inserts. A flood of one-row INSERTs is the number-one cause of slow restores; export side merges rows via the --opt defaults, and the restore side gains a lot from innodb_flush_log_at_trx_commit=0 plus autocommit off.

# 错误示范:每行一条提交,慢到怀疑人生
# mysql me_db < dump.sql          # 无外键关闭也无缓存池

# 修复对照:先做工作区再导
mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS me_db"
mysql -u root -p me_db < dump.sql && echo "seated OK" || echo "import returned non-zero"
# 加速用:临时降低 flush 强度
mysql -u root -p -e "SET GLOBAL innodb_flush_log_at_trx_commit=0; SET autocommit=0;"

Recover from accidental deletes: full dump plus binlog back to the moment before

Accidental deletes (an UPDATE missing a WHERE, a DELETE without conditions) are the most painful operational accidents, remedied by full-plus-binlog replay: import the full dump into a staging database, then replay the binlog with --stop-position or --stop-datetime until the transaction just before the mistake, cutting at the right moment.

The precondition is binlog enabled (log_bin=ON) with row or mixed format. The rhythm: locate the offending event coordinates in the binlog (mysqlbinlog to find that DEL/UPDATE), set the stop position, import into a staging db to verify, then promote back to production. Full flow below.

# 1) 找到误删事件
mysqlbinlog --no-defaults --skip-opt --base64-output=decode-rows \
  /var/lib/mysql/bin.000044 | grep -n "DELETE FROM" | head
# 2) 重放到指定时刻之前
mysqlbinlog --no-defaults --stop-datetime="2026-09-06 10:15:00" \
  /var/lib/mysql/bin.000044 | mysql -u root -p stage_db
# 3) 核对 row count 后切回
mysql -u root -p -e "SELECT COUNT(*) FROM stage_db.orders"
# 确认无误再用一片事务导入真正表:
mysql -u root -p me_db < stage_clean.sql

Mistake vs fix: restore "succeeded" yet recovered nothing

It is common for an import script to report success while the target table is empty or missing rows. Three root causes usually: importing the wrong (stale) dump, the target schema already holds same-named tables so the dump without DROP TABLE jumps straight over them, or FK ordering interrupting at a child table that should be inserted first.

Turn "success" into "verifiable success": after import, replay the dump against a fresh database and cross-check row counts, or run the dump with --force once to surface alerts. A robust check compares row counts between two schemas—script and the fix essentials below.

# 错误示范:默认产出会在导入前自动 DROP,若目标已有表则可能跳过

# 修复对照:追加 --add-drop-table 保证重建,或先清空旧库
mysqldump --add-drop-table me_db > me_db_clean.sql
# 还原后用行数对账
mysql -u root -p -N -e "SELECT (SELECT COUNT(*) FROM src.orders) AS s, (SELECT COUNT(*) FROM stage.orders) AS t"
# 找出库里缺失的表
mysql -u root -p -N -e "SELECT table_name FROM information_schema.tables WHERE table_schema='stage'"

Official References

Each command links to its official documentation below, so you can verify the latest usage and read deeper.