MariaDB 备份恢复:mysqldump 到定时冷备,一条链路走通

MariaDB 备份恢复:mysqldump 到定时冷备,一条链路走通

MariaDBMySQL数据库备份mysqldumpcron 定时备份
📶 入门 🧩 MariaDB · cron

数据库挂掉的方式多种多样:磁盘损坏、误执行 DROP TABLE、被勒索软件加密、升级大版本失败回滚不了。共同点是,等到需要备份时已经晚了。多数人对数据库备份的认知停留在「拷个文件」,但对运行中的 MariaDB 直接 cp 数据目录,拿到的大概率是一份坏文件。这篇文章把逻辑备份和物理备份的区别讲清楚,然后给一套小服务器今天就能上线的方案:mysqldump 单库导出、gzip 压缩、cron 定时、本地加异地冷备各留 7 份。

先回答选型问题:MariaDB 官方文档把备份分成两大类,逻辑备份输出 SQL 语句,物理备份直接复制数据文件。两者关键差异一张表看懂:

维度逻辑备份(mysqldump)物理备份(mariabackup)
备份产物SQL 文本,体积大数据文件副本,体积小
恢复速度慢,要重新执行 SQL快,直接放回数据目录
跨版本/跨库可恢复到不同版本甚至其他数据库基本绑死同类版本
备份粒度可精确到库、表、甚至行目录和文件级
备份期间影响全表扫描,可能挤掉缓冲池非阻塞设计,对写入影响小
数据量适合十 GB 以内更大数据量

官方文档同时提醒:LVM 快照这类块层方案对数据库是盲的,不刷缓存不加锁时捕捉到的是类似突然断电的状态,有页损坏风险,且快照和源数据在同一块盘上,算不上真正的备份。小服务器别把系统快照当数据库备份。数据量在十 GB 以内、单机单库的场景,mysqldump 是更简单的选择,也是本文的主线。

单库导出:一条命令加三个参数

mysqldump 是 MariaDB 自带的客户端工具(MariaDB 10.5 起推荐用新名字 mariadb-dump,两者是同一个程序)。最基础的用法:

mariadb-dump -u root -p db_name > backup.sql

实际跑定时任务时加三个参数,安全性和完整性都会好一截:

mariadb-dump --single-transaction --routines --triggers \
  -u backup_user -p"密码" db_name | gzip > /var/backups/mysql/db_name-$(date +%F).sql.gz

三个参数各自的用意:

  • --single-transaction:对 InnoDB 表在单个一致性事务里导出,备份期间应用照常写入,不用锁表。存储引擎全是 InnoDB 时必加。
  • --routines:默认情况下存储过程和函数不会出现在导出文件里,触发器才会跟着表走。有存储过程就加这个参数,否则恢复完才发现少了东西。
  • 管道接 gzip:SQL 文本压缩比很高,几十 MB 的库压完通常只剩几 MB。

验证导出文件是否完整,看文件末尾有没有 Dump completed 这行标记。被 disk full 截断的备份文件不会带这行。

恢复就是把 SQL 文件灌回去:

mariadb db_name < backup.sql

注意恢复前目标库要先建好(CREATE DATABASE),dump 文件里默认不包含建库语句。

备份专用账号:只给备份需要的权限

定时备份不该用 root 连数据库。建一个只能备份的专用账号,是数据库安全加固(内链 /tutorials/server-security-hardening)在数据库层面的延伸:

CREATE USER 'backup_user'@'localhost' IDENTIFIED BY '强密码';
GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER ON *.* TO 'backup_user'@'localhost';
FLUSH PRIVILEGES;

密码写在命令行里会进 shell 历史和进程列表,改成把凭据放在受保护的配置文件里。MariaDB 支持 ~/.my.cnf(新版叫 ~/.my.cnf,通用格式):

[client]
user=backup_user
password=你的强密码
chmod 600 /var/backups/.my.cnf

之后所有备份命令都不需要 -p 参数,cron 里也干净。

cron 定时:每天凌晨自动跑

备份脚本是整条链路的核心。写入 /usr/local/bin/mysql-backup.sh。脚本里同时做了三件事:滚动保留 7 份、校验导出完整性、失败立即终止。

#!/bin/bash
set -euo pipefail

BACKUP_DIR="/var/backups/mysql"
KEEP=7
DBS=(app_db blog_db)

mkdir -p "$BACKUP_DIR"
for db in "${DBS[@]}"; do
  mariadb-dump --single-transaction --routines --triggers \
    --defaults-extra-file=/var/backups/.my.cnf \
    "$db" | gzip > "$BACKUP_DIR/${db}-$(date +%F).sql.gz"
done

# 只保留最近 KEEP 份, older 文件直接删
find "$BACKUP_DIR" -name '*.sql.gz' -mtime +$KEEP -delete

# 导出文件末尾必须有完成标记,否则视为失败
for f in "$BACKUP_DIR"/*.sql.gz; do
  if ! gzip -cd "$f" | tail -c 200 | grep -q "Dump completed"; then
    echo "backup incomplete: $f" >&2
    exit 1
  fi
done

要点说明:

  • set -euo pipefail 是 bash 脚本防御性编程(内链 /tutorials/bash-defensive-scripting)的标准开头,任何一步失败立即终止,避免拿着半截备份继续跑。
  • find ... -mtime +7 -delete 负责滚动清理,本地不会无限膨胀。
  • 最后的完整性检查把每次备份的末尾都扫一遍,导出中断能当场发现。

权限收紧,然后注册到 cron:

chmod 700 /usr/local/bin/mysql-backup.sh
crontab -e
15 3 * * * /usr/local/bin/mysql-backup.sh >> /var/log/mysql-backup.log 2>&1

每天 03:15 跑一次,避开整点高负载时段。输出追加到日志文件,出问题时能追溯。cron 与 systemd timer 的取舍之前单独写过(内链 /tutorials/cron-vs-systemd-timer),这里不展开。

冷备:本地一份不叫备份

本地 /var/backups/mysql 和数据库在同一块盘上。磁盘坏一次,两份一起没。上一篇文章讲的异地对象存储(内链 /tutorials/auto-backup-to-cos-oss)在数据库备份里同样适用,备份脚本末尾加一行同步即可:

rclone copy "$BACKUP_DIR" remote:mysql-cold --max-age 24h

--max-age 24h 只传当天的新文件,配合 rclone copy 不删除远端旧版本,远端自然形成长期保留链。这行要放在 set -euo pipefail 脚本里,rclone 失败会终止脚本并在 cron 日志里留下记录,备份静默失败是最危险的形态——你以为有备份,实际没有。

一个实用原则:没有做过恢复演练的备份等于没有备份。挑个空闲时段把最近一份 .sql.gz 灌进一个临时库,确认表结构和行数正常,这条链路才算真正验证过。

常见问题

Q:备份文件里的中文乱码了怎么办? 导出时显式指定字符集:mariadb-dump --default-character-set=utf8mb4 ...。多数乱码是库的默认字符集和导出连接字符集不一致导致的,统一成 utf8mb4 一劳永逸。

Q:导出过程中应用还能写入吗? InnoDB 表加 --single-transaction 可以,备份读到的是事务开始时的一致性快照。MyISAM 表没有事务,做不到这一点,写操作要么暂停要么接受数据不一致。

Q:dump 文件太大,导入恢复太慢怎么办? 先确认数据量:十 GB 以内的库 mysqldump 恢复通常在分钟级。更大的库换物理备份工具 mariabackup,恢复时直接拷文件,跳过执行 SQL 的过程。临时加速导入可以在恢复会话里 SET foreign_key_checks=0 和 SET unique_checks=0,恢复完改回来。

Q:cron 里备份没跑,日志也没报错,从哪查? 先确认脚本单独手动执行没问题,再查 cron 环境差异:cron 的 PATH 和登录 shell 不同,脚本里调用的命令最好写绝对路径。/var/log/syslog 里 grep CRON 能看到任务是否被调度。

Q:要不要每天备份全部数据库? 按变更频率分层。核心业务库每天备,几乎不动的库(比如年度归档表)每周备一次。mariadb-dump 支持一次指定多个库或 --all-databases,分层用不同的 cron 行即可。


备份这件事的门槛从来在「第一次恢复演练」之前。脚本 20 行,cron 一行,rclone 一行,加起来半小时的工作量,换来的是磁盘损坏或误删库之后还能把数据拿回来。数据量超过十 GB、或者备份窗口开始影响业务时,再把 mariabackup 换上来。