Files
agent-skills/skills/g3fo-docs/references/middleware/mysql/restore-sync.sh
T
ken.li 3808192fd8 feat(mysql): 添加MySQL全量备份与还原脚本及文档
新增备份脚本backup-sync.sh和还原脚本restore-sync.sh,用于处理严重复制冲突场景
更新故障分析文档,补充全量备份还原的操作流程和注意事项
添加测试流程模板文件_TEST_FLOW_TEMPLATE.md
更新SKILL.md文档,增加测试流程分析相关内容
2026-02-02 14:40:46 +08:00

227 lines
7.0 KiB
Bash

#!/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