备份概述
数据备份是数据库管理中最重要的任务之一。有效的备份策略可以保护数据免受硬件故障、人为错误、恶意攻击等威胁,确保业务连续性。
🔄 逻辑备份
导出 SQL 语句,可读性好,跨平台兼容,适合小到中型数据库
💿 物理备份
直接复制数据文件,速度快,适合大型数据库
📊 增量备份
只备份变更的数据,节省存储空间和时间
🔄 差异备份
备份自上次完整备份以来的所有变更
备份策略的核心原则:
- 3-2-1 原则:3份副本,2种不同媒介,1份异地存储
- 定期测试:定期验证备份的完整性和可恢复性
- 自动化:使用自动化工具减少人为错误
- 监控告警:建立备份监控和失败告警机制
- 文档记录:详细记录备份和恢复流程
备份类型 | 备份速度 | 恢复速度 | 存储空间 | 跨平台性 | 适用场景 |
---|---|---|---|---|---|
逻辑备份 | 慢 | 慢 | 中等 | 优秀 | 小到中型数据库 |
物理备份 | 快 | 快 | 大 | 一般 | 大型数据库 |
增量备份 | 快 | 中等 | 小 | 中等 | 频繁变更的数据库 |
差异备份 | 中等 | 中等 | 中等 | 中等 | 平衡方案 |
逻辑备份
准备测试环境
-- 创建测试数据库和表
CREATE DATABASE backup_demo;
USE backup_demo;
-- 创建用户表
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 创建订单表
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
order_number VARCHAR(50) NOT NULL UNIQUE,
total_amount DECIMAL(10,2) NOT NULL,
status ENUM('pending', 'paid', 'shipped', 'delivered', 'cancelled') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
-- 创建产品表
CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
price DECIMAL(10,2) NOT NULL,
stock_quantity INT DEFAULT 0,
category VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 插入测试数据
INSERT INTO users (username, email, password_hash) VALUES
('alice', 'alice@example.com', 'hash1'),
('bob', 'bob@example.com', 'hash2'),
('charlie', 'charlie@example.com', 'hash3'),
('diana', 'diana@example.com', 'hash4'),
('eve', 'eve@example.com', 'hash5');
INSERT INTO products (name, description, price, stock_quantity, category) VALUES
('Laptop', 'High-performance laptop', 999.99, 50, 'Electronics'),
('Mouse', 'Wireless mouse', 29.99, 200, 'Electronics'),
('Keyboard', 'Mechanical keyboard', 79.99, 100, 'Electronics'),
('Monitor', '24-inch monitor', 199.99, 75, 'Electronics'),
('Headphones', 'Noise-cancelling headphones', 149.99, 120, 'Electronics');
INSERT INTO orders (user_id, order_number, total_amount, status) VALUES
(1, 'ORD-001', 999.99, 'delivered'),
(2, 'ORD-002', 109.98, 'shipped'),
(3, 'ORD-003', 29.99, 'paid'),
(1, 'ORD-004', 349.98, 'pending'),
(4, 'ORD-005', 149.99, 'delivered');
-- 创建视图
CREATE VIEW order_summary AS
SELECT
u.username,
u.email,
COUNT(o.id) as order_count,
SUM(o.total_amount) as total_spent
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.username, u.email;
-- 创建存储过程
DELIMITER //
CREATE PROCEDURE GetUserOrders(IN user_id INT)
BEGIN
SELECT
o.id,
o.order_number,
o.total_amount,
o.status,
o.created_at
FROM orders o
WHERE o.user_id = user_id
ORDER BY o.created_at DESC;
END //
DELIMITER ;
-- 创建触发器
DELIMITER //
CREATE TRIGGER update_user_timestamp
BEFORE UPDATE ON users
FOR EACH ROW
BEGIN
SET NEW.updated_at = CURRENT_TIMESTAMP;
END //
DELIMITER ;
SELECT 'Test database and data created successfully' AS message;
使用 mysqldump 进行逻辑备份
# 1. 完整数据库备份
mysqldump -u root -p backup_demo > backup_demo_full.sql
# 2. 备份特定表
mysqldump -u root -p backup_demo users orders > backup_demo_tables.sql
# 3. 只备份表结构(不包含数据)
mysqldump -u root -p --no-data backup_demo > backup_demo_structure.sql
# 4. 只备份数据(不包含表结构)
mysqldump -u root -p --no-create-info backup_demo > backup_demo_data.sql
# 5. 备份所有数据库
mysqldump -u root -p --all-databases > all_databases_backup.sql
# 6. 备份时包含存储过程、函数、触发器
mysqldump -u root -p --routines --triggers backup_demo > backup_demo_complete.sql
# 7. 压缩备份
mysqldump -u root -p backup_demo | gzip > backup_demo.sql.gz
# 8. 备份时添加时间戳
mysqldump -u root -p backup_demo > backup_demo_$(date +%Y%m%d_%H%M%S).sql
# 9. 备份大数据库(分块处理)
mysqldump -u root -p --single-transaction --routines --triggers backup_demo > backup_demo_large.sql
# 10. 远程备份
mysqldump -h remote_host -u username -p database_name > remote_backup.sql
# 11. 备份时排除特定表
mysqldump -u root -p backup_demo --ignore-table=backup_demo.temp_table > backup_demo_exclude.sql
# 12. 创建一致性备份(InnoDB)
mysqldump -u root -p --single-transaction --flush-logs --master-data=2 backup_demo > backup_demo_consistent.sql
高级 mysqldump 选项
# 重要的 mysqldump 参数说明
# --single-transaction: 确保 InnoDB 表的一致性备份
# --flush-logs: 备份前刷新日志
# --master-data=2: 记录二进制日志位置(用于主从复制)
# --routines: 包含存储过程和函数
# --triggers: 包含触发器
# --events: 包含事件调度器
# --hex-blob: 使用十六进制格式导出二进制数据
# --complete-insert: 生成完整的 INSERT 语句
# --extended-insert: 使用多行 INSERT 语法(默认启用)
# --no-autocommit: 禁用自动提交
# --set-gtid-purged=OFF: 禁用 GTID 相关输出
# 生产环境推荐的备份命令
mysqldump -u backup_user -p \
--single-transaction \
--flush-logs \
--master-data=2 \
--routines \
--triggers \
--events \
--hex-blob \
--complete-insert \
--set-gtid-purged=OFF \
backup_demo > backup_demo_production.sql
# 备份脚本示例
#!/bin/bash
# backup_script.sh
DB_NAME="backup_demo"
DB_USER="root"
DB_PASS="your_password"
BACKUP_DIR="/backup/mysql"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/${DB_NAME}_${DATE}.sql"
# 创建备份目录
mkdir -p $BACKUP_DIR
# 执行备份
mysqldump -u $DB_USER -p$DB_PASS \
--single-transaction \
--routines \
--triggers \
$DB_NAME > $BACKUP_FILE
# 压缩备份文件
gzip $BACKUP_FILE
# 删除7天前的备份
find $BACKUP_DIR -name "${DB_NAME}_*.sql.gz" -mtime +7 -delete
echo "Backup completed: ${BACKUP_FILE}.gz"
逻辑备份的恢复
-- 1. 恢复完整数据库
-- 首先删除现有数据库(谨慎操作)
DROP DATABASE IF EXISTS backup_demo;
CREATE DATABASE backup_demo;
-- 从备份文件恢复
-- mysql -u root -p backup_demo < backup_demo_full.sql
-- 2. 恢复到新数据库
CREATE DATABASE backup_demo_restored;
-- mysql -u root -p backup_demo_restored < backup_demo_full.sql
-- 3. 恢复特定表
-- mysql -u root -p backup_demo < backup_demo_tables.sql
-- 4. 从压缩备份恢复
-- gunzip < backup_demo.sql.gz | mysql -u root -p backup_demo
-- 5. 恢复时显示进度
-- pv backup_demo_full.sql | mysql -u root -p backup_demo
-- 6. 部分恢复(只恢复特定表的数据)
-- 从备份文件中提取特定表的 INSERT 语句
-- grep "INSERT INTO users" backup_demo_full.sql | mysql -u root -p backup_demo
-- 验证恢复结果
USE backup_demo;
SELECT 'Users table:' as info;
SELECT COUNT(*) as user_count FROM users;
SELECT 'Orders table:' as info;
SELECT COUNT(*) as order_count FROM orders;
SELECT 'Products table:' as info;
SELECT COUNT(*) as product_count FROM products;
-- 检查视图是否正常
SELECT 'Order summary view:' as info;
SELECT * FROM order_summary LIMIT 3;
-- 检查存储过程是否存在
SHOW PROCEDURE STATUS WHERE Db = 'backup_demo';
-- 检查触发器是否存在
SHOW TRIGGERS FROM backup_demo;
物理备份
使用文件系统备份
# 1. 停机备份(最安全但需要停机)
# 停止 MySQL 服务
sudo systemctl stop mysql
# 或者
sudo service mysql stop
# 复制数据目录
sudo cp -R /var/lib/mysql /backup/mysql_physical_$(date +%Y%m%d_%H%M%S)
# 启动 MySQL 服务
sudo systemctl start mysql
# 2. 热备份(使用 rsync,风险较高)
# 注意:这种方法可能导致数据不一致,不推荐用于生产环境
rsync -av /var/lib/mysql/ /backup/mysql_hot_backup/
# 3. 使用 LVM 快照备份(推荐)
# 创建 LVM 快照
sudo lvcreate -L 10G -s -n mysql_snapshot /dev/vg0/mysql_lv
# 挂载快照
sudo mkdir /mnt/mysql_snapshot
sudo mount /dev/vg0/mysql_snapshot /mnt/mysql_snapshot
# 从快照复制数据
sudo cp -R /mnt/mysql_snapshot /backup/mysql_lvm_$(date +%Y%m%d_%H%M%S)
# 卸载并删除快照
sudo umount /mnt/mysql_snapshot
sudo lvremove -f /dev/vg0/mysql_snapshot
# 4. 查看 MySQL 数据目录位置
-- 在 MySQL 中执行
SHOW VARIABLES LIKE 'datadir';
SHOW VARIABLES LIKE 'innodb_data_home_dir';
SHOW VARIABLES LIKE 'log_bin_basename';
使用 MySQL Enterprise Backup (MEB)
# MySQL Enterprise Backup 是 MySQL 企业版的官方物理备份工具
# 注意:这是商业版本的功能,社区版不包含
# 1. 完整备份
mysqlbackup --user=backup_user --password=password \
--backup-dir=/backup/meb_full_$(date +%Y%m%d_%H%M%S) \
backup-and-apply-log
# 2. 增量备份
mysqlbackup --user=backup_user --password=password \
--backup-dir=/backup/meb_incremental_$(date +%Y%m%d_%H%M%S) \
--incremental \
--incremental-base=dir:/backup/meb_full_20240120_100000 \
backup
# 3. 压缩备份
mysqlbackup --user=backup_user --password=password \
--backup-dir=/backup/meb_compressed_$(date +%Y%m%d_%H%M%S) \
--compress \
backup-and-apply-log
# 4. 恢复备份
mysqlbackup --backup-dir=/backup/meb_full_20240120_100000 \
copy-back
# 注意:社区版用户可以考虑使用 Percona XtraBackup 作为替代
使用 Percona XtraBackup
# Percona XtraBackup 是开源的 MySQL 物理备份工具
# 支持热备份,不需要停机
# 安装 Percona XtraBackup
# Ubuntu/Debian:
# sudo apt-get install percona-xtrabackup-80
# CentOS/RHEL:
# sudo yum install percona-xtrabackup-80
# 1. 完整备份
xtrabackup --user=backup_user --password=password \
--backup \
--target-dir=/backup/xtrabackup_full_$(date +%Y%m%d_%H%M%S)
# 2. 准备备份(应用日志)
xtrabackup --prepare \
--target-dir=/backup/xtrabackup_full_20240120_100000
# 3. 增量备份
# 首先需要一个完整备份作为基础
xtrabackup --user=backup_user --password=password \
--backup \
--target-dir=/backup/xtrabackup_base
# 第一次增量备份
xtrabackup --user=backup_user --password=password \
--backup \
--target-dir=/backup/xtrabackup_inc1 \
--incremental-basedir=/backup/xtrabackup_base
# 第二次增量备份
xtrabackup --user=backup_user --password=password \
--backup \
--target-dir=/backup/xtrabackup_inc2 \
--incremental-basedir=/backup/xtrabackup_inc1
# 4. 准备增量备份
# 准备基础备份
xtrabackup --prepare --apply-log-only \
--target-dir=/backup/xtrabackup_base
# 应用第一个增量备份
xtrabackup --prepare --apply-log-only \
--target-dir=/backup/xtrabackup_base \
--incremental-dir=/backup/xtrabackup_inc1
# 应用第二个增量备份
xtrabackup --prepare \
--target-dir=/backup/xtrabackup_base \
--incremental-dir=/backup/xtrabackup_inc2
# 5. 恢复备份
# 停止 MySQL 服务
sudo systemctl stop mysql
# 清空数据目录
sudo rm -rf /var/lib/mysql/*
# 恢复数据
xtrabackup --copy-back \
--target-dir=/backup/xtrabackup_base
# 修改权限
sudo chown -R mysql:mysql /var/lib/mysql
# 启动 MySQL 服务
sudo systemctl start mysql
# 6. 压缩备份
xtrabackup --user=backup_user --password=password \
--backup \
--compress \
--target-dir=/backup/xtrabackup_compressed_$(date +%Y%m%d_%H%M%S)
# 解压备份
xtrabackup --decompress \
--target-dir=/backup/xtrabackup_compressed_20240120_100000
# 删除压缩文件
find /backup/xtrabackup_compressed_20240120_100000 -name "*.qp" -delete
二进制日志备份
配置二进制日志
-- 查看二进制日志配置
SHOW VARIABLES LIKE 'log_bin%';
SHOW VARIABLES LIKE 'binlog%';
SHOW VARIABLES LIKE 'expire_logs_days';
-- 查看二进制日志文件
SHOW BINARY LOGS;
-- 查看当前二进制日志位置
SHOW MASTER STATUS;
-- 查看二进制日志内容
-- SHOW BINLOG EVENTS IN 'mysql-bin.000001';
-- 配置二进制日志(在 my.cnf 中)
-- [mysqld]
-- log-bin=mysql-bin
-- binlog-format=ROW
-- expire-logs-days=7
-- max-binlog-size=100M
-- sync-binlog=1
-- 手动刷新二进制日志
FLUSH BINARY LOGS;
-- 清理旧的二进制日志
-- PURGE BINARY LOGS TO 'mysql-bin.000010';
-- PURGE BINARY LOGS BEFORE '2024-01-01 00:00:00';
使用二进制日志进行点时间恢复
# 1. 模拟数据变更和误操作
# 首先记录当前时间和二进制日志位置
mysql -u root -p -e "SHOW MASTER STATUS;"
echo "Current time: $(date)"
# 在 MySQL 中执行一些操作
mysql -u root -p backup_demo << EOF
-- 插入一些新数据
INSERT INTO users (username, email, password_hash) VALUES
('frank', 'frank@example.com', 'hash6'),
('grace', 'grace@example.com', 'hash7');
-- 更新一些数据
UPDATE products SET price = price * 1.1 WHERE category = 'Electronics';
-- 记录这个时间点
SELECT NOW() as 'Good state timestamp';
EOF
# 等待一段时间,然后模拟误操作
sleep 5
mysql -u root -p backup_demo << EOF
-- 误删除数据(模拟事故)
DELETE FROM users WHERE username IN ('frank', 'grace');
DROP TABLE products;
SELECT NOW() as 'Accident timestamp';
EOF
# 2. 点时间恢复过程
# 首先从完整备份恢复
mysql -u root -p << EOF
DROP DATABASE IF EXISTS backup_demo;
CREATE DATABASE backup_demo;
EOF
# 恢复完整备份
mysql -u root -p backup_demo < backup_demo_full.sql
# 3. 使用 mysqlbinlog 恢复到特定时间点
# 找到需要恢复的二进制日志文件
mysql -u root -p -e "SHOW BINARY LOGS;"
# 查看二进制日志内容,找到误操作的时间点
mysqlbinlog /var/lib/mysql/mysql-bin.000001 | grep -A 5 -B 5 "DELETE FROM users"
# 恢复到误操作之前的时间点
# 假设误操作发生在 2024-01-20 14:30:00
mysqlbinlog --stop-datetime="2024-01-20 14:30:00" \
/var/lib/mysql/mysql-bin.000001 | mysql -u root -p backup_demo
# 4. 基于位置的恢复(更精确)
# 查看特定位置的日志
mysqlbinlog --start-position=1000 --stop-position=2000 \
/var/lib/mysql/mysql-bin.000001
# 恢复到特定位置
mysqlbinlog --stop-position=1500 \
/var/lib/mysql/mysql-bin.000001 | mysql -u root -p backup_demo
# 5. 跳过特定事务
# 如果需要跳过某个有问题的事务
mysqlbinlog --start-position=1000 --stop-position=1400 \
/var/lib/mysql/mysql-bin.000001 | mysql -u root -p backup_demo
mysqlbinlog --start-position=1600 \
/var/lib/mysql/mysql-bin.000001 | mysql -u root -p backup_demo
二进制日志管理脚本
#!/bin/bash
# binlog_backup.sh - 二进制日志备份脚本
MYSQL_USER="root"
MYSQL_PASS="your_password"
BACKUP_DIR="/backup/binlogs"
DATE=$(date +%Y%m%d_%H%M%S)
# 创建备份目录
mkdir -p $BACKUP_DIR
# 获取当前二进制日志文件列表
mysql -u $MYSQL_USER -p$MYSQL_PASS -e "SHOW BINARY LOGS;" | \
awk 'NR>1 {print $1}' > /tmp/binlog_list.txt
# 刷新二进制日志,生成新的日志文件
mysql -u $MYSQL_USER -p$MYSQL_PASS -e "FLUSH BINARY LOGS;"
# 备份除最新文件外的所有二进制日志
while read binlog_file; do
if [ "$binlog_file" != "$(tail -1 /tmp/binlog_list.txt)" ]; then
cp /var/lib/mysql/$binlog_file $BACKUP_DIR/
echo "Backed up: $binlog_file"
fi
done < /tmp/binlog_list.txt
# 压缩备份的二进制日志
cd $BACKUP_DIR
tar -czf binlogs_$DATE.tar.gz mysql-bin.*
rm -f mysql-bin.*
# 清理旧的备份(保留30天)
find $BACKUP_DIR -name "binlogs_*.tar.gz" -mtime +30 -delete
echo "Binary log backup completed: binlogs_$DATE.tar.gz"
# 清理 MySQL 中的旧二进制日志(保留7天)
mysql -u $MYSQL_USER -p$MYSQL_PASS -e \
"PURGE BINARY LOGS BEFORE DATE_SUB(NOW(), INTERVAL 7 DAY);"
rm -f /tmp/binlog_list.txt
备份策略和自动化
备份策略设计
📅 每日策略
增量备份 + 二进制日志备份
📊 每周策略
差异备份 + 备份验证
🗓️ 每月策略
完整备份 + 异地存储
🔄 实时策略
主从复制 + 读写分离
时间 | 备份类型 | 备份方法 | 保留期限 | 存储位置 |
---|---|---|---|---|
每天 02:00 | 增量备份 | XtraBackup | 7天 | 本地存储 |
每天 03:00 | 二进制日志 | mysqlbinlog | 30天 | 本地存储 |
每周日 01:00 | 完整备份 | mysqldump | 4周 | 本地 + 远程 |
每月1日 00:00 | 完整备份 | XtraBackup | 12个月 | 异地存储 |
实时 | 主从复制 | MySQL Replication | 持续 | 从服务器 |
自动化备份脚本
#!/bin/bash
# comprehensive_backup.sh - 综合备份脚本
# 配置参数
MYSQL_USER="backup_user"
MYSQL_PASS="backup_password"
MYSQL_HOST="localhost"
BACKUP_BASE_DIR="/backup/mysql"
LOG_FILE="/var/log/mysql_backup.log"
EMAIL_ALERT="admin@example.com"
RETENTION_DAYS=30
# 创建日期标识
DATE=$(date +%Y%m%d_%H%M%S)
DAY_OF_WEEK=$(date +%u) # 1=Monday, 7=Sunday
DAY_OF_MONTH=$(date +%d)
# 日志函数
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a $LOG_FILE
}
# 错误处理函数
handle_error() {
log_message "ERROR: $1"
echo "Backup failed: $1" | mail -s "MySQL Backup Failed" $EMAIL_ALERT
exit 1
}
# 检查 MySQL 连接
check_mysql_connection() {
mysql -h $MYSQL_HOST -u $MYSQL_USER -p$MYSQL_PASS -e "SELECT 1;" > /dev/null 2>&1
if [ $? -ne 0 ]; then
handle_error "Cannot connect to MySQL server"
fi
}
# 创建备份目录
create_backup_dirs() {
mkdir -p $BACKUP_BASE_DIR/{daily,weekly,monthly,binlogs}
if [ $? -ne 0 ]; then
handle_error "Cannot create backup directories"
fi
}
# 每日增量备份
daily_backup() {
log_message "Starting daily incremental backup"
DAILY_DIR="$BACKUP_BASE_DIR/daily/$DATE"
mkdir -p $DAILY_DIR
# 使用 XtraBackup 进行增量备份
if [ -d "$BACKUP_BASE_DIR/weekly/latest" ]; then
BASE_DIR="$BACKUP_BASE_DIR/weekly/latest"
else
# 如果没有周备份,创建完整备份
BASE_DIR="$DAILY_DIR"
xtrabackup --user=$MYSQL_USER --password=$MYSQL_PASS \
--backup --target-dir=$BASE_DIR
fi
if [ "$BASE_DIR" != "$DAILY_DIR" ]; then
xtrabackup --user=$MYSQL_USER --password=$MYSQL_PASS \
--backup --target-dir=$DAILY_DIR \
--incremental-basedir=$BASE_DIR
fi
if [ $? -eq 0 ]; then
log_message "Daily backup completed successfully"
else
handle_error "Daily backup failed"
fi
}
# 每周完整备份
weekly_backup() {
log_message "Starting weekly full backup"
WEEKLY_DIR="$BACKUP_BASE_DIR/weekly/$DATE"
mkdir -p $WEEKLY_DIR
# 使用 mysqldump 进行逻辑备份
mysqldump -h $MYSQL_HOST -u $MYSQL_USER -p$MYSQL_PASS \
--single-transaction --routines --triggers --events \
--all-databases | gzip > $WEEKLY_DIR/full_backup.sql.gz
if [ $? -eq 0 ]; then
# 创建符号链接指向最新备份
ln -sfn $WEEKLY_DIR $BACKUP_BASE_DIR/weekly/latest
log_message "Weekly backup completed successfully"
else
handle_error "Weekly backup failed"
fi
}
# 每月归档备份
monthly_backup() {
log_message "Starting monthly archive backup"
MONTHLY_DIR="$BACKUP_BASE_DIR/monthly/$DATE"
mkdir -p $MONTHLY_DIR
# 使用 XtraBackup 进行物理备份
xtrabackup --user=$MYSQL_USER --password=$MYSQL_PASS \
--backup --compress --target-dir=$MONTHLY_DIR
if [ $? -eq 0 ]; then
log_message "Monthly backup completed successfully"
# 上传到远程存储(示例:rsync 到远程服务器)
# rsync -av $MONTHLY_DIR/ backup-server:/remote/backup/mysql/
else
handle_error "Monthly backup failed"
fi
}
# 二进制日志备份
binlog_backup() {
log_message "Starting binary log backup"
BINLOG_DIR="$BACKUP_BASE_DIR/binlogs/$DATE"
mkdir -p $BINLOG_DIR
# 获取二进制日志文件列表
mysql -h $MYSQL_HOST -u $MYSQL_USER -p$MYSQL_PASS \
-e "SHOW BINARY LOGS;" | awk 'NR>1 {print $1}' > /tmp/binlog_list.txt
# 刷新日志
mysql -h $MYSQL_HOST -u $MYSQL_USER -p$MYSQL_PASS -e "FLUSH BINARY LOGS;"
# 备份二进制日志文件
while read binlog_file; do
if [ -f "/var/lib/mysql/$binlog_file" ]; then
cp /var/lib/mysql/$binlog_file $BINLOG_DIR/
fi
done < /tmp/binlog_list.txt
# 压缩备份
cd $BINLOG_DIR && tar -czf ../binlogs_$DATE.tar.gz .
rm -rf $BINLOG_DIR
log_message "Binary log backup completed"
rm -f /tmp/binlog_list.txt
}
# 清理旧备份
cleanup_old_backups() {
log_message "Cleaning up old backups"
# 清理每日备份(保留7天)
find $BACKUP_BASE_DIR/daily -type d -mtime +7 -exec rm -rf {} +
# 清理每周备份(保留4周)
find $BACKUP_BASE_DIR/weekly -type d -mtime +28 -exec rm -rf {} +
# 清理二进制日志备份(保留30天)
find $BACKUP_BASE_DIR/binlogs -name "*.tar.gz" -mtime +$RETENTION_DAYS -delete
log_message "Cleanup completed"
}
# 验证备份
verify_backup() {
log_message "Verifying backup integrity"
# 检查最新的备份文件
LATEST_BACKUP=$(find $BACKUP_BASE_DIR -name "*.sql.gz" -o -name "*.tar.gz" | \
sort -r | head -1)
if [ -n "$LATEST_BACKUP" ]; then
# 检查文件完整性
if file $LATEST_BACKUP | grep -q "gzip compressed"; then
gzip -t $LATEST_BACKUP
if [ $? -eq 0 ]; then
log_message "Backup verification passed: $LATEST_BACKUP"
else
handle_error "Backup verification failed: $LATEST_BACKUP"
fi
fi
fi
}
# 发送成功通知
send_success_notification() {
BACKUP_SIZE=$(du -sh $BACKUP_BASE_DIR | cut -f1)
echo "MySQL backup completed successfully.
Total backup size: $BACKUP_SIZE
Date: $(date)" | \
mail -s "MySQL Backup Success" $EMAIL_ALERT
}
# 主执行流程
main() {
log_message "Starting MySQL backup process"
check_mysql_connection
create_backup_dirs
# 根据日期决定备份类型
if [ $DAY_OF_MONTH -eq 1 ]; then
# 每月1日执行月备份
monthly_backup
elif [ $DAY_OF_WEEK -eq 7 ]; then
# 每周日执行周备份
weekly_backup
else
# 其他时间执行日备份
daily_backup
fi
# 每天都执行二进制日志备份
binlog_backup
# 清理旧备份
cleanup_old_backups
# 验证备份
verify_backup
# 发送成功通知
send_success_notification
log_message "MySQL backup process completed successfully"
}
# 执行主函数
main
使用 Crontab 调度备份
# 编辑 crontab
crontab -e
# 添加以下备份任务
# 每天凌晨2点执行备份
0 2 * * * /path/to/comprehensive_backup.sh >> /var/log/mysql_backup_cron.log 2>&1
# 每周日凌晨1点执行完整备份
0 1 * * 0 /path/to/weekly_full_backup.sh >> /var/log/mysql_backup_cron.log 2>&1
# 每月1日凌晨0点执行月度备份
0 0 1 * * /path/to/monthly_backup.sh >> /var/log/mysql_backup_cron.log 2>&1
# 每小时备份二进制日志
0 * * * * /path/to/binlog_backup.sh >> /var/log/mysql_backup_cron.log 2>&1
# 查看 crontab 任务
crontab -l
# 监控 cron 日志
tail -f /var/log/mysql_backup_cron.log
# 备份脚本权限设置
chmod +x /path/to/comprehensive_backup.sh
chmod +x /path/to/weekly_full_backup.sh
chmod +x /path/to/monthly_backup.sh
chmod +x /path/to/binlog_backup.sh
# 创建专用的备份用户
mysql -u root -p << EOF
CREATE USER 'backup_user'@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT, SHOW DATABASES, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER, RELOAD, REPLICATION CLIENT ON *.* TO 'backup_user'@'localhost';
FLUSH PRIVILEGES;
EOF
灾难恢复
灾难恢复计划
🚨 灾难恢复步骤
- 评估损失:确定数据丢失的范围和原因
- 停止服务:防止进一步的数据损坏
- 准备环境:准备恢复所需的硬件和软件环境
- 恢复数据:按照恢复计划执行数据恢复
- 验证数据:检查恢复数据的完整性和一致性
- 测试应用:验证应用程序功能正常
- 恢复服务:重新启动生产服务
- 监控系统:密切监控系统运行状态
- 总结经验:分析事故原因,改进备份策略
完整的灾难恢复示例
#!/bin/bash
# disaster_recovery.sh - 灾难恢复脚本
# 配置参数
MYSQL_USER="root"
MYSQL_PASS="recovery_password"
BACKUP_DIR="/backup/mysql"
RECOVERY_LOG="/var/log/mysql_recovery.log"
TARGET_TIME="2024-01-20 14:30:00" # 恢复到的目标时间
# 日志函数
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a $RECOVERY_LOG
}
# 第1步:停止 MySQL 服务
stop_mysql() {
log_message "Stopping MySQL service"
sudo systemctl stop mysql
if [ $? -eq 0 ]; then
log_message "MySQL service stopped successfully"
else
log_message "Failed to stop MySQL service"
exit 1
fi
}
# 第2步:备份当前损坏的数据(如果可能)
backup_corrupted_data() {
log_message "Backing up corrupted data for analysis"
CORRUPTED_BACKUP_DIR="/backup/corrupted_$(date +%Y%m%d_%H%M%S)"
mkdir -p $CORRUPTED_BACKUP_DIR
if [ -d "/var/lib/mysql" ]; then
cp -R /var/lib/mysql $CORRUPTED_BACKUP_DIR/
log_message "Corrupted data backed up to $CORRUPTED_BACKUP_DIR"
fi
}
# 第3步:清理数据目录
clean_data_directory() {
log_message "Cleaning MySQL data directory"
sudo rm -rf /var/lib/mysql/*
sudo mkdir -p /var/lib/mysql
sudo chown mysql:mysql /var/lib/mysql
log_message "Data directory cleaned"
}
# 第4步:恢复基础备份
restore_base_backup() {
log_message "Restoring base backup"
# 查找最新的完整备份
LATEST_FULL_BACKUP=$(find $BACKUP_DIR/weekly -name "full_backup.sql.gz" | sort -r | head -1)
if [ -z "$LATEST_FULL_BACKUP" ]; then
log_message "No full backup found"
exit 1
fi
log_message "Using backup: $LATEST_FULL_BACKUP"
# 启动 MySQL(临时)
sudo systemctl start mysql
sleep 10
# 恢复备份
gunzip < $LATEST_FULL_BACKUP | mysql -u $MYSQL_USER -p$MYSQL_PASS
if [ $? -eq 0 ]; then
log_message "Base backup restored successfully"
else
log_message "Failed to restore base backup"
exit 1
fi
}
# 第5步:应用二进制日志进行点时间恢复
apply_binlog_recovery() {
log_message "Applying binary log for point-in-time recovery"
# 查找需要应用的二进制日志文件
BINLOG_FILES=$(find $BACKUP_DIR/binlogs -name "*.tar.gz" | sort)
for binlog_archive in $BINLOG_FILES; do
log_message "Processing binlog archive: $binlog_archive"
# 解压二进制日志
TEMP_DIR="/tmp/binlog_recovery_$(date +%s)"
mkdir -p $TEMP_DIR
tar -xzf $binlog_archive -C $TEMP_DIR
# 应用二进制日志到目标时间
for binlog_file in $TEMP_DIR/mysql-bin.*; do
if [ -f "$binlog_file" ]; then
mysqlbinlog --stop-datetime="$TARGET_TIME" $binlog_file | \
mysql -u $MYSQL_USER -p$MYSQL_PASS
fi
done
# 清理临时文件
rm -rf $TEMP_DIR
done
log_message "Binary log recovery completed"
}
# 第6步:验证恢复结果
verify_recovery() {
log_message "Verifying recovery results"
# 检查数据库连接
mysql -u $MYSQL_USER -p$MYSQL_PASS -e "SELECT 1;" > /dev/null 2>&1
if [ $? -ne 0 ]; then
log_message "Cannot connect to MySQL after recovery"
exit 1
fi
# 检查关键表的数据
mysql -u $MYSQL_USER -p$MYSQL_PASS -e "
SELECT 'Database recovery verification' as status;
SHOW DATABASES;
USE backup_demo;
SELECT COUNT(*) as user_count FROM users;
SELECT COUNT(*) as order_count FROM orders;
SELECT COUNT(*) as product_count FROM products;
"
log_message "Recovery verification completed"
}
# 第7步:重启服务
restart_services() {
log_message "Restarting MySQL service"
sudo systemctl restart mysql
if [ $? -eq 0 ]; then
log_message "MySQL service restarted successfully"
else
log_message "Failed to restart MySQL service"
exit 1
fi
}
# 主恢复流程
main_recovery() {
log_message "Starting disaster recovery process"
stop_mysql
backup_corrupted_data
clean_data_directory
restore_base_backup
apply_binlog_recovery
verify_recovery
restart_services
log_message "Disaster recovery completed successfully"
}
# 执行恢复
main_recovery
恢复测试和验证
-- 创建恢复测试脚本
DELIMITER //
CREATE PROCEDURE TestRecovery()
BEGIN
DECLARE user_count INT;
DECLARE order_count INT;
DECLARE product_count INT;
DECLARE test_result VARCHAR(100);
-- 检查数据完整性
SELECT COUNT(*) INTO user_count FROM users;
SELECT COUNT(*) INTO order_count FROM orders;
SELECT COUNT(*) INTO product_count FROM products;
-- 验证数据一致性
IF user_count > 0 AND order_count > 0 AND product_count > 0 THEN
SET test_result = 'Recovery test PASSED';
ELSE
SET test_result = 'Recovery test FAILED';
END IF;
-- 输出测试结果
SELECT
test_result as result,
user_count as users,
order_count as orders,
product_count as products,
NOW() as test_time;
-- 检查外键约束
SELECT 'Foreign key check:' as info;
SELECT
o.id,
o.user_id,
u.username
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE u.id IS NULL
LIMIT 5;
-- 检查数据范围
SELECT 'Data range check:' as info;
SELECT
MIN(created_at) as earliest_record,
MAX(created_at) as latest_record
FROM users;
END //
DELIMITER ;
-- 执行恢复测试
CALL TestRecovery();
-- 性能测试
SET @start_time = NOW(6);
SELECT
u.username,
COUNT(o.id) as order_count,
SUM(o.total_amount) as total_spent
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.username;
SET @end_time = NOW(6);
SELECT
'Performance test' as test_type,
TIMESTAMPDIFF(MICROSECOND, @start_time, @end_time) / 1000 as execution_time_ms;
-- 清理测试对象
DROP PROCEDURE IF EXISTS TestRecovery;
最佳实践和注意事项
🎯 备份最佳实践:
- 定期测试恢复:至少每月测试一次完整的恢复流程
- 多层备份策略:结合逻辑备份、物理备份和实时复制
- 异地存储:重要备份必须存储在不同地理位置
- 自动化监控:建立备份成功/失败的自动告警机制
- 文档化流程:详细记录备份和恢复的操作步骤
- 权限控制:严格控制备份文件的访问权限
- 加密保护:对敏感数据的备份进行加密
⚠️ 常见错误和注意事项:
- 备份未测试:备份文件损坏或无法恢复时才发现
- 权限问题:恢复时文件权限不正确导致 MySQL 无法启动
- 版本不兼容:不同 MySQL 版本间的备份恢复兼容性问题
- 磁盘空间不足:恢复时目标磁盘空间不够
- 字符集问题:备份和恢复时字符集设置不一致
- 时区问题:时间戳数据在不同时区间的转换问题
- 依赖关系:忽略外键约束和触发器的恢复顺序
备份安全和合规
# 1. 备份文件加密
# 使用 GPG 加密备份文件
mysqldump -u root -p backup_demo | gzip | \
gpg --cipher-algo AES256 --compress-algo 2 --symmetric \
--output backup_demo_encrypted_$(date +%Y%m%d).sql.gz.gpg
# 解密备份文件
gpg --decrypt backup_demo_encrypted_20240120.sql.gz.gpg | \
gunzip | mysql -u root -p backup_demo_restored
# 2. 使用 OpenSSL 加密
mysqldump -u root -p backup_demo | \
openssl enc -aes-256-cbc -salt -out backup_demo_ssl_$(date +%Y%m%d).sql.enc
# 解密 OpenSSL 备份
openssl enc -aes-256-cbc -d -in backup_demo_ssl_20240120.sql.enc | \
mysql -u root -p backup_demo_restored
# 3. 备份文件完整性校验
# 创建校验和
sha256sum backup_demo_full.sql > backup_demo_full.sql.sha256
# 验证校验和
sha256sum -c backup_demo_full.sql.sha256
# 4. 安全的备份传输
# 使用 rsync over SSH
rsync -avz -e ssh /backup/mysql/ user@backup-server:/remote/backup/mysql/
# 使用 scp 传输
scp backup_demo_full.sql.gz user@backup-server:/remote/backup/
# 5. 备份访问日志
# 记录备份文件访问
auditctl -w /backup/mysql -p rwxa -k mysql_backup_access
# 查看访问日志
ausearch -k mysql_backup_access
-- 清理示例数据和对象
DROP DATABASE IF EXISTS backup_demo;
SELECT 'Backup and recovery tutorial completed. All example objects cleaned up.' AS message;