PostgreSQL 备份脚本
📋 概述
这个脚本专门用于 PostgreSQL 数据库的备份操作,支持全量备份、增量备份和压缩备份。
🚀 备份脚本
#!/bin/bash
# PostgreSQL 备份脚本
set -euo pipefail
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# 日志函数
log_info() {
    echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
log_success() {
    echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
log_warning() {
    echo -e "${YELLOW}[WARNING]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
log_error() {
    echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
# 环境变量配置
DB_HOST="${DB_HOST:-localhost}"
DB_PORT="${DB_PORT:-5432}"
DB_NAME="${DB_NAME:-postgres}"
DB_USER="${DB_USER:-postgres}"
DB_PASSWORD="${DB_PASSWORD:-}"
BACKUP_DIR="${BACKUP_DIR:-/backup/postgres}"
BACKUP_RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-7}"
COMPRESS_BACKUP="${COMPRESS_BACKUP:-true}"
BACKUP_TYPE="${BACKUP_TYPE:-full}"  # full, schema, data
# 检查依赖
check_dependencies() {
    log_info "检查依赖..."
    if ! command -v pg_dump &> /dev/null; then
        log_error "pg_dump 未安装"
        exit 1
    fi
    if ! command -v psql &> /dev/null; then
        log_error "psql 未安装"
        exit 1
    fi
    if [[ "$COMPRESS_BACKUP" == "true" ]] && ! command -v gzip &> /dev/null; then
        log_warning "gzip 未安装,将跳过压缩"
        COMPRESS_BACKUP=false
    fi
    log_success "依赖检查完成"
}
# 测试数据库连接
test_connection() {
    log_info "测试数据库连接..."
    if ! PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT 1;" &> /dev/null; then
        log_error "无法连接到数据库"
        exit 1
    fi
    log_success "数据库连接测试通过"
}
# 获取数据库信息
get_db_info() {
    log_info "获取数据库信息..."
    local db_size=$(PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c "
        SELECT pg_size_pretty(pg_database_size('$DB_NAME'));
    " | xargs)
    local table_count=$(PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c "
        SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public';
    " | xargs)
    log_info "数据库大小: $db_size"
    log_info "表数量: $table_count"
}
# 创建备份目录
create_backup_dir() {
    log_info "创建备份目录..."
    mkdir -p "$BACKUP_DIR"
    log_success "备份目录创建完成: $BACKUP_DIR"
}
# 全量备份
perform_full_backup() {
    log_info "执行全量备份..."
    local timestamp=$(date '+%Y%m%d_%H%M%S')
    local backup_file="$BACKUP_DIR/${DB_NAME}_full_${timestamp}.sql"
    local pg_dump_args=(
        -h "$DB_HOST"
        -p "$DB_PORT"
        -U "$DB_USER"
        -d "$DB_NAME"
        --verbose
        --clean
        --if-exists
        --create
        --no-owner
        --no-privileges
        --format=custom
        -f "$backup_file"
    )
    if PGPASSWORD="$DB_PASSWORD" pg_dump "${pg_dump_args[@]}"; then
        log_success "全量备份完成: $backup_file"
        # 压缩备份文件
        if [[ "$COMPRESS_BACKUP" == "true" ]]; then
            log_info "压缩备份文件..."
            gzip "$backup_file"
            backup_file="${backup_file}.gz"
            log_success "压缩完成: $backup_file"
        fi
        echo "$backup_file"
    else
        log_error "全量备份失败"
        exit 1
    fi
}
# Schema 备份
perform_schema_backup() {
    log_info "执行 Schema 备份..."
    local timestamp=$(date '+%Y%m%d_%H%M%S')
    local backup_file="$BACKUP_DIR/${DB_NAME}_schema_${timestamp}.sql"
    local pg_dump_args=(
        -h "$DB_HOST"
        -p "$DB_PORT"
        -U "$DB_USER"
        -d "$DB_NAME"
        --verbose
        --schema-only
        --no-owner
        --no-privileges
        -f "$backup_file"
    )
    if PGPASSWORD="$DB_PASSWORD" pg_dump "${pg_dump_args[@]}"; then
        log_success "Schema 备份完成: $backup_file"
        # 压缩备份文件
        if [[ "$COMPRESS_BACKUP" == "true" ]]; then
            log_info "压缩备份文件..."
            gzip "$backup_file"
            backup_file="${backup_file}.gz"
            log_success "压缩完成: $backup_file"
        fi
        echo "$backup_file"
    else
        log_error "Schema 备份失败"
        exit 1
    fi
}
# 数据备份
perform_data_backup() {
    log_info "执行数据备份..."
    local timestamp=$(date '+%Y%m%d_%H%M%S')
    local backup_file="$BACKUP_DIR/${DB_NAME}_data_${timestamp}.sql"
    local pg_dump_args=(
        -h "$DB_HOST"
        -p "$DB_PORT"
        -U "$DB_USER"
        -d "$DB_NAME"
        --verbose
        --data-only
        --no-owner
        --no-privileges
        -f "$backup_file"
    )
    if PGPASSWORD="$DB_PASSWORD" pg_dump "${pg_dump_args[@]}"; then
        log_success "数据备份完成: $backup_file"
        # 压缩备份文件
        if [[ "$COMPRESS_BACKUP" == "true" ]]; then
            log_info "压缩备份文件..."
            gzip "$backup_file"
            backup_file="${backup_file}.gz"
            log_success "压缩完成: $backup_file"
        fi
        echo "$backup_file"
    else
        log_error "数据备份失败"
        exit 1
    fi
}
# 增量备份(基于 WAL)
perform_incremental_backup() {
    log_info "执行增量备份..."
    # 检查是否启用了 WAL 归档
    local wal_archive_mode=$(PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c "
        SHOW wal_level;
    " | xargs)
    if [[ "$wal_archive_mode" != "replica" && "$wal_archive_mode" != "logical" ]]; then
        log_warning "WAL 级别不是 replica 或 logical,无法执行增量备份"
        log_info "建议在 postgresql.conf 中设置: wal_level = replica"
        return 1
    fi
    # 执行 pg_basebackup
    local timestamp=$(date '+%Y%m%d_%H%M%S')
    local backup_dir="$BACKUP_DIR/${DB_NAME}_incremental_${timestamp}"
    if PGPASSWORD="$DB_PASSWORD" pg_basebackup \
        -h "$DB_HOST" \
        -p "$DB_PORT" \
        -U "$DB_USER" \
        -D "$backup_dir" \
        --verbose \
        --progress; then
        log_success "增量备份完成: $backup_dir"
        echo "$backup_dir"
    else
        log_error "增量备份失败"
        exit 1
    fi
}
# 验证备份文件
verify_backup() {
    local backup_file=$1
    log_info "验证备份文件: $backup_file"
    if [[ ! -f "$backup_file" ]]; then
        log_error "备份文件不存在"
        return 1
    fi
    # 检查文件大小
    local file_size=$(du -h "$backup_file" | cut -f1)
    log_info "备份文件大小: $file_size"
    # 如果是压缩文件,检查完整性
    if [[ "$backup_file" == *.gz ]]; then
        if gzip -t "$backup_file"; then
            log_success "压缩文件完整性验证通过"
        else
            log_error "压缩文件损坏"
            return 1
        fi
    fi
    log_success "备份文件验证通过"
}
# 清理旧备份
cleanup_old_backups() {
    log_info "清理旧备份文件..."
    local deleted_count=0
    # 清理 SQL 备份文件
    if [[ -d "$BACKUP_DIR" ]]; then
        deleted_count=$(find "$BACKUP_DIR" -name "*.sql*" -mtime +"$BACKUP_RETENTION_DAYS" | wc -l)
        find "$BACKUP_DIR" -name "*.sql*" -mtime +"$BACKUP_RETENTION_DAYS" -delete
    fi
    # 清理增量备份目录
    if [[ -d "$BACKUP_DIR" ]]; then
        local incremental_deleted=$(find "$BACKUP_DIR" -name "*_incremental_*" -type d -mtime +"$BACKUP_RETENTION_DAYS" | wc -l)
        find "$BACKUP_DIR" -name "*_incremental_*" -type d -mtime +"$BACKUP_RETENTION_DAYS" -exec rm -rf {} +
        deleted_count=$((deleted_count + incremental_deleted))
    fi
    log_success "清理完成,删除了 $deleted_count 个旧备份文件"
}
# 生成备份报告
generate_backup_report() {
    local backup_file=$1
    log_info "生成备份报告..."
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    local report_file="$BACKUP_DIR/backup_report_$(date '+%Y%m%d').txt"
    cat >> "$report_file" << EOF
=== PostgreSQL 备份报告 ===
备份时间: $timestamp
数据库: $DB_NAME
主机: $DB_HOST:$DB_PORT
备份类型: $BACKUP_TYPE
备份文件: $backup_file
文件大小: $(du -h "$backup_file" | cut -f1)
压缩: $COMPRESS_BACKUP
EOF
    log_success "备份报告已生成: $report_file"
}
# 主函数
main() {
    log_info "开始 PostgreSQL 备份..."
    check_dependencies
    test_connection
    get_db_info
    create_backup_dir
    local backup_file=""
    case "$BACKUP_TYPE" in
        "full")
            backup_file=$(perform_full_backup)
            ;;
        "schema")
            backup_file=$(perform_schema_backup)
            ;;
        "data")
            backup_file=$(perform_data_backup)
            ;;
        "incremental")
            backup_file=$(perform_incremental_backup)
            ;;
        *)
            log_error "不支持的备份类型: $BACKUP_TYPE"
            log_info "支持的备份类型: full, schema, data, incremental"
            exit 1
            ;;
    esac
    # 验证备份
    if [[ -n "$backup_file" ]]; then
        verify_backup "$backup_file"
        generate_backup_report "$backup_file"
    fi
    # 清理旧备份
    cleanup_old_backups
    log_success "PostgreSQL 备份完成!"
}
# 脚本入口
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi
🔧 使用方法
1. 环境变量配置
# 数据库配置
export DB_HOST="localhost"
export DB_PORT="5432"
export DB_NAME="my_database"
export DB_USER="postgres"
export DB_PASSWORD="password"
# 备份配置
export BACKUP_DIR="/backup/postgres"
export BACKUP_RETENTION_DAYS="7"
export COMPRESS_BACKUP="true"
export BACKUP_TYPE="full"  # full, schema, data, incremental
2. 执行备份
# 全量备份
export BACKUP_TYPE="full"
./postgres-backup.sh
# Schema 备份
export BACKUP_TYPE="schema"
./postgres-backup.sh
# 数据备份
export BACKUP_TYPE="data"
./postgres-backup.sh
# 增量备份
export BACKUP_TYPE="incremental"
./postgres-backup.sh
3. 定时备份
# 每天凌晨2点执行全量备份
0 2 * * * /path/to/postgres-backup.sh >> /var/log/postgres-backup.log 2>&1
# 每小时执行增量备份
0 * * * * export BACKUP_TYPE="incremental" && /path/to/postgres-backup.sh >> /var/log/postgres-backup.log 2>&1
📊 备份类型说明
| 备份类型 | 说明 | 适用场景 | 
|---|---|---|
full | 
全量备份,包含所有数据和结构 | 完整备份,灾难恢复 | 
schema | 
仅备份数据库结构 | 部署新环境,结构迁移 | 
data | 
仅备份数据,不包含结构 | 数据迁移,数据恢复 | 
incremental | 
基于 WAL 的增量备份 | 频繁备份,最小化数据丢失 |