MySQL 数据库备份脚本使用说明
1. 脚本简介
本脚本用于备份 MySQL 或 MariaDB 数据库,支持:
- 通过参数备份指定数据库
- 通过参数全量备份所有数据库
- 通过环境变量传递数据库地址、端口、用户名和密码
- 自动使用 gzip 压缩备份文件
- 自动检查压缩文件完整性
- 自动生成 SHA-256 校验文件
- 备份失败时自动清理未完成文件
- 备份存储过程、函数、事件和触发器
脚本文件:mysql_backup.sh
2. 运行环境
执行服务器需要安装以下命令:
bashmysqldumpgzipmktemp
可以通过以下命令确认 mysqldump 是否已经安装:
mysqldump --version
3. 配置数据库连接信息
执行脚本前,先在当前终端中设置环境变量:
export DB_HOST="127.0.0.1"
export DB_PORT="3306"
export DB_USER="backup_user"
export DB_PASSWORD="your_password"
环境变量说明:
| 环境变量 | 是否必填 | 说明 | 默认值 |
|---|---|---|---|
DB_HOST |
是 | 数据库服务器地址 | 无 |
DB_PORT |
否 | 数据库端口 | 3306 |
DB_USER |
是 | 数据库用户名 | 无 |
DB_PASSWORD |
是 | 数据库密码 | 无 |
BACKUP_DIR |
否 | 默认备份目录 | ./backups |
脚本执行结束后,如果不再需要这些变量,可以清除密码:
unset DB_PASSWORD
4. 添加执行权限
首次使用时执行:
chmod +x mysql_backup.sh
5. 备份指定数据库
./mysql_backup.sh --database your_database --output-dir /data/backups
简写方式:
./mysql_backup.sh -d your_database -o /data/backups
6. 全量备份
备份当前实例中的所有数据库:
./mysql_backup.sh --all --output-dir /data/backups
简写方式:
./mysql_backup.sh -a -o /data/backups
7. 使用默认备份目录
可以通过 BACKUP_DIR 设置默认目录:
export BACKUP_DIR="/data/backups"
./mysql_backup.sh --all
如果没有设置 BACKUP_DIR,也没有传入 --output-dir,备份文件将保存到当前目录下的 backups 文件夹。
8. 查看帮助
./mysql_backup.sh --help
9. 备份文件格式
指定数据库备份示例:
127.0.0.1_your_database_20260911_143000.sql.gz
127.0.0.1_your_database_20260911_143000.sql.gz.sha256
全量备份示例:
127.0.0.1_all_databases_20260911_143000.sql.gz
127.0.0.1_all_databases_20260911_143000.sql.gz.sha256
其中:
-
.sql.gz是经过 gzip 压缩的数据库备份文件。 -
.sha256是备份文件的 SHA-256 校验文件。
10. 校验备份文件
Linux 环境:
sha256sum -c 127.0.0.1_your_database_20260911_143000.sql.gz.sha256
也可以检查 gzip 文件是否损坏:
gzip -t 127.0.0.1_your_database_20260911_143000.sql.gz
命令没有输出且返回成功,表示压缩文件结构正常。
11. 恢复指定数据库备份
脚本使用 --databases 生成单库备份,备份内容包含创建和选择数据库的语句。恢复命令如下:
gzip -dc 127.0.0.1_your_database_20260911_143000.sql.gz | \
mysql -h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" -p
执行后,MySQL 客户端会提示输入密码。
12. 恢复全量备份
gzip -dc 127.0.0.1_all_databases_20260911_143000.sql.gz | \
mysql -h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" -p
全量恢复可能覆盖同名数据库中的现有对象。执行前应确认恢复目标,并提前保留当前数据。
13. 安全说明
- 数据库密码通过环境变量传入,不会作为
mysqldump命令行参数出现。 - 脚本会创建权限为
600的临时 MySQL 配置文件,并在执行结束后删除。 - 脚本使用
umask 077,新生成的备份文件默认仅允许当前系统用户访问。 - 建议创建专用备份账号,不要直接使用 MySQL
root账号。 - 不要把包含真实密码的
export命令写入代码仓库或公共脚本。 - 建议将备份文件复制到独立服务器或对象存储中,避免数据库服务器损坏时备份同时丢失。
14. 数据一致性说明
脚本默认使用 --single-transaction,适合以 InnoDB 为主的数据库,可以在不长时间锁表的情况下获得一致性备份。
如果数据库包含 MyISAM 等非事务表,--single-transaction 无法保证这些表在持续写入时的一致性。此类场景应选择业务低峰期备份、暂停写入,或者采用锁表和物理备份方案。
15. 建议的备份账号权限
实际权限需要根据 MySQL 版本、表结构和功能进行调整。常见逻辑备份账号可以参考:
CREATE USER 'backup_user'@'backup_server_ip' IDENTIFIED BY 'strong_password';
GRANT SELECT, SHOW VIEW, TRIGGER, EVENT ON *.* TO 'backup_user'@'backup_server_ip';
GRANT EXECUTE ON *.* TO 'backup_user'@'backup_server_ip';
FLUSH PRIVILEGES;
请将示例中的账号、来源地址和密码替换为实际值,并遵循最小权限原则。
16. 生产环境建议
- 定期执行恢复演练;只有实际恢复成功的备份才是可信备份。
- 对备份目录设置磁盘空间监控和告警。
- 根据业务需求增加备份保留和过期清理策略。
- 对重要备份进行异地保存和加密。
- 记录每次备份的开始时间、结束时间、文件大小和执行结果。
- 在数据量较大时,先评估逻辑备份对数据库负载、网络和磁盘空间的影响。
17. 完整脚本代码
将以下内容保存为 mysql_backup.sh,然后执行 chmod +x mysql_backup.sh 添加执行权限:
#!/usr/bin/env bash
# MySQL/MariaDB logical backup script.
# Credentials are read from DB_HOST, DB_PORT, DB_USER and DB_PASSWORD.
set -Eeuo pipefail
umask 077
PROGRAM_NAME="${0##*/}"
BACKUP_MODE=""
DATABASE_NAME=""
OUTPUT_DIR="${BACKUP_DIR:-./backups}"
TEMP_CNF=""
TEMP_BACKUP=""
usage() {
cat <<EOF
Usage:
${PROGRAM_NAME} --database DATABASE [--output-dir DIRECTORY]
${PROGRAM_NAME} --all [--output-dir DIRECTORY]
Required environment variables:
DB_HOST MySQL server address
DB_USER MySQL user
DB_PASSWORD MySQL password
Optional environment variables:
DB_PORT MySQL port (default: 3306)
BACKUP_DIR Default output directory (default: ./backups)
Options:
-d, --database DATABASE Back up one database
-a, --all Back up all databases
-o, --output-dir DIR Write backups to DIR
-h, --help Show this help
EOF
}
die() {
printf 'ERROR: %s\n' "$*" >&2
exit 1
}
log() {
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
}
cleanup() {
local status=$?
[[ -z "$TEMP_CNF" ]] || rm -f -- "$TEMP_CNF"
if (( status != 0 )) && [[ -n "$TEMP_BACKUP" ]]; then
rm -f -- "$TEMP_BACKUP"
fi
exit "$status"
}
trap cleanup EXIT INT TERM
while (( $# > 0 )); do
case "$1" in
-d|--database)
(( $# >= 2 )) || die "$1 requires a database name"
[[ -z "$BACKUP_MODE" ]] || die "choose exactly one of --database or --all"
BACKUP_MODE="database"
DATABASE_NAME="$2"
shift 2
;;
-a|--all)
[[ -z "$BACKUP_MODE" ]] || die "choose exactly one of --database or --all"
BACKUP_MODE="all"
shift
;;
-o|--output-dir)
(( $# >= 2 )) || die "$1 requires a directory"
OUTPUT_DIR="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
die "unknown argument: $1 (use --help for usage)"
;;
esac
done
[[ -n "$BACKUP_MODE" ]] || die "choose --database DATABASE or --all"
[[ -n "${DB_HOST:-}" ]] || die "DB_HOST is not set"
[[ -n "${DB_USER:-}" ]] || die "DB_USER is not set"
[[ "${DB_PASSWORD+x}" == x ]] || die "DB_PASSWORD is not set"
DB_PORT="${DB_PORT:-3306}"
[[ "$DB_PORT" =~ ^[0-9]+$ ]] && (( DB_PORT >= 1 && DB_PORT <= 65535 )) || die "DB_PORT must be between 1 and 65535"
if [[ "$BACKUP_MODE" == "database" ]]; then
[[ -n "$DATABASE_NAME" ]] || die "database name cannot be empty"
[[ "$DATABASE_NAME" != -* && "$DATABASE_NAME" != *$'\n'* && "$DATABASE_NAME" != *$'\r'* ]] || die "invalid database name"
fi
for command_name in mysqldump gzip mktemp; do
command -v "$command_name" >/dev/null 2>&1 || die "required command not found: $command_name"
done
mkdir -p -- "$OUTPUT_DIR"
[[ -d "$OUTPUT_DIR" && -w "$OUTPUT_DIR" ]] || die "output directory is not writable: $OUTPUT_DIR"
# Escape values for a MySQL option file, which prevents the password from
# appearing in the process list or command-line history.
cnf_escape() {
local value="$1"
value=${value//\\/\\\\}
value=${value//\"/\\\"}
printf '%s' "$value"
}
TEMP_CNF="$(mktemp "${TMPDIR:-/tmp}/mysql-backup.cnf.XXXXXX")"
chmod 600 "$TEMP_CNF"
{
printf '[client]\n'
printf 'host="%s"\n' "$(cnf_escape "$DB_HOST")"
printf 'port=%s\n' "$DB_PORT"
printf 'user="%s"\n' "$(cnf_escape "$DB_USER")"
printf 'password="%s"\n' "$(cnf_escape "$DB_PASSWORD")"
} >"$TEMP_CNF"
timestamp="$(date '+%Y%m%d_%H%M%S')"
safe_host="${DB_HOST//[^[:alnum:]._-]/_}"
if [[ "$BACKUP_MODE" == "all" ]]; then
backup_label="all_databases"
dump_scope=(--all-databases)
else
backup_label="${DATABASE_NAME//[^[:alnum:]._-]/_}"
dump_scope=(--databases "$DATABASE_NAME")
fi
final_backup="${OUTPUT_DIR%/}/${safe_host}_${backup_label}_${timestamp}.sql.gz"
TEMP_BACKUP="$(mktemp "${OUTPUT_DIR%/}/.${safe_host}_${backup_label}_${timestamp}.XXXXXX.sql.gz")"
dump_options=(
--defaults-extra-file="$TEMP_CNF"
--single-transaction
--quick
--routines
--events
--triggers
--hex-blob
--default-character-set=utf8mb4
)
log "starting backup: ${backup_label} from ${DB_HOST}:${DB_PORT}"
if ! mysqldump "${dump_options[@]}" "${dump_scope[@]}" | gzip -c >"$TEMP_BACKUP"; then
die "backup failed"
fi
[[ -s "$TEMP_BACKUP" ]] || die "backup output is empty"
gzip -t "$TEMP_BACKUP" || die "compressed backup verification failed"
mv -- "$TEMP_BACKUP" "$final_backup"
TEMP_BACKUP=""
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$final_backup" >"${final_backup}.sha256"
elif command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$final_backup" >"${final_backup}.sha256"
fi
backup_size="$(du -h "$final_backup" | awk '{print $1}')"
log "backup completed: ${final_backup} (${backup_size})"