1、概述
MySQL 里"增量备份"其实有两条路线:
- 物理增量:用 Percona XtraBackup,基于 LSN 备份变化的数据页(大库首选)
- 逻辑增量 :mysqldump 本身没有真增量,靠 全量 + binlog 回放 实现增量/时间点恢复(小库够用)
下面讲的是逻辑增量方式。
2、前置准备
2.1 开启 binlog(增量恢复必须)
my.cnf / my.ini
[mysqld]
server-id = 1
log_bin = /var/lib/mysql/mysql-bin
binlog_format = ROW
expire_logs_days = 7
- server-id:必须配置,binlog依赖server-id
- binlog_format:生产强制ROW!statement容易数据不一致
- expire_logs_days:数据库自动清理本地binlog,不要设置太大,防止磁盘打满
重启 MySQL 后确认:
SHOW VARIABLES LIKE 'log_bin';
SHOW MASTER STATUS;
2.2 备份账号权限
CREATE USER 'backup_user'@'localhost' IDENTIFIED BY 'Backup@123';
GRANT RELOAD, LOCK TABLES, REPLICATION CLIENT, PROCESS, SELECT, BACKUP_ADMIN ON *.* TO 'backup_user'@'localhost';
3、mysqldump 全量 + binlog 增量(适合 <50G 库)
3.1 全量备份
mysqldump -ubackup_user -p \
--single-transaction \
--source-data=2 \
--routines --triggers --events \
--all-databases \
--flush-logs \
--default-character-set=utf8mb4 \
> /backup/full_$(date +%F).sql
--single-transaction:核心!InnoDB 通过事务快照实现一致性,不加锁,业务正常读写;只对 InnoDB 生效,MyISAM 会失效--source-data=2- 会在 sql 文件头部写入注释:
-- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000005', MASTER_LOG_POS=156; - =2 是注释形式,导入 sql 的时候不会执行;=1 是可执行语句,不要用 1
- 这个位点就是我们后续 binlog 回放的起始位点,非常关键
- 会在 sql 文件头部写入注释:
--routines:导出存储过程、函数(很多脚本漏掉,恢复后存储过程丢失)--triggers:导出触发器--events:导出定时事件 event--all-databases:导出所有库;如果只需要指定库,改成--databases db1 db2--flush-logs:dump 开始前,切割 binlog,生成一个新 binlog 文件--default-character-set=utf8mb4:支持 emoji,避免字符集错乱
查看位点:
grep "CHANGE MASTER" /backup/full_2026-11-11.sql
# 输出类似:mysql-bin.000123 位置 156
3.2 "增量备份"(其实是归档 binlog)
全量之后,新数据都在 binlog 里,start-position和位置用的上面的查看位点的信息:
# 导出某个位点之后的 binlog
mysqlbinlog --start-position=156 \
/var/lib/mysql/mysql-bin.000123 \
> /backup/inc_$(date +%F_%H%M).sql
生产建议定时 FLUSH LOGS,把旧 binlog 拷贝走压缩归档。
3.3 恢复流程
# 1. 停业务(避免新写入)
# 2. 建库清表或直接全量导入
mysql -uroot -p < /backup/full_2026-11-11.sql
# 3. 回放增量 binlog
mysql -uroot -p < /backup/inc_2026-11-11_1400.sql
# 4. 精确到误删前(可选 PITR)
mysqlbinlog --stop-datetime="2026-11-11 13:59:59" \
/var/lib/mysql/mysql-bin.000124 | mysql -uroot -p
4、生产方案
4.1 目录结构
/data/backup/
├── full/ # 全量
├── binlog/ # 增量
└── binlog.pos # 记录上次 binlog 位点
4.2 全量备份脚本
/data/backup/full.sh
# 每周日 02:00 执行: 0 2 * * 0 /data/backup/full.sh
USER="backup_user"
PASS="Backup@123"
DIR="/data/backup/full"
BINLOG_DIR="/data/backup/binlog"
# 1. 全量导出(--flush-logs 切一个新 binlog,之后的新数据都在新文件里)
mysqldump --no-defaults -u${USER} -p${PASS} \
--single-transaction --source-data=2 --routines --triggers --events \
--all-databases --flush-logs --set-gtid-purged=OFF \
| gzip > ${DIR}/full_$(date +%F).sql.gz
if [ $? -ne 0 ]; then
echo "$(date) FULL BACKUP FAILED" >> /data/backup/backup.log
exit 1
fi
# 2. 记录本次全量对应的 binlog 位点(用于增量从这儿开始)
# --source-data=2 写入了 sql 文件头,提取出来存好
gunzip -c ${DIR}/full_$(date +%F).sql.gz | grep "CHANGE MASTER" | head -1 > ${DIR}/full_$(date +%F).pos
# 3. 同时把这个位点作为增量备份的起点
gunzip -c ${DIR}/full_$(date +%F).sql.gz | grep "CHANGE MASTER" | head -1 > /data/backup/binlog.pos
# 4. 清理 7 天前的全量
find ${DIR} -name "full_*.sql.gz" -mtime +7 -delete
find ${DIR} -name "full_*.pos" -mtime +7 -delete
echo "$(date) full backup ok" >> /data/backup/backup.log
- 示例脚本备份的是全部库,如果只备份指定库,需要修改脚本如:--databases test sys。
4.3 binlog 增量备份脚本
/data/backup/binlog.sh
# 每 5 分钟执行: */5 * * * * /data/backup/binlog.sh
USER="backup_user"
PASS="Backup@123"
BINLOG_DIR="/usr/local/mysql" # binlog 实际目录
BACKUP_DIR="/data/backup/binlog"
POS_FILE="/data/backup/binlog.pos"
# 读取上次位点
if [ -f "${POS_FILE}" ]; then
LAST_FILE=$(awk -F"'" '{print $2}' ${POS_FILE})
LAST_POS=$(awk -F'MASTER_LOG_POS=' '{print $2}' ${POS_FILE} | tr -d ';')
else
echo "ERROR: no pos file, run full.sh first" >> /data/backup/backup.log
exit 1
fi
# 获取当前位点
CURRENT=$(mysql --no-defaults -u${USER} -p${PASS} -N -e "SHOW MASTER STATUS" 2>/dev/null)
CUR_FILE=$(echo "${CURRENT}" | awk '{print $1}')
CUR_POS=$(echo "${CURRENT}" | awk '{print $2}')
# 位点没变就跳过
if [ "${LAST_FILE}" == "${CUR_FILE}" ] && [ "${LAST_POS}" == "${CUR_POS}" ]; then
exit 0
fi
# 导出从上次位点到当前的 binlog
mysqlbinlog --no-defaults \
--start-position=${LAST_POS} \
${BINLOG_DIR}/${LAST_FILE} \
| gzip > ${BACKUP_DIR}/inc_$(date +%F_%H%M%S).sql.gz
if [ ${PIPESTATUS[0]} -ne 0 ]; then
echo "$(date) binlog backup failed" >> /data/backup/backup.log
exit 1
fi
# 更新位点
echo "CHANGE MASTER TO MASTER_LOG_FILE='${CUR_FILE}', MASTER_LOG_POS=${CUR_POS};" > ${POS_FILE}
# 清理 7 天前的增量
find ${BACKUP_DIR} -name "inc_*.sql.gz" -mtime +7 -delete
echo "$(date) binlog backup ok: ${LAST_FILE}:${LAST_POS} -> ${CUR_FILE}:${CUR_POS}" >> /data/backup/backup.log
- 增量备份(binlog) → 永远是实例级,无法指定库。
4.4 恢复脚本
/data/backup/restore.sh
# 用法: ./restore.sh
USER="root"
PASS="RootPass"
FULL_DIR="/data/backup/full"
BINLOG_DIR="/data/backup/binlog"
echo "=== 可用全量备份 ==="
ls -lt ${FULL_DIR}/full_*.sql.gz | head -5
read -p "输入要恢复的全量文件名: " FULL_FILE
# 1. 恢复全量
echo "恢复全量: ${FULL_FILE}"
gunzip -c ${FULL_DIR}/${FULL_FILE} | mysql --no-defaults -u${USER} -p${PASS}
# 2. 恢复增量(按时间顺序)
echo "恢复增量 binlog..."
for f in $(ls ${BINLOG_DIR}/inc_*.sql.gz 2>/dev/null | sort); do
echo " 回放: $f"
gunzip -c $f | mysql --no-defaults -u${USER} -p${PASS} --force
done
echo "恢复完成"
-
示例脚本是恢复全部库,如果只恢复部分库,需要改参数mysqlbinlog 的 --database 参数,如
mysqlbinlog --no-defaults
--database=db1
mysql-bin.000071
| mysql --no-defaults -uroot -p
4.5 定时crontab
crontab -e
0 2 * * 0 /data/backup/full.sh
*/5 * * * * /data/backup/binlog.sh
- 全量:每周日 02:00 执行
- 增量:每 5 分钟执行
4.6 部署 3 步搞定
# 1. 创建目录
mkdir -p /data/backup/{full,binlog}
# 2. 上传 3 个脚本,改权限
chmod +x /data/backup/*.sh
# 3. 改脚本里的用户名密码
vi /data/backup/full.sh
vi /data/backup/binlog.sh
vi /data/backup/restore.sh
# 4. 先手动跑一次全量,确认没问题
/data/backup/full.sh
4.7 恢复时万一要精确到秒
# 先恢复全量
gunzip -c /data/backup/full/full_2026-11-11.sql.gz | mysql -uroot -p
# 再回放 binlog 到误删前的那一刻
mysqlbinlog --no-defaults \
--stop-datetime="2026-11-11 14:29:59" \
/var/lib/mysql/mysql-bin.000071 \
| mysql -uroot -p
4.8 总结
- 全量 = mysqldump +
--flush-logs切 binlog; - 增量 = 从上次位点用
mysqlbinlog捞变化; - 恢复 = 全量导入 + 增量回放。