feat(mysql): 添加MySQL全量备份与还原脚本及文档
新增备份脚本backup-sync.sh和还原脚本restore-sync.sh,用于处理严重复制冲突场景 更新故障分析文档,补充全量备份还原的操作流程和注意事项 添加测试流程模板文件_TEST_FLOW_TEMPLATE.md 更新SKILL.md文档,增加测试流程分析相关内容
This commit is contained in:
@@ -11,6 +11,7 @@ description: g3fo 系统相关文档。管理业务流程说明、服务职责
|
|||||||
- **查业务流程**:查阅 `references/business_flows/` (如订单生命周期、用户入金等跨服务流程)。
|
- **查业务流程**:查阅 `references/business_flows/` (如订单生命周期、用户入金等跨服务流程)。
|
||||||
- **查服务职责**:查阅 `references/services/` (如 `g3fo-trade-service.md` 定义的具体功能)。
|
- **查服务职责**:查阅 `references/services/` (如 `g3fo-trade-service.md` 定义的具体功能)。
|
||||||
- **查架构概览**:查阅 `references/architecture/domain_overview.md`。
|
- **查架构概览**:查阅 `references/architecture/domain_overview.md`。
|
||||||
|
- **分析测试流程**:参考 `references/business_flows/_TEST_FLOW_TEMPLATE.md`。
|
||||||
- **查标准运维流程**:去 `references/middleware/` 或 `references/system-init/`。
|
- **查标准运维流程**:去 `references/middleware/` 或 `references/system-init/`。
|
||||||
- **查具体环境/客户资产**:
|
- **查具体环境/客户资产**:
|
||||||
- 内部环境(Dev/UAT):查阅 `references/inventory/internal.md`。
|
- 内部环境(Dev/UAT):查阅 `references/inventory/internal.md`。
|
||||||
@@ -19,7 +20,7 @@ description: g3fo 系统相关文档。管理业务流程说明、服务职责
|
|||||||
## 2. 问答逻辑 (AI 引导)
|
## 2. 问答逻辑 (AI 引导)
|
||||||
|
|
||||||
### A. 业务逻辑问答
|
### A. 业务逻辑问答
|
||||||
1. **识别范围**:判断用户问题涉及哪些业务流程或服务。
|
1. **识别范围**:判断用户问题涉及哪些业务流程 or 服务。
|
||||||
2. **加载文档**:
|
2. **加载文档**:
|
||||||
- 优先查找 `business_flows/` 下的相关流程文档。
|
- 优先查找 `business_flows/` 下的相关流程文档。
|
||||||
- 结合 `services/` 下涉及的服务文档,深入了解具体职责。
|
- 结合 `services/` 下涉及的服务文档,深入了解具体职责。
|
||||||
@@ -45,14 +46,29 @@ description: g3fo 系统相关文档。管理业务流程说明、服务职责
|
|||||||
- **格式 A (Docker 模式)**:完整的 `docker exec -it mysql mysql -uroot -pafe123456 -h127.0.0.1 -e "SQL_CONTENT"`。
|
- **格式 A (Docker 模式)**:完整的 `docker exec -it mysql mysql -uroot -pafe123456 -h127.0.0.1 -e "SQL_CONTENT"`。
|
||||||
- **格式 B (纯 SQL 模式)**:仅包含 SQL 语句本身,方便在 GUI 工具中执行。
|
- **格式 B (纯 SQL 模式)**:仅包含 SQL 语句本身,方便在 GUI 工具中执行。
|
||||||
|
|
||||||
|
### C. 测试流程分析 (New)
|
||||||
|
1. **需求解析**:识别用户提出的业务关键词(如“下单”、“撤单”、“充值”)。
|
||||||
|
2. **文档检索**:
|
||||||
|
- 查找 `business_flows/` 下的业务流程文档。
|
||||||
|
- 查找 `services/` 下涉及的服务定义。
|
||||||
|
3. **代码溯源**:
|
||||||
|
- 根据文档中提到的服务,在项目中搜索对应的 Controller、Service 或核心逻辑代码。
|
||||||
|
- **重点搜索路径**:
|
||||||
|
- REST 接口:`com.afe.g3fo.*.controller` 或 `com.afe.g3fo.*.api`。
|
||||||
|
- Dubbo 接口:`com.afe.g3fo.*.facade`(定义)及 `com.afe.g3fo.*.facade.impl`(实现)。
|
||||||
|
- **重点搜索注解**:`@RequestMapping`, `@PostMapping`, `@GetMapping`, `@Service`, `@DubboService` 等。
|
||||||
|
4. **表格生成**:汇总信息,输出包含“步骤”、“涉及服务”、“关键接口/代码逻辑”、“测试内容/预期结果”的表格。
|
||||||
|
- **格式参考**:`references/business_flows/_TEST_FLOW_TEMPLATE.md`。
|
||||||
|
|
||||||
## 3. 示例 Prompt (用户可参考)
|
## 3. 示例 Prompt (用户可参考)
|
||||||
- **业务咨询**:
|
- **业务咨询**:
|
||||||
|
- “我想测试下单业务流程,请分析代码并列出详细的测试步骤表格。”
|
||||||
- “g3fo 系统中,一个订单从下单到成交会经过哪些服务?请结合 `references/business_flows/` 下的相关文档回答。”
|
- “g3fo 系统中,一个订单从下单到成交会经过哪些服务?请结合 `references/business_flows/` 下的相关文档回答。”
|
||||||
- “`g3fo-margin-service` 负责哪些核心逻辑?它的上下游依赖是谁?”
|
- “`g3fo-margin-service` 负责哪些核心逻辑?它的上下游依赖是谁?”
|
||||||
- **部署配置**:
|
- **部署配置**:
|
||||||
- “请结合 `references/inventory/internal.md` 中的 `UAT` 环境变量,参考 `references/middleware/keepalived/deploy.md` 部署文档,为我生成 Node A 的 `keepalived.conf` 配置文件。”
|
- “请结合 `references/inventory/internal.md` 中的 `UAT` 环境变量,参考 `references/middleware/keepalived/deploy.md` 部署文档,为我生成 Node A 的 `keepalived.conf` 配置文件。”
|
||||||
- **故障排查**:
|
- **故障排查**:
|
||||||
- “我的 MySQL 出现了复制冲突,报错 `Duplicate entry`,请根据 `references/middleware/mysql/fault-analysis.md` 提供排查脚本和修复建议。”
|
- “我的 MySQL 出现了复制冲突,报错 `Duplicate entry`,请根据 `references/middleware/mysql/fault-analysis.md` 提供排查脚本 and 修复建议。”
|
||||||
|
|
||||||
## 4. 资源地图
|
## 4. 资源地图
|
||||||
- **业务与服务**:
|
- **业务与服务**:
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Test Flow Template
|
||||||
|
|
||||||
|
## Business Flow: [Flow Name]
|
||||||
|
|
||||||
|
| 步骤 | 涉及服务 | 关键接口/代码逻辑 | 测试内容/预期结果 |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| 1. [步骤名称] | `[服务名]` | `[类名.方法名()]` | [测试要点及预期行为描述] |
|
||||||
|
| 2. [步骤名称] | `[服务名]` | `[类名.方法名()]` | [测试要点及预期行为描述] |
|
||||||
|
|
||||||
|
### Testing Notes
|
||||||
|
- **Prerequisites**: [e.g., User must be logged in, Balance > 0]
|
||||||
|
- **Key Data Points**: [e.g., OrderID, TransactionID]
|
||||||
|
- **Edge Cases to Cover**:
|
||||||
|
- [Edge Case 1]
|
||||||
|
- [Edge Case 2]
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
TAG=""
|
||||||
|
while getopts "t:" opt; do
|
||||||
|
case $opt in
|
||||||
|
t) TAG="$OPTARG" ;;
|
||||||
|
*) echo "Invalid option: -$OPTARG" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
shift $((OPTIND-1))
|
||||||
|
|
||||||
|
cd /data/backup/mysql || exit 1
|
||||||
|
if [ -n "$TAG" ]; then
|
||||||
|
BACKUP_DIR="./$(date +%Y%m%d%H%M%S)_${TAG}"
|
||||||
|
else
|
||||||
|
BACKUP_DIR="./$(date +%Y%m%d%H%M%S)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
|
||||||
|
MYSQL_USER="root"
|
||||||
|
MYSQL_PASSWORD="ENC(VN13kACOdhyl7xYqbgDMTMP0fK4dwoCkFe0AQW0VEt7/tpwx96GzRVY6eIpwlASM)"
|
||||||
|
MYSQL_HOST="127.0.0.1"
|
||||||
|
|
||||||
|
if [[ "$MYSQL_PASSWORD" =~ ^ENC\(.*\)$ ]]; then
|
||||||
|
encrypted_pwd="${MYSQL_PASSWORD:4:-1}"
|
||||||
|
if ! MYSQL_PASSWORD=$(java -cp /data/secure/jasypt-agent.jar com.jasypt.agent.DecryptionCLI $encrypted_pwd \
|
||||||
|
2>&1); then
|
||||||
|
echo -e "\033[31m[Fatal Error] Password decryption failed. Reason:"
|
||||||
|
echo "$MYSQL_PASSWORD" | grep -iE "error|exception"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Function to execute MySQL command
|
||||||
|
mysql_exec() {
|
||||||
|
docker exec -i mysql mysql -u "$MYSQL_USER" -p"$MYSQL_PASSWORD" -h "$MYSQL_HOST" -N -e "$1" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to restore MySQL state (called on exit)
|
||||||
|
restore_mysql_state() {
|
||||||
|
if [ "$READ_ONLY_SET" = "true" ]; then
|
||||||
|
echo "Restoring MySQL state..."
|
||||||
|
mysql_exec "SET GLOBAL read_only = OFF;" || echo "[Warning] Failed to set read_only = OFF"
|
||||||
|
echo "MySQL state restored"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set trap to ensure MySQL state is restored even on error
|
||||||
|
trap restore_mysql_state EXIT ERR
|
||||||
|
|
||||||
|
# Pre-backup: Set read-only and stop slave
|
||||||
|
echo "Setting MySQL to read-only mode..."
|
||||||
|
if ! mysql_exec "SET GLOBAL read_only = ON;"; then
|
||||||
|
echo "[Critical Error] Failed to set read_only = ON"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
READ_ONLY_SET="true"
|
||||||
|
|
||||||
|
echo "Stopping slave replication..."
|
||||||
|
if ! mysql_exec "STOP REPLICA;"; then
|
||||||
|
echo "[Warning] Failed to stop replica (may not be a slave server or already stopped)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get GTID position using SELECT @@global.gtid_executed;
|
||||||
|
echo "Getting GTID position..."
|
||||||
|
GTID_EXECUTED=$(mysql_exec "SELECT @@global.gtid_executed;")
|
||||||
|
if [ -n "$GTID_EXECUTED" ]; then
|
||||||
|
# Remove newlines and literal \n strings
|
||||||
|
GTID_EXECUTED=$(echo "$GTID_EXECUTED" | tr -d '\n' | sed 's/\\n//g' | tr -s ' ')
|
||||||
|
echo "GTID Executed: $GTID_EXECUTED"
|
||||||
|
# Write GTID to file for restore use
|
||||||
|
echo "$GTID_EXECUTED" > "$BACKUP_DIR/gtid_executed.txt"
|
||||||
|
else
|
||||||
|
echo "[Warning] Failed to get GTID executed information"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get database list for backup
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
# When no arguments, get all non-system databases
|
||||||
|
databases=$(mysql_exec "SHOW DATABASES;" | grep -Ev "(Database|information_schema|performance_schema|mysql|skywalking|sys)")
|
||||||
|
|
||||||
|
# Convert to array and check empty values
|
||||||
|
readarray -t db_list <<< "$databases" || {
|
||||||
|
echo "ERROR: No valid databases found or failed to retrieve database list"
|
||||||
|
restore_mysql_state
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
else
|
||||||
|
# Use arguments as database list when provided
|
||||||
|
db_list=("$@")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if database list is empty
|
||||||
|
if [ ${#db_list[@]} -eq 0 ]; then
|
||||||
|
echo "ERROR: No databases to backup"
|
||||||
|
restore_mysql_state
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
backup_list_str="$BACKUP_DIR.7z"
|
||||||
|
# Perform backup
|
||||||
|
for db in "${db_list[@]}"; do
|
||||||
|
echo "Backing up database: $db"
|
||||||
|
backup_list_str="$backup_list_str $db"
|
||||||
|
if ! docker exec -i mysql mysqldump -u "$MYSQL_USER" -p"$MYSQL_PASSWORD" -h "$MYSQL_HOST" --set-gtid-purged=OFF --single-transaction --databases "$db" > "$BACKUP_DIR/$db.sql"; then
|
||||||
|
echo "[Critical Error] Failed to backup database $db, aborting"
|
||||||
|
restore_mysql_state
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Post-backup: Restore MySQL state (before compression)
|
||||||
|
echo "Restoring MySQL to read-write mode..."
|
||||||
|
mysql_exec "SET GLOBAL read_only = OFF;" || echo "[Warning] Failed to set read_only = OFF"
|
||||||
|
|
||||||
|
# Clear the trap since we've successfully restored
|
||||||
|
READ_ONLY_SET="false"
|
||||||
|
trap - EXIT ERR
|
||||||
|
|
||||||
|
# Compress backup files
|
||||||
|
if ! 7zz a "$BACKUP_DIR.7z" "$BACKUP_DIR" >/dev/null; then
|
||||||
|
echo "Compression failed. Please check disk space or 7z installation"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Cleanup temporary files
|
||||||
|
rm -rf "$BACKUP_DIR"
|
||||||
|
echo "Backup completed: $BACKUP_DIR.7z"
|
||||||
|
echo $backup_list_str >> backup_list.txt
|
||||||
|
|
||||||
|
echo "Backup process completed successfully"
|
||||||
@@ -99,3 +99,122 @@ BEGIN; COMMIT;
|
|||||||
SET GTID_NEXT = 'AUTOMATIC';
|
SET GTID_NEXT = 'AUTOMATIC';
|
||||||
START REPLICA;
|
START REPLICA;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 2. 全量备份还原解决冲突(适用于严重冲突场景)
|
||||||
|
|
||||||
|
当复制冲突过多、数据不一致严重,或手动修复成本过高时,可以使用全量备份还原的方式彻底解决冲突问题。此方法通过从主库(或备份库)全量备份数据,然后在从库上完全还原,实现数据一致性。
|
||||||
|
|
||||||
|
### 2.1 相关脚本
|
||||||
|
|
||||||
|
- **备份脚本**: [`backup-sync.sh`](./backup-sync.sh) - 在主库或备份库上执行全量备份
|
||||||
|
- **还原脚本**: [`restore-sync.sh`](./restore-sync.sh) - 在从库上执行全量还原
|
||||||
|
|
||||||
|
### 2.2 操作流程
|
||||||
|
|
||||||
|
#### 步骤 1: 在备份机上执行备份
|
||||||
|
|
||||||
|
在需要备份的 MySQL 服务器(通常是主库或数据一致的节点)上执行备份脚本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 备份所有数据库(默认)
|
||||||
|
cd /data/backup/mysql
|
||||||
|
./backup-sync.sh
|
||||||
|
|
||||||
|
# 或指定标签备份
|
||||||
|
./backup-sync.sh -t conflict_resolve
|
||||||
|
|
||||||
|
# 或备份指定数据库
|
||||||
|
./backup-sync.sh database1 database2
|
||||||
|
```
|
||||||
|
|
||||||
|
备份脚本会自动:
|
||||||
|
- 设置 MySQL 为只读模式
|
||||||
|
- 停止复制(如果是从库)
|
||||||
|
- 记录当前 GTID 位置
|
||||||
|
- 执行全量备份并压缩为 `.7z` 文件
|
||||||
|
- 恢复 MySQL 为读写模式
|
||||||
|
|
||||||
|
备份完成后,会在 `/data/backup/mysql/` 目录下生成 `YYYYMMDDHHMMSS.7z` 或 `YYYYMMDDHHMMSS_TAG.7z` 格式的备份文件。
|
||||||
|
|
||||||
|
#### 步骤 2: 传输备份文件到还原机
|
||||||
|
|
||||||
|
使用 `scp` 将备份文件推送到需要还原的 DB 机的 `/data/backup/mysql/` 目录:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 从备份机执行(将备份文件推送到还原机)
|
||||||
|
scp /data/backup/mysql/YYYYMMDDHHMMSS.7z user@restore-server:/data/backup/mysql/
|
||||||
|
|
||||||
|
# 示例
|
||||||
|
scp /data/backup/mysql/20250127120000.7z root@192.168.1.100:/data/backup/mysql/
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意**: 确保还原机的 `/data/backup/mysql/` 目录存在且有写入权限。
|
||||||
|
|
||||||
|
#### 步骤 3: 在还原机上执行还原
|
||||||
|
|
||||||
|
在需要还原的 MySQL 服务器(从库)上执行还原脚本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /data/backup/mysql
|
||||||
|
|
||||||
|
# 还原所有数据库
|
||||||
|
./restore-sync.sh YYYYMMDDHHMMSS.7z
|
||||||
|
|
||||||
|
# 或仅还原指定数据库
|
||||||
|
./restore-sync.sh YYYYMMDDHHMMSS.7z database1.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
还原脚本会自动:
|
||||||
|
- 设置 MySQL 为只读模式
|
||||||
|
- 停止复制
|
||||||
|
- 重置二进制日志和 GTID
|
||||||
|
- 导入备份数据(导入时禁用 binlog,避免循环复制)
|
||||||
|
- 设置 GTID purged(从备份时的 GTID 位置)
|
||||||
|
- 恢复 MySQL 为读写模式
|
||||||
|
- **自动启动复制并检查状态**
|
||||||
|
|
||||||
|
#### 步骤 4: 在备份机上启动同步(重要)
|
||||||
|
|
||||||
|
还原完成后,还原脚本会自动启动复制并显示同步状态。确认看到同步成功后,**需要在备份机(主库)上执行以下操作**:
|
||||||
|
|
||||||
|
**A. Docker 执行模式 (包含 -h127.0.0.1):**
|
||||||
|
```bash
|
||||||
|
docker exec -it mysql mysql -uroot -pafe123456 -h127.0.0.1 -e "
|
||||||
|
START REPLICA;
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
**B. 纯 SQL 模式:**
|
||||||
|
```sql
|
||||||
|
START REPLICA;
|
||||||
|
```
|
||||||
|
|
||||||
|
等待 3 秒后,检查同步状态:
|
||||||
|
|
||||||
|
**A. Docker 执行模式 (包含 -h127.0.0.1):**
|
||||||
|
```bash
|
||||||
|
sleep 3
|
||||||
|
docker exec -it mysql mysql -uroot -pafe123456 -h127.0.0.1 -e "SHOW REPLICA STATUS\G"
|
||||||
|
```
|
||||||
|
|
||||||
|
**B. 纯 SQL 模式:**
|
||||||
|
```sql
|
||||||
|
-- 等待 3 秒后执行
|
||||||
|
SHOW REPLICA STATUS\G;
|
||||||
|
```
|
||||||
|
|
||||||
|
检查关键指标:
|
||||||
|
- `Replica_IO_Running`: 应为 `Yes`
|
||||||
|
- `Replica_SQL_Running`: 应为 `Yes`
|
||||||
|
- `Last_IO_Error`: 应为空
|
||||||
|
- `Last_SQL_Error`: 应为空
|
||||||
|
- `Seconds_Behind_Source`: 延迟秒数(应逐渐减小)
|
||||||
|
|
||||||
|
### 2.3 注意事项
|
||||||
|
|
||||||
|
1. **备份时机**: 建议在业务低峰期执行备份,减少对业务的影响
|
||||||
|
2. **网络传输**: 确保备份机和还原机之间网络畅通,备份文件较大时传输可能需要较长时间
|
||||||
|
3. **磁盘空间**: 确保还原机有足够的磁盘空间存放备份文件和临时解压文件
|
||||||
|
4. **权限要求**: 脚本需要 MySQL root 权限,以及 `/data/backup/mysql/` 目录的读写权限
|
||||||
|
5. **GTID 一致性**: 还原脚本会自动处理 GTID,确保复制能够正常继续
|
||||||
|
6. **备份机同步**: 还原完成后务必在备份机(主库)上执行 `START REPLICA` 并检查状态,确保双向复制正常
|
||||||
|
|||||||
@@ -0,0 +1,227 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Define variables
|
||||||
|
MYSQL_USER="root"
|
||||||
|
MYSQL_PASSWORD="ENC(VN13kACOdhyl7xYqbgDMTMP0fK4dwoCkFe0AQW0VEt7/tpwx96GzRVY6eIpwlASM)"
|
||||||
|
MYSQL_HOST="127.0.0.1"
|
||||||
|
BACKUP_DIR="/data/backup/mysql"
|
||||||
|
TEMP_DIR="/tmp/mysql_restore_temp"
|
||||||
|
|
||||||
|
if [[ "$MYSQL_PASSWORD" =~ ^ENC\(.*\)$ ]]; then
|
||||||
|
encrypted_pwd="${MYSQL_PASSWORD:4:-1}"
|
||||||
|
# Decrypt password
|
||||||
|
if ! MYSQL_PASSWORD=$(java -cp /data/secure/jasypt-agent.jar com.jasypt.agent.DecryptionCLI $encrypted_pwd \
|
||||||
|
2>&1); then
|
||||||
|
echo -e "\033[31m[Fatal Error] Password decryption failed. Reason:"
|
||||||
|
echo "$MYSQL_PASSWORD" | grep -iE "error|exception"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Function to execute MySQL command
|
||||||
|
mysql_exec() {
|
||||||
|
docker exec -i mysql mysql -u "$MYSQL_USER" -p"$MYSQL_PASSWORD" -h "$MYSQL_HOST" -N -e "$1" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to restore MySQL state (called on exit)
|
||||||
|
restore_mysql_state() {
|
||||||
|
if [ "$READ_ONLY_SET" = "true" ]; then
|
||||||
|
echo "Restoring MySQL state..."
|
||||||
|
mysql_exec "SET GLOBAL read_only = OFF;" || echo "[Warning] Failed to set read_only = OFF"
|
||||||
|
echo "MySQL state restored"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check parameters
|
||||||
|
if [ $# -lt 1 ]; then
|
||||||
|
echo "Usage: $0 <backup_file.7z> [sql_file]"
|
||||||
|
echo "Example: $0 20250414105525.7z nacos.sql"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
BACKUP_FILE="$1"
|
||||||
|
SQL_FILE="$2"
|
||||||
|
|
||||||
|
# Verify backup file existence
|
||||||
|
if [ ! -f "$BACKUP_DIR/$BACKUP_FILE" ]; then
|
||||||
|
echo "Error: Backup file $BACKUP_DIR/$BACKUP_FILE not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract base backup name
|
||||||
|
BACKUP_NAME=$(basename "$BACKUP_FILE" .7z)
|
||||||
|
|
||||||
|
# Create temporary directory
|
||||||
|
mkdir -p "$TEMP_DIR"
|
||||||
|
echo "Creating temporary directory: $TEMP_DIR"
|
||||||
|
|
||||||
|
# Extract backup file
|
||||||
|
echo "Extracting $BACKUP_FILE to $TEMP_DIR..."
|
||||||
|
7zz x "$BACKUP_DIR/$BACKUP_FILE" -o"$TEMP_DIR" -y
|
||||||
|
|
||||||
|
# Check extraction status
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Error: Decompression failed"
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Extraction completed"
|
||||||
|
|
||||||
|
# Handle nested directory
|
||||||
|
NESTED_DIR="$TEMP_DIR/$BACKUP_NAME"
|
||||||
|
if [ -d "$NESTED_DIR" ]; then
|
||||||
|
echo "Detected nested directory: $NESTED_DIR"
|
||||||
|
SQL_DIR="$NESTED_DIR"
|
||||||
|
else
|
||||||
|
echo "No nested directory found, using root extraction path"
|
||||||
|
SQL_DIR="$TEMP_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify SQL files existence
|
||||||
|
SQL_COUNT=$(find "$SQL_DIR" -name "*.sql" | wc -l)
|
||||||
|
if [ "$SQL_COUNT" -eq 0 ]; then
|
||||||
|
echo "Error: No SQL files found in extracted directory"
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Pre-restore: Set read-only, stop replica, and reset GTID
|
||||||
|
echo "Setting MySQL to read-only mode..."
|
||||||
|
if ! mysql_exec "SET GLOBAL read_only = ON;"; then
|
||||||
|
echo "[Critical Error] Failed to set read_only = ON"
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
READ_ONLY_SET="true"
|
||||||
|
|
||||||
|
# Set trap to ensure MySQL state is restored even on error
|
||||||
|
trap restore_mysql_state EXIT ERR
|
||||||
|
|
||||||
|
echo "Stopping replica replication..."
|
||||||
|
if ! mysql_exec "STOP REPLICA;"; then
|
||||||
|
echo "[Warning] Failed to stop replica (may not be a replica server or already stopped)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Resetting binary logs and GTIDs..."
|
||||||
|
if ! mysql_exec "RESET BINARY LOGS AND GTIDS;"; then
|
||||||
|
echo "[Critical Error] Failed to reset binary logs and GTIDs"
|
||||||
|
restore_mysql_state
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Database import logic
|
||||||
|
if [ -n "$SQL_FILE" ]; then
|
||||||
|
# Import specific SQL file
|
||||||
|
if [ -f "$SQL_DIR/$SQL_FILE" ]; then
|
||||||
|
echo "Importing $SQL_FILE..."
|
||||||
|
|
||||||
|
db_name=$(basename "$SQL_FILE" .sql)
|
||||||
|
echo "Using database: $db_name"
|
||||||
|
|
||||||
|
# Create database if not exists
|
||||||
|
docker exec -i mysql mysql --init-command="SET SQL_LOG_BIN=0;" -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -h"$MYSQL_HOST" --connect-expired-password -e "CREATE DATABASE IF NOT EXISTS \`$db_name\`;"
|
||||||
|
|
||||||
|
# Import SQL file
|
||||||
|
docker exec -i mysql mysql --init-command="SET SQL_LOG_BIN=0;" -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -h"$MYSQL_HOST" --connect-expired-password "$db_name" < "$SQL_DIR/$SQL_FILE"
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "Successfully imported $SQL_FILE"
|
||||||
|
else
|
||||||
|
echo "Error: Failed to import $SQL_FILE"
|
||||||
|
restore_mysql_state
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Error: SQL file $SQL_DIR/$SQL_FILE not found"
|
||||||
|
restore_mysql_state
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Import all SQL files
|
||||||
|
echo "Importing all SQL files..."
|
||||||
|
for sql_file in $(find "$SQL_DIR" -name "*.sql" | sort); do
|
||||||
|
db_name=$(basename "$sql_file" .sql)
|
||||||
|
echo "Importing $sql_file to database $db_name..."
|
||||||
|
|
||||||
|
# Create database if not exists
|
||||||
|
docker exec -i mysql mysql --init-command="SET SQL_LOG_BIN=0;" -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -h"$MYSQL_HOST" --connect-expired-password -e "CREATE DATABASE IF NOT EXISTS \`$db_name\`;"
|
||||||
|
|
||||||
|
# Import SQL file
|
||||||
|
docker exec -i mysql mysql --init-command="SET SQL_LOG_BIN=0;" -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -h"$MYSQL_HOST" --connect-expired-password "$db_name" < "$sql_file"
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "Successfully imported $sql_file to $db_name"
|
||||||
|
else
|
||||||
|
echo "Error: Failed to import $sql_file to $db_name"
|
||||||
|
restore_mysql_state
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Post-restore: Set GTID purged from backup file
|
||||||
|
GTID_FILE="$SQL_DIR/gtid_executed.txt"
|
||||||
|
if [ -f "$GTID_FILE" ]; then
|
||||||
|
echo "Reading GTID from $GTID_FILE..."
|
||||||
|
GTID_EXECUTED=$(cat "$GTID_FILE" | tr -d '\n' | sed 's/\\n//g' | tr -s ' ' | xargs)
|
||||||
|
if [ -n "$GTID_EXECUTED" ]; then
|
||||||
|
echo "Setting GTID purged to: $GTID_EXECUTED"
|
||||||
|
if ! mysql_exec "SET GLOBAL gtid_purged = '$GTID_EXECUTED';"; then
|
||||||
|
echo "[Critical Error] Failed to set gtid_purged"
|
||||||
|
restore_mysql_state
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "GTID purged set successfully"
|
||||||
|
else
|
||||||
|
echo "[Warning] GTID file is empty"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "[Warning] GTID file $GTID_FILE not found, skipping GTID purged setting"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Restore MySQL to read-write mode
|
||||||
|
echo "Restoring MySQL to read-write mode..."
|
||||||
|
mysql_exec "SET GLOBAL read_only = OFF;" || echo "[Warning] Failed to set read_only = OFF"
|
||||||
|
READ_ONLY_SET="false"
|
||||||
|
|
||||||
|
# Start replica
|
||||||
|
echo "Starting replica replication..."
|
||||||
|
if ! mysql_exec "START REPLICA;"; then
|
||||||
|
echo "[Warning] Failed to start replica (may not be a replica server)"
|
||||||
|
else
|
||||||
|
echo "Waiting 3 seconds for replica to initialize..."
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
echo "Checking replica status..."
|
||||||
|
REPLICA_STATUS=$(mysql_exec "SHOW REPLICA STATUS\G")
|
||||||
|
if [ -n "$REPLICA_STATUS" ]; then
|
||||||
|
echo "Replica Status:"
|
||||||
|
echo "$REPLICA_STATUS"
|
||||||
|
|
||||||
|
# Check for errors in replica status
|
||||||
|
ERROR_COUNT=$(echo "$REPLICA_STATUS" | grep -i "Last_Error\|Last_IO_Error\|Last_SQL_Error" | grep -v "No error" | wc -l)
|
||||||
|
if [ "$ERROR_COUNT" -gt 0 ]; then
|
||||||
|
echo "[Warning] Replica status shows errors, please check the output above"
|
||||||
|
else
|
||||||
|
echo "Replica status appears normal"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "[Warning] Failed to get replica status"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clear the trap since we've successfully restored
|
||||||
|
trap - EXIT ERR
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
echo "Cleaning temporary directory..."
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
|
||||||
|
echo "Database restore operation completed"
|
||||||
|
exit 0
|
||||||
Reference in New Issue
Block a user