暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

Oracle表空间使用率自动检测与智能扩容实战——Shell脚本监控告警及自动扩展数据文件与ASM磁盘组管理基础知识【Oracle数据库分享--0x06】

Acdante 2026-04-24
20

写在前面

各位DBA朋友们好,我是Acdante。

0x01篇分享了Oracle备份脚本(expdp&RMAN)和表空间监控的脚本,反响不错,很多读者私信问了表空间监控的问题。说实话,表空间爆满是生产环境里最常见也最致命的问题之一——凌晨3点被电话叫起来,发现是表空间满了业务中断,这种酸爽谁经历谁知道。


注意注意分享脚本仅供参考,生产使用建议充分测试和验证,自动添加固然省事,但是也存在一定风险,建议操作慎重.

也可以只用监控脚本,只做查询,添加人工介入(当然,作自动添加,很多时候是因为开发或者其他人员,再看到表空间90%后,不看数据文件路径,直接添加,结果导致Oracle RAC环境下,直接把数据文件添加到某个节点本地$ORACLE_HOME内,这种文件错误,会导致跨实例访问数据失败,以及重启后节点异常无法访问数据文件等等问题,相信不少DBA也经历过,要做迁移,数据文件copy等,单机情况下,还有加到各种莫名路径的,默认HOME下的,所以添加数据文件,扩容表空间,看似简单的一个动作,却也存在不少“天坑”,多一分细心,多一分考虑,会给数据库的持续稳定运行,带来很大助益,也会省去后续很多故障隐患和未知风险 。


今天这篇文章,咱们就彻底解决这个问题。

文章内容比较硬核,我会从以下几个方面展开:

  • 📊 表空间使用率的真实计算方式(不是简单的 dba_free_space
  • 📈 数据文件自增长上限的真实使用率检测
  • 🤖 大于90%自动添加数据文件(不冲突、不踩坑)
  • 📝 完整的日志记录机制
  • 💾 文件系统空间 vs ASM磁盘组空间的智能判断
  • 🔔 钉钉/邮件外发告警
  • 📦 ASM磁盘组监控全解析
  • 🔄 从11g到23ai/26ai ASM的变化与限制

温馨提示:本文约2.3万字,建议先收藏,配合咖啡慢慢看 ☕


一、表空间使用率:你以为的和实际的

1.1 常见误区

很多同学监控表空间,直接一个SQL就完事了:

SELECT a.tablespace_name,
       ROUND(a.total_size, 2) AS total_mb,
       ROUND(a.total_size - NVL(b.free_size, 0), 2) AS used_mb,
       ROUND((a.total_size - NVL(b.free_size, 0)) a.total_size * 100, 2) AS pct_used
FROM (SELECT tablespace_name, SUM(bytes) 1024 1024 AS total_size
      FROM dba_data_files GROUP BY tablespace_name) a,
     (SELECT tablespace_name, SUM(bytes) 1024 1024 AS free_size
      FROM dba_free_space GROUP BY tablespace_name) b
WHERE a.tablespace_name = b.tablespace_name(+)
ORDER BY pct_used DESC;

这个SQL看起来没毛病,但它计算的是当前已分配空间的使用率,而不是表空间的真实增长上限

什么意思呢?

举个例子:

  • 表空间 USERS
     有一个数据文件,初始100MB,AUTOEXTEND ON
    MAXBYTES 32GB
  • 当前已经用了90MB
  • 按上面的SQL:使用率 = 90/100 = 90%
  • 但真实情况是:这个文件还可以自动增长到32GB,真实使用率 = 90/32768 = 0.27%

差距巨大,对吧?

1.2 真实使用率的计算

正确的做法是:计算数据文件自增长上限(MAXBYTES)的真实使用率。

也可以参看0x03文章的内容

表空间检查实战

acdante,公众号:AcdanteOracle数据库表空间与数据文件实战指南【Oracle数据库分享--0x03】

核心逻辑如下:

SELECT df.tablespace_name,
       -- 当前已分配空间(MB)
       ROUND(SUM(df.bytes) 1024 1024, 2) AS current_allocated_mb,
       -- 实际已使用空间(MB)
       ROUND((SUM(df.bytes) - NVL(fs.free_bytes, 0)) 1024 1024, 2) AS actual_used_mb,
       -- 最大可扩展空间(MB):考虑AUTOEXTEND
       ROUND(SUM(
           CASE
               WHEN df.autoextensible = 'YES' THEN df.maxbytes
               ELSE df.bytes
           END
       ) 1024 1024, 2) AS max_extendable_mb,
       -- 真实使用率(基于最大可扩展空间)
       ROUND(
           (SUM(df.bytes) - NVL(fs.free_bytes, 0))
           SUM(
               CASE
                   WHEN df.autoextensible = 'YES' THEN df.maxbytes
                   ELSE df.bytes
               END
           ) * 100, 2
       ) AS real_pct_used,
       -- 空间不足预警(剩余可增长MB)
       ROUND(
           SUM(
               CASE
                   WHEN df.autoextensible = 'YES' THEN df.maxbytes
                   ELSE df.bytes
               END
           ) 1024 1024 - (SUM(df.bytes) - NVL(fs.free_bytes, 0)) 1024 1024, 2
       ) AS remaining_mb
FROM dba_data_files df,
     (SELECT tablespace_name, SUM(bytes) AS free_bytes
      FROM dba_free_space
      GROUP BY tablespace_name) fs
WHERE df.tablespace_name = fs.tablespace_name(+)
GROUP BY df.tablespace_name, fs.free_bytes
ORDER BY real_pct_used DESC;

💡 关键点解析
autoextensible = 'YES'
 的数据文件,用 maxbytes
 作为上限
autoextensible = 'NO'
 的数据文件,当前大小就是上限
- 真实使用率 = 实际已使用 最大可扩展空间

1.3 关于MAXBYTES的坑

⚠️ 注意: Oracle有个经典的坑——数据文件的 MAXBYTES
 默认值。

对于一个数据文件:

  • 如果创建时指定了 AUTOEXTEND ON MAXSIZE 32G
    ,那 MAXBYTES = 32G
  • 如果创建时只写了 AUTOEXTEND ON
    (没指定MAXSIZE),默认最大是 数据文件所在块大小 × 2^22 - 1

对于8KB块大小的数据库:

  • 默认MAXBYTES = 8192 × 4194304 = 32GB - 8KB(约32GB)
  • 对于32KB块大小:可以到 128GB

这个知识点很重要,因为在监控脚本里,你需要正确理解 MAXBYTES
 的含义。


二、核心Shell脚本:表空间自动检测与扩容

2.1 脚本设计思路

整个脚本需要实现以下功能:

序号
脚本核心功能
功能说明
1
连接 Oracle 数据库
建立数据库可靠连接,为后续查询、操作提供会话基础
2
查询所有表空间的真实使用率
精准统计表空间已用空间、总空间、使用率等核心指标
3
判断数据文件所在位置 (FS ASM)
识别数据文件存储介质,区分本地文件系统与 ASM 存储
4
检查目标位置的可用空间
核查数据文件所在存储的剩余容量,确保扩容有足够空间
5
找出使用率 > 90% 的表空间
筛选高风险表空间,作为自动扩容和告警的目标对象
6
自动添加数据文件 (避免命名冲突)
自动生成合规数据文件名,无冲突完成表空间扩容操作
7
记录完整操作日志
全程记录连接、查询、判断、扩容等所有操作,便于追溯审计
8
发送钉钉 邮件告警通知
表空间超标时,自动推送告警信息,及时通知运维人员


2.2 完整脚本

下面是完整的生产级别脚本,我做了详细的注释,每个关键步骤都有说明。

#!/bin/bash
#=============================================================
# 脚本名称: oracle_tablespace_monitor.sh
# 脚本功能: Oracle表空间使用率自动检测与智能扩容
# 适用版本: Oracle 11g/12c/19c/21c/23ai
# 作者: Acdante
# 日期: 2025-09-15
# 说明: 
#   1. 检测表空间真实使用率(基于数据文件自增长上限)
#   2. 使用率>90%自动添加数据文件
#   3. 自动判断文件系统/ASM磁盘组可用空间
#   4. 完整日志记录
#   5. 钉钉/邮件告警
#=============================================================

# ===================== 配置区域 =====================
# Oracle环境变量
export ORACLE_BASE=/u01/app/oracle
export ORACLE_HOME=/u01/app/oracle/product/19.0.0/dbhome_1
export ORACLE_SID=ORCL
export PATH=$ORACLE_HOME/bin:$PATH
export LD_LIBRARY_PATH=$ORACLE_HOME/lib:$LD_LIBRARY_PATH

# 数据库连接信息(建议使用操作系统认证)
DB_USER="/ as sysdba"

# 监控阈值(百分比)
ALERT_THRESHOLD=80    # 告警阈值: 80%
AUTOADD_THRESHOLD=90  # 自动扩容阈值: 90%

# 自动扩容参数
ADD_FILE_SIZE="10G"            # 每次添加数据文件的大小
ADD_FILE_SIZE_KB=10485760      # 10G对应的KB值(10*1024*1024)
MAX_AUTOADD_COUNT=5            # 单个表空间最大自动添加文件数
MAX_FILE_SIZE="32G"            # 单个数据文件最大尺寸

# 文件系统空间检查阈值(剩余空间不低于此值才允许扩容,单位GB)
FS_MIN_FREE_GB=50

# ASM磁盘组空间检查阈值(剩余空间不低于此值才允许扩容,单位GB)
ASM_MIN_FREE_GB=100

# 日志目录
LOG_DIR=/u01/scripts/log/tablespace_monitor
mkdir -p ${LOG_DIR}

# 日志文件
LOG_FILE=${LOG_DIR}/tablespace_monitor_$(date +%Y%m%d_%H%M%S).log

# 告警配置
# 钉钉机器人webhook地址(替换为你的实际地址)
DINGTALK_WEBHOOK="https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN_HERE"
# 邮件配置
MAIL_TO="dba@company.com,ops@company.com"
MAIL_FROM="oracle_monitor@company.com"
MAIL_SMTP="smtp.company.com"

# ===================== 函数定义 =====================

# 日志记录函数
log() {
    local level=$1
    shift
    local msg="$*"
    local timestamp=$(date "+%Y-%m-%d %H:%M:%S")
    echo "[${timestamp}] [${level}] ${msg}" | tee -a ${LOG_FILE}
}

# 日志记录(不输出到终端,仅写文件)
logf() {
    local level=$1
    shift
    local msg="$*"
    local timestamp=$(date "+%Y-%m-%d %H:%M:%S")
    echo "[${timestamp}] [${level}] ${msg}" >> ${LOG_FILE}
}

# 钉钉告警函数
send_dingtalk() {
    local title="$1"
    local text="$2"

    # 检查webhook是否配置
    if [[ "${DINGTALK_WEBHOOK}" == *"YOUR_TOKEN_HERE"* ]]; then
        log "WARN" "钉钉webhook未配置,跳过钉钉告警"
        return 1
    fi

    # 构建钉钉消息体(markdown格式)
    local json_data=$(cat <<EOF
{
    "msgtype": "markdown",
    "markdown": {
        "title": "${title}",
        "text": "${text}"
    },
    "at": {
        "isAtAll": false
    }
}
EOF
)

    # 发送钉钉消息
    local response=$(curl -s -H "Content-Type: application/json" \
        -d "${json_data}" \
        "${DINGTALK_WEBHOOK}" 2>&1)

    local ret_code=$?
    if [ ${ret_code} -eq 0 ]; then
        log "INFO" "钉钉告警发送成功"
        logf "DEBUG" "钉钉响应: ${response}"
    else
        log "ERROR" "钉钉告警发送失败, 返回码: ${ret_code}"
        logf "ERROR" "钉钉错误信息: ${response}"
    fi
}

# 邮件告警函数
send_mail() {
    local subject="$1"
    local body="$2"

    # 检查mailx是否可用
    if ! command -v mailx &>/dev/null; then
        log "WARN" "mailx命令不可用,跳过邮件告警"
        return 1
    fi

    echo "${body}" | mailx -s "${subject}" \
        -S smtp="${MAIL_SMTP}" \
        -S from="${MAIL_FROM}" \
        ${MAIL_TO}

    local ret_code=$?
    if [ ${ret_code} -eq 0 ]; then
        log "INFO" "邮件告警发送成功, 收件人: ${MAIL_TO}"
    else
        log "ERROR" "邮件告警发送失败, 返回码: ${ret_code}"
    fi
}

# 检查文件系统可用空间(单位GB)
check_filesystem_space() {
    local file_path=$1
    local dir_path=$(dirname "${file_path}")

    # 获取目录所在文件系统的可用空间(GB)
    local avail_gb=$(df -BG "${dir_path}" 2>/dev/null | tail -1 | awk '{print $4}' | sed 's/G//')

    if [ -z "${avail_gb}" ]; then
        log "ERROR" "无法获取文件系统空间: ${dir_path}"
        echo "0"
        return 1
    fi

    logf "INFO" "文件系统 ${dir_path} 可用空间: ${avail_gb}GB"
    echo "${avail_gb}"
    return 0
}

# 检查ASM磁盘组可用空间(单位GB)
check_asm_diskgroup_space() {
    local diskgroup_name=$1

    # 通过sqlplus查询ASM磁盘组可用空间
    local sql_result=$(sqlplus -S "${DB_USER}" <<EOF
SET HEADING OFF
SET FEEDBACK OFF
SET PAGESIZE 0
SET LINESIZE 200
SET TRIMOUT ON
SET TRIMSPOOL ON

SELECT ROUND(free_mb/1024, 2)
FROM v\\$asm_diskgroup
WHERE name = UPPER('${diskgroup_name}');
EXIT;
EOF
)

    local avail_gb=$(echo "${sql_result}" | tr -d '[:space:]')

    if [ -z "${avail_gb}" ] || [ "${avail_gb}" = "0" ]; then
        log "ERROR" "无法获取ASM磁盘组空间: ${diskgroup_name} 或空间为0"
        echo "0"
        return 1
    fi

    logf "INFO" "ASM磁盘组 ${diskgroup_name} 可用空间: ${avail_gb}GB"
    echo "${avail_gb}"
    return 0
}

# 判断数据文件路径类型(文件系统 or ASM)
get_storage_type() {
    local file_path=$1

    if [[ "${file_path}" == +* ]]; then
        echo "ASM"
    else
        echo "FS"
    fi
}

# 从ASM路径中提取磁盘组名称
get_asm_diskgroup() {
    local file_path=$1
    # ASM路径格式: +DISKGROUP_NAME/...
    echo "${file_path}" | sed 's/^+\([^/]*\).*/\1/'
}

# 检查数据文件名是否已存在
check_file_exists() {
    local file_name=$1
    local sql_result=$(sqlplus -S "${DB_USER}" <<EOF
SET HEADING OFF
SET FEEDBACK OFF
SET PAGESIZE 0
SELECT COUNT(*) FROM dba_data_files WHERE file_name = '${file_name}';
EXIT;
EOF
)
    local count=$(echo "${sql_result}" | tr -d '[:space:]')
    echo "${count}"
}

# 生成不冲突的数据文件名
generate_file_name() {
    local tablespace_name=$1
    local storage_type=$2
    local file_path=$3

    local file_name=""
    local counter=1

    if [ "${storage_type}" = "ASM" ]; then
        # ASM路径格式: +DISKGROUP_NAME/ORCL/DATAFILE/ts_name_nn.dbf
        local dg_name=$(get_asm_diskgroup "${file_path}")
        local base_path="+${dg_name}/${ORACLE_SID}/DATAFILE"

        while true; do
            file_name="${base_path}/${tablespace_name,,}_$(printf '%02d' ${counter}).dbf"
            local exists=$(check_file_exists "${file_name}")
            if [ "${exists}" = "0" ]; then
                break
            fi
            counter=$((counter + 1))
            if [ ${counter} -gt 99 ]; then
                log "ERROR" "数据文件编号超过99,无法生成文件名"
                echo ""
                return 1
            fi
        done
    else
        # 文件系统路径: 取现有数据文件的目录
        local dir_path=$(dirname "${file_path}")

        while true; do
            file_name="${dir_path}/${tablespace_name,,}_$(printf '%02d' ${counter}).dbf"
            local exists=$(check_file_exists "${file_name}")
            if [ "${exists}" = "0" ]; then
                break
            fi
            counter=$((counter + 1))
            if [ ${counter} -gt 99 ]; then
                log "ERROR" "数据文件编号超过99,无法生成文件名"
                echo ""
                return 1
            fi
        done
    fi

    echo "${file_name}"
}

# 执行添加数据文件操作
add_datafile() {
    local tablespace_name=$1
    local file_name=$2
    local file_size=$3

    log "INFO" "========================================="
    log "INFO" "开始添加数据文件"
    log "INFO" "表空间: ${tablespace_name}"
    log "INFO" "文件名: ${file_name}"
    log "INFO" "文件大小: ${file_size}"
    log "INFO" "========================================="

    # 构建ALTER TABLESPACE语句
    local sql_stmt="ALTER TABLESPACE ${tablespace_name} ADD DATAFILE '${file_name}' SIZE ${file_size} AUTOEXTEND ON NEXT 1G MAXSIZE ${MAX_FILE_SIZE};"

    logf "INFO" "执行SQL: ${sql_stmt}"

    # 记录SQL到日志
    log "INFO" "执行SQL: ${sql_stmt}"

    local sql_result=$(sqlplus -S "${DB_USER}" <<EOF
SET SERVEROUTPUT ON
SET LINESIZE 200
SET PAGESIZE 0

${sql_stmt}

-- 验证添加结果
SELECT 'VERIFY:' || file_name || '|' || bytes/1024/1024 || 'MB' 
FROM dba_data_files 
WHERE tablespace_name = UPPER('${tablespace_name}')
ORDER BY file_id DESC
FETCH FIRST 1 ROWS ONLY;

EXIT;
EOF
)

    local ret_code=$?

    # 记录SQL执行结果
    logf "INFO" "SQL执行结果: ${sql_result}"

    # 检查执行结果
    if echo "${sql_result}" | grep -qi "ORA-"; then
        local ora_error=$(echo "${sql_result}" | grep -oP 'ORA-\d+.*' | head -1)
        log "ERROR" "添加数据文件失败: ${ora_error}"
        logf "ERROR" "完整错误输出: ${sql_result}"
        return 1
    elif [ ${ret_code} -ne 0 ]; then
        log "ERROR" "SQL*Plus执行失败, 返回码: ${ret_code}"
        logf "ERROR" "完整输出: ${sql_result}"
        return 1
    else
        log "INFO" "✅ 数据文件添加成功!"
        logf "INFO" "验证结果: ${sql_result}"
        return 0
    fi
}

# ===================== 主逻辑 =====================

main() {
    log "INFO" "============================================================"
    log "INFO" "  Oracle表空间监控自动扩容脚本启动"
    log "INFO" "  执行时间: $(date '+%Y-%m-%d %H:%M:%S')"
    log "INFO" "  数据库实例: ${ORACLE_SID}"
    log "INFO" "  告警阈值: ${ALERT_THRESHOLD}%"
    log "INFO" "  自动扩容阈值: ${AUTOADD_THRESHOLD}%"
    log "INFO" "  每次扩容大小: ${ADD_FILE_SIZE}"
    log "INFO" "  最大自动扩容次数: ${MAX_AUTOADD_COUNT}"
    log "INFO" "============================================================"

    # 检查数据库连接
    local conn_test=$(sqlplus -S "${DB_USER}" <<EOF
SET HEADING OFF
SET FEEDBACK OFF
SET PAGESIZE 0
SELECT 'CONNECT_OK' FROM dual;
EXIT;
EOF
)

    if ! echo "${conn_test}" | grep -q "CONNECT_OK"; then
        log "ERROR" "无法连接数据库,请检查Oracle环境变量和连接信息"
        logf "ERROR" "连接测试结果: ${conn_test}"
        send_dingtalk "🔴 数据库连接失败" "## 数据库连接失败\n\n- 实例: ${ORACLE_SID}\n- 时间: $(date '+%Y-%m-%d %H:%M:%S')\n- 请立即检查!"
        exit 1
    fi
    log "INFO" "数据库连接成功"

    # ==========================================
    # 第一步: 查询所有表空间的真实使用率
    # ==========================================
    log "INFO" "----------------------------------------"
    log "INFO" "第一步: 查询表空间使用率"
    log "INFO" "----------------------------------------"

    # 创建临时文件存储查询结果
    local tmp_file=$(mktemp tmp/ts_monitor_XXXXXX.tmp)

    # 执行表空间使用率查询
    sqlplus -S "${DB_USER}" <<EOF > ${tmp_file} 2>&1
SET HEADING OFF
SET FEEDBACK OFF
SET PAGESIZE 0
SET LINESIZE 500
SET TRIMOUT ON
SET TRIMSPOOL ON
SET COLSEP '|'

-- 主查询: 表空间真实使用率
SELECT 
    df.tablespace_name,
    ROUND(SUM(df.bytes)/1024/1024, 2),
    ROUND((SUM(df.bytes) - NVL(fs.free_bytes, 0))/1024/1024, 2),
    ROUND(SUM(CASE WHEN df.autoextensible = 'YES' THEN df.maxbytes ELSE df.bytes END)/1024/1024, 2),
    ROUND((SUM(df.bytes) - NVL(fs.free_bytes, 0)) SUM(CASE WHEN df.autoextensible = 'YES' THEN df.maxbytes ELSE df.bytes END) * 100, 2),
    df.file_name,
    df.autoextensible,
    ROUND(df.maxbytes/1024/1024, 2)
FROM dba_data_files df,
     (SELECT tablespace_name, SUM(bytes) AS free_bytes
      FROM dba_free_space GROUP BY tablespace_name) fs
WHERE df.tablespace_name = fs.tablespace_name(+)
GROUP BY df.tablespace_name, df.file_name, df.autoextensible, df.maxbytes, fs.free_bytes
ORDER BY 5 DESC;

EXIT;
EOF

    log "INFO" "表空间查询完成, 结果已保存"

    # 解析查询结果并处理
    local alert_msg=""
    local addfile_msg=""
    local error_msg=""

    while IFS='|' read -r ts_name current_mb used_mb max_mb pct_used file_path autoext maxbytes_mb; do
        # 跳过空行和系统表空间(根据需要调整)
        [ -z "${ts_name}" ] && continue
        ts_name=$(echo "${ts_name}" | tr -d '[:space:]')
        pct_used=$(echo "${pct_used}" | tr -d '[:space:]')

        [ -z "${ts_name}" ] && continue
        [ -z "${pct_used}" ] && continue

        # 去除百分号(如果有)
        pct_used=$(echo "${pct_used}" | sed 's/%//g')

        logf "INFO" "表空间: ${ts_name}, 当前: ${current_mb}MB, 已用: ${used_mb}MB, 上限: ${max_mb}MB, 使用率: ${pct_used}%"

        # 检查是否需要告警
        if (( $(echo "${pct_used} >= ${ALERT_THRESHOLD}" | bc -l) )); then
            alert_msg="${alert_msg}\n- **${ts_name}**: ${pct_used}% (已用${used_mb}MB 上限${max_mb}MB)"

            log "WARN" "⚠️ 表空间 ${ts_name} 使用率 ${pct_used}% >= 告警阈值 ${ALERT_THRESHOLD}%"
        fi

        # 检查是否需要自动扩容
        if (( $(echo "${pct_used} >= ${AUTOADD_THRESHOLD}" | bc -l) )); then
            log "WARN" "🚨 表空间 ${ts_name} 使用率 ${pct_used}% >= 自动扩容阈值 ${AUTOADD_THRESHOLD}%"

            # ------------------------------------------
            # 第二步: 检查存储空间是否足够
            # ------------------------------------------
            local storage_type=$(get_storage_type "${file_path}")
            local avail_space=0
            local can_expand=false

            log "INFO" "存储类型: ${storage_type}, 数据文件路径: ${file_path}"

            if [ "${storage_type}" = "ASM" ]; then
                local dg_name=$(get_asm_diskgroup "${file_path}")
                avail_space=$(check_asm_diskgroup_space "${dg_name}")

                if [ "${avail_space}" -gt "${ASM_MIN_FREE_GB}" ]; then
                    can_expand=true
                    log "INFO" "ASM磁盘组 ${dg_name} 可用空间 ${avail_space}GB > 最低要求 ${ASM_MIN_FREE_GB}GB, 允许扩容"
                else
                    log "ERROR" "❌ ASM磁盘组 ${dg_name} 可用空间 ${avail_space}GB <= 最低要求 ${ASM_MIN_FREE_GB}GB, 无法扩容!"
                    error_msg="${error_msg}\n- **${ts_name}**: ASM磁盘组 ${dg_name} 空间不足 (${avail_space}GB)"
                fi
            else
                avail_space=$(check_filesystem_space "${file_path}")

                if [ "${avail_space}" -gt "${FS_MIN_FREE_GB}" ]; then
                    can_expand=true
                    log "INFO" "文件系统可用空间 ${avail_space}GB > 最低要求 ${FS_MIN_FREE_GB}GB, 允许扩容"
                else
                    log "ERROR" "❌ 文件系统可用空间 ${avail_space}GB <= 最低要求 ${FS_MIN_FREE_GB}GB, 无法扩容!"
                    error_msg="${error_msg}\n- **${ts_name}**: 文件系统空间不足 (${avail_space}GB)"
                fi
            fi

            # ------------------------------------------
            # 第三步: 执行自动扩容
            # ------------------------------------------
            if [ "${can_expand}" = true ]; then
                # 生成新的数据文件名
                local new_file=$(generate_file_name "${ts_name}" "${storage_type}" "${file_path}")

                if [ -z "${new_file}" ]; then
                    log "ERROR" "无法生成数据文件名,跳过表空间 ${ts_name} 的扩容"
                    error_msg="${error_msg}\n- **${ts_name}**: 无法生成数据文件名"
                    continue
                fi

                log "INFO" "生成新数据文件名: ${new_file}"

                # 执行添加数据文件
                local add_result=$(add_datafile "${ts_name}" "${new_file}" "${ADD_FILE_SIZE}")
                local add_ret=$?

                if [ ${add_ret} -eq 0 ]; then
                    addfile_msg="${addfile_msg}\n- **${ts_name}**: 已添加 ${new_file} (${ADD_FILE_SIZE})"
                    log "INFO" "✅ 表空间 ${ts_name} 自动扩容成功"
                else
                    error_msg="${error_msg}\n- **${ts_name}**: 自动扩容失败"
                    log "ERROR" "❌ 表空间 ${ts_name} 自动扩容失败"
                fi
            fi
        fi
    done < ${tmp_file}

    # 清理临时文件
    rm -f ${tmp_file}

    # ==========================================
    # 第四步: 发送告警通知
    # ==========================================
    log "INFO" "----------------------------------------"
    log "INFO" "第四步: 发送告警通知"
    log "INFO" "----------------------------------------"

    if [ -n "${alert_msg}" ] || [ -n "${addfile_msg}" ] || [ -n "${error_msg}" ]; then
        local dingtalk_content="## 🔔 Oracle表空间监控报告\n\n"
        dingtalk_content+="**数据库**: ${ORACLE_SID}\n"
        dingtalk_content+="**检查时间**: $(date '+%Y-%m-%d %H:%M:%S')\n\n"

        if [ -n "${alert_msg}" ]; then
            dingtalk_content+="### ⚠️ 使用率告警(>=${ALERT_THRESHOLD}%)\n${alert_msg}\n\n"
        fi

        if [ -n "${addfile_msg}" ]; then
            dingtalk_content+="### ✅ 自动扩容成功\n${addfile_msg}\n\n"
        fi

        if [ -n "${error_msg}" ]; then
            dingtalk_content+="### ❌ 异常信息\n${error_msg}\n\n"
        fi

        dingtalk_content+="---\n**详细日志**: ${LOG_FILE}"

        # 发送钉钉告警
        send_dingtalk "Oracle表空间监控报告" "${dingtalk_content}"

        # 发送邮件告警
        local mail_subject="[Oracle监控] 表空间告警 - ${ORACLE_SID} - $(date '+%Y%m%d')"
        local mail_body=$(echo -e "${dingtalk_content}" | sed 's/## g' | sed 's/### g' | sed 's/\*\*//g')
        send_mail "${mail_subject}" "${mail_body}"
    else
        log "INFO" "✅ 所有表空间使用率正常,无需告警"
    fi

    # ==========================================
    # 第五步: 生成汇总日志
    # ==========================================
    log "INFO" "============================================================"
    log "INFO" "  表空间监控执行完成"
    log "INFO" "  完成时间: $(date '+%Y-%m-%d %H:%M:%S')"
    log "INFO" "  日志文件: ${LOG_FILE}"
    log "INFO" "============================================================"

    # 清理30天以前的日志文件
    find ${LOG_DIR} -name "tablespace_monitor_*.log" -mtime +30 -delete
    log "INFO" "已清理30天前的历史日志"
}

# ===================== 执行入口 =====================
# 记录脚本开始时间
START_TIME=$(date +%s)

# 执行主逻辑,所有输出同时写入日志
main 2>&1 | tee -a ${LOG_FILE}

# 计算执行时长
END_TIME=$(date +%s)
ELAPSED=$((END_TIME - START_TIME))
log "INFO" "脚本总执行时长: ${ELAPSED}秒"

2.3 脚本关键点解析

看到这里,你可能觉得脚本很长。确实,但每一个函数都有明确的职责。

钉钉的话,需要打通钉钉API网址,也可以通过代理转发形式或者网闸等内外网隔离设备。

这种脚本方式,自然是最原始的,有条件,可以通过统一运维平台去告警外发,这里也推荐几个个人觉得非常不错的,比如夜莺/hertzbeat项目,老牌的普罗米修斯/Zabbix等也可以。

我来梳理几个关键点:

🔑 关键点1:表空间使用率的双重检查

脚本中做了两层判断:

层级
阈值
动作
第一层
≥80%
发送告警通知
第二层
≥90%
告警 + 自动添加数据文件

这样设计是因为:

  • 80%只是预警,可能不需要立即扩容
  • 90%是危险线,必须自动干预

🔑 关键点2:存储类型智能判断

if [[ "${file_path}" == +* ]]; then
    # ASM路径以+开头
    storage_type="ASM"
else
    # 文件系统路径
    storage_type="FS"
fi

这是区分ASM和文件系统最简单可靠的方式——ASM路径一定以 +
 开头。

🔑 关键点3:数据文件命名防冲突

# 循环检查文件名是否已存在
while true; do
    file_name="${dir}/${ts_name,,}_$(printf '%02d' ${counter}).dbf"
    exists=$(check_file_exists "${file_name}")
    if [ "${exists}" = "0" ]; then
        break
    fi
    counter=$((counter + 1))
done

先查 dba_data_files
 确认文件名不冲突,再执行 ALTER TABLESPACE ADD DATAFILE
先验后做,永不冲突。

🔑 关键点4:操作日志全记录

脚本记录了:

  • 每次检查的所有表空间使用率
  • 决策过程(为什么扩容/为什么不扩容)
  • 执行的完整SQL语句
  • 执行结果和验证
  • 告警发送结果

日志格式示例:

[2026-04-22 03:00:01] [INFO] ============================================================
[2026-04-22 03:00:01] [INFO]   Oracle表空间监控自动扩容脚本启动
[2026-04-22 03:00:01] [INFO]   执行时间: 2026-04-22 03:00:01
[2026-04-22 03:00:01] [INFO]   数据库实例: ORCL
[2026-04-22 03:00:02] [INFO] 数据库连接成功
[2026-04-22 03:00:03] [WARN] ⚠️ 表空间 USERS 使用率 92.35% >= 告警阈值 80%
[2026-04-22 03:00:03] [WARN] 🚨 表空间 USERS 使用率 92.35% >= 自动扩容阈值 90%
[2026-04-22 03:00:03] [INFO] 存储类型: FS, 数据文件路径: oradata/ORCL/users01.dbf
[2026-04-22 03:00:03] [INFO] 文件系统 oradata/ORCL 可用空间: 256GB > 最低要求 50GB, 允许扩容
[2026-04-22 03:00:04] [INFO] 生成新数据文件名: oradata/ORCL/users_02.dbf
[2026-04-22 03:00:04] [INFO] 执行SQL: ALTER TABLESPACE USERS ADD DATAFILE '/oradata/ORCL/users_02.dbf' SIZE 10G AUTOEXTEND ON NEXT 1G MAXSIZE 32G;
[2026-04-22 03:00:05] [INFO] ✅ 数据文件添加成功!
[2026-04-22 03:00:05] [INFO] ✅ 表空间 USERS 自动扩容成功
[2026-04-22 03:00:06] [INFO] 钉钉告警发送成功


三、定时任务配置

3.1 Crontab配置

将脚本配置为定时执行,建议每30分钟检测一次:

# 编辑crontab
crontab -e

# 添加以下内容(每30分钟执行一次)
*/30 * * * * u01/scripts/oracle_tablespace_monitor.sh >> u01/scripts/log/tablespace_monitor/cron.log 2>&1

3.2 执行频率建议

业务场景
建议频率
说明
核心交易库
每15分钟
数据增长快,需要高频监控
一般业务库
每30分钟
平衡监控频率和资源消耗
开发测试库
每2小时
数据增长慢,无需高频
数据仓库
每15分钟
ETL期间数据增长迅猛

四、钉钉机器人配置详解

4.1 创建钉钉群机器人

  1. 打开钉钉群 → 群设置 → 智能群助手 → 添加机器人
  2. 选择"自定义"机器人
  3. 设置机器人名称(如:Oracle监控告警)
  4. 安全设置选择"自定义关键词",输入"Oracle"
  5. 复制Webhook地址,替换脚本中的 DINGTALK_WEBHOOK

4.2 告警效果展示

钉钉收到的告警消息效果如下:

🔔 Oracle表空间监控报告

数据库: ORCL
检查时间: 2026-04-22 03:00:01

⚠️ 使用率告警(>=80%)
- USERS: 92.35% (已用29552MB 上限32000MB)
- UNDO_TBS: 85.67% (已用27414MB 上限32000MB)

✅ 自动扩容成功
- USERS: 已添加 oradata/ORCL/users_02.dbf (10G)

---
详细日志: u01/scripts/log/tablespace_monitor/tablespace_monitor_20260422_030001.log

4.3 邮件告警备选方案

如果你的环境不方便用钉钉,邮件告警也是不错的选择。脚本中已集成邮件发送功能,配置好 MAIL_TO
MAIL_FROM
MAIL_SMTP
 即可。

进阶玩法:可以使用Python的 smtplib
 + email
 模块发送HTML格式的邮件,表格更美观。但考虑到脚本的可移植性(不一定有Python环境),这里用的是最基础的 mailx


五、ASM磁盘组监控SQL大全和基础知识

表空间使用率,也没办法绕开ASM磁盘组管理,生产RAC集群大多都使用ASM管理数据文件,添加扩容,也需要同步检测ASM磁盘组,除了通过grid用户的asmcmd管理查看,使用oracle用户的基本SQL也可以非常直观的去看到相关信息。从Oracle10g到目前的Oracle 26ai,ASM也发生了很大的变化。

5.1 ASM磁盘组基本监控

对于使用ASM存储的环境,磁盘组的监控同样重要。

查看所有磁盘组状态和空间

-- =============================================
-- ASM磁盘组空间使用率监控
-- 适用版本: 11g/12c/19c/23ai
-- =============================================
SELECT 
    name AS diskgroup_name,
    state AS diskgroup_state,
    type AS redundancy_type,
    ROUND(total_mb/1024, 2) AS total_gb,
    ROUND(free_mb/1024, 2) AS free_gb,
    ROUND((total_mb - free_mb)/1024, 2) AS used_gb,
    ROUND((total_mb - free_mb) total_mb * 100, 2) AS pct_used,
    ROUND(usable_file_mb/1024, 2) AS usable_free_gb,  -- 考虑冗余后的实际可用
    required_mirror_free_mb/1024 AS required_mirror_free_gb  -- 冗余所需空间
FROM v$asm_diskgroup
ORDER BY pct_used DESC;

💡 关键字段说明
total_mb
: 磁盘组总容量
free_mb
: 磁盘组剩余空间
usable_file_mb
: 考虑镜像冗余后,实际可用的空间。这个才是你真正能写入数据的剩余空间!
- 对于 NORMAL
 冗余,usable_file_mb ≈ free_mb 2

- 对于 HIGH
 冗余,usable_file_mb ≈ free_mb 3

查看ASM磁盘详情

-- =============================================
-- ASM磁盘详细信息
-- =============================================
SELECT 
    dg.name AS diskgroup_name,
    d.name AS disk_name,
    d.path AS disk_path,
    d.failgroup AS failure_group,
    ROUND(d.total_mb/1024, 2) AS total_gb,
    ROUND(d.free_mb/1024, 2) AS free_gb,
    ROUND((d.total_mb - d.free_mb) d.total_mb * 100, 2) AS pct_used,
    d.state AS disk_state,
    d.mount_status,
    d.header_status,
    d.mode_status,
    d.redundancy
FROM v$asm_disk d, v$asm_diskgroup dg
WHERE d.group_number = dg.group_number
ORDER BY dg.name, d.name;

查看ASM磁盘组的IO统计

-- =============================================
-- ASM磁盘组IO性能统计
-- =============================================
SELECT 
    dg.name AS diskgroup_name,
    SUM(d.reads) AS total_reads,
    SUM(d.writes) AS total_writes,
    ROUND(SUM(d.read_time) * 1000 NULLIF(SUM(d.reads), 0), 2) AS avg_read_ms,
    ROUND(SUM(d.write_time) * 1000 NULLIF(SUM(d.writes), 0), 2) AS avg_write_ms,
    SUM(d.bytes_read) 1024/1024/1024 AS total_read_gb,
    SUM(d.bytes_written) 1024/1024/1024 AS total_write_gb
FROM v$asm_disk_stat d, v$asm_diskgroup dg
WHERE d.group_number = dg.group_number
GROUP BY dg.name;

查看ASM磁盘组客户端连接

-- =============================================
-- ASM客户端连接信息(RAC环境下很有用)
-- =============================================
SELECT 
    dg.name AS diskgroup_name,
    c.instance_name,
    c.db_name,
    c.status,
    c.SOFTWARE_VERSION,
    c.COMPATIBLE_VERSION
FROM v$asm_client c, v$asm_diskgroup dg
WHERE c.group_number = dg.group_number
ORDER BY dg.name, c.instance_name;

5.2 ASM磁盘组告警阈值建议

冗余类型
告警阈值
危险阈值
说明
External
80%
90%
无镜像,空间紧张就是紧张
Normal
70%
85%
考虑镜像占用,usable_file_mb更关键
High
60%
75%
三重镜像,可用空间是1/3
⚠️ 重要提示:监控ASM空间时,不要只看free_mb,要看 usable_file_mb

usable_file_mb
 才是扣除冗余后真正能用的空间。如果 usable_file_mb
 变成负数,说明磁盘组已经无法保证冗余策略,新数据无法写入,这是紧急状态

5.3 综合监控SQL(推荐)

-- =============================================
-- ASM磁盘组综合监控(推荐用于告警脚本)
-- 直接给出可用/不可用的判断
-- =============================================
SELECT 
    name AS diskgroup_name,
    state,
    type AS redundancy,
    ROUND(total_mb/1024, 2) AS total_gb,
    ROUND(free_mb/1024, 2) AS free_gb,
    ROUND(usable_file_mb/1024, 2) AS usable_free_gb,
    ROUND((1 - usable_file_mb NULLIF(total_mb, 0)) * 100, 2) AS effective_pct_used,
    CASE
        WHEN usable_file_mb < 0 THEN '🔴 CRITICAL - 无法保证冗余!'
        WHEN ROUND(usable_file_mb/1024, 2) < 50 THEN '🟡 WARNING - 可用空间不足50GB'
        WHEN ROUND((1 - usable_file_mb NULLIF(total_mb, 0)) * 100, 2) > 85 THEN '🟡 WARNING - 使用率超85%'
        ELSE '🟢 NORMAL'
    END AS alert_status
FROM v$asm_diskgroup
ORDER BY effective_pct_used DESC;


六、ASM磁盘组类型与冗余度详解

6.1 ASM冗余度类型

ASM提供三种冗余策略,创建磁盘组时选定,创建后不可更改

External(外部冗余)

项目
内容
冗余类型
External 冗余
数据
1 份
镜像
无(依赖底层存储)
磁盘最少
1 块
可用空间
100%
性能
最佳
External冗余度,多用于追求存储空间最大化,且存储可用性完全依赖底层存储设备,如共享存储的RAID机制,底层存储双活复制,或者存储级别的冗余,Oralce数据库完全不关心数据冗余,所有数据从Oracle LGWR写入ASM磁盘组只会写入一份。
  • 适用场景
    :底层存储已经做了RAID(如SAN、高端存储阵列)
  • 优点
    :所有磁盘空间都可用于数据存储,性能最优
  • 缺点
    :完全依赖底层存储的可靠性
  • 生产建议
    :如果有企业级SAN存储,优先选择

Normal(常规冗余 双向镜像)

项目
内容
冗余类型
Normal 冗余
数据
1 份
镜像
1 份(共 2 份)
磁盘最少
2 块(不同故障组)
可用空间
50%
性能
良好
Normal方式用的比较多,如OCR磁盘;也是最省钱的存储双活方式,底层只需要2台存储+1台仲裁存储(NFS也可以),即可实现数据库存储的双活部署和冗余。数据写入双副本镜像模式。针对OCR的高可用和无底层双活存储直接复制的情况下,如何部署高可用架构,这个后续我再把我的生产环境部署方案分享一下。单独写一篇吧。很多数据库一体机,存储节点高可用也采用ASM冗余来做的。包括Oracle自己的Exadata一体机也是。
  • 适用场景
    :使用本地磁盘或普通存储,需要数据保护
  • 优点
    :单块磁盘故障不影响数据可用性
  • 缺点
    :可用空间减半
  • 生产建议
    :RAC环境的默认推荐选择

High(高冗余 三向镜像)

项目
内容
冗余类型
High 冗余
数据
1 份
镜像
2 份(共 3 份)
磁盘最少
3 块(不同故障组)
可用空间
33.3%
性能
一般
High是冗余度最高的,三副本模式,所有数据均衡分部在三个不同的FailGroup-故障组内。
  • 适用场景
    :金融核心、超高可用性要求
  • 优点
    :同时坏两块磁盘数据仍然安全
  • 缺点
    :可用空间只有1/3,成本高
  • 生产建议
    :关键业务系统,存储成本不是瓶颈时考虑

6.2 选择冗余度的决策树

可通过这个决策树判断,是否需要。最终还是要结合实际生产和现实条件去做选择。副本越多自然是更加安全可靠。

七、Oracle各版本ASM变化与限制

7.1 ASM版本演进概览

ASM 版本
核心定位
关键特性
10g
ASM 诞生
基础文件系统、ASM 基础功能
11g
ASM 成熟
ACFS、ASMCA
12c
Flex ASM 架构
ADVM、ASM Proxy
19c
增强管理
自动化管理、高可用增强
23ai
AI 融合
智能运维、云原生

7.2 各版本详细对比

Oracle 11g R2 (11.2.0.x)

ASM核心特性:

  • ASM成为Grid Infrastructure的核心组件
  • 引入ASMCA(ASM Configuration Assistant)图形化管理
  • ACFS(ASM Cluster File System)文件系统
  • ADVM(ASM Dynamic Volume Manager)卷管理
  • ASM实例可以独立于数据库运行

磁盘大小限制:

  • 单个ASM磁盘最大:2TB(外部冗余)/ 2TB(普通冗余)
  • 单个磁盘组最大磁盘数:10000块
  • 单个ASM实例最大管理磁盘组:63个
  • 每个磁盘组最大磁盘数:10000块

文件大小限制(8KB块大小):

  • 外部冗余:32TB(2^32 × 8KB)
  • 普通冗余:16TB
  • 高冗余:10.6TB

注意: 11g的单盘限制实际上受ASM层面和操作系统层面的双重限制。在32位系统上,单盘上限更小。

Oracle 12c R1/R2 (12.1.0.x 12.2.0.x)

新增特性:

  • Flex ASM
    :ASM实例不再需要在每个节点运行,通过代理模式访问远程ASM
  • ASM Filter Driver (AFD)
    :取代ASMLib,提供更可靠的磁盘访问控制
  • ASM File Groups
    :支持在同一个磁盘组内按属性分组管理文件
  • ASM Quota Groups
    :磁盘组级别的空间配额管理

磁盘大小限制提升:

  • 单个ASM磁盘最大:4PB(理论值,受存储阵列限制)
  • 单个磁盘组最大容量:4EB(理论值)
  • 实际测试中单盘可以支持64TB+的LUN

12c R2关键改进:

  • 支持4KB扇区磁盘的原生IO
  • ASM Rebalance性能提升(增量rebalance)
  • 支持NVMe设备直连

Oracle 19c (19.3+)

新增/增强特性:

  • ASM Metadata自动备份
    :元数据定期自动备份到磁盘组
  • ASM克隆
    :支持磁盘组级别的克隆
  • ASM Flex Redundancy
    :更灵活的冗余策略(每个文件独立设置冗余级别)
  • Automatic Big Table Caching
    集成ASM
  • 数据卫士增强
    :DG中的ASM管理更加自动化

磁盘大小限制:

  • 单个ASM磁盘最大:无硬编码限制(受存储和OS限制)
  • 实际生产环境中支持单盘64TB
  • 单个磁盘组最大磁盘数:10000块
  • 每个节点最大磁盘组:63个
  • 每个数据库实例最大数据文件:65533个

19c ASM关键变化:

  • 支持4K原生扇区磁盘的完整IO
  • ASM rebalance算法优化,支持并行rebalance
  • ASM Scrubbing
    :磁盘数据一致性自动检查和修复
  • 支持更细粒度的ASM权限管理

Oracle 21c (21.3+)

作为创新版本(非长期支持),21c引入了一些试验性特性:

  • ASM与Kubernetes集成
    :支持在K8s环境中的ASM管理
  • ASM增强的云存储支持
    :更好地支持公有云块存储
  • 智能化磁盘管理
    :基于使用模式的自动优化

Oracle 23ai (23.4+)

革命性变化:

  • True Cache与ASM集成
    :True Cache使用ASM管理缓存数据
  • JSON Relational Duality
    在ASM层面的优化存储
  • AI向量索引
    的存储优化
  • Automatic Indexing增强
    在ASM层面的存储管理

磁盘大小限制(23ai):

  • 单个ASM磁盘:无限制(受存储阵列和OS限制)
  • 实际测试支持单盘128TB+
  • 磁盘组总容量:理论无限制
  • 文件数量限制:2^48个(巨大到几乎无限)

23ai RAC + ASM变化:

  • 零停机ASM升级
    :更平滑的ASM版本升级
  • 智能故障组管理
    :基于地理位置的故障组自动分配
  • ASM与Oracle Database Vault集成增强

Oracle 26ai (预计2026)

预期变化:

  • 全栈AI驱动运维
    :ASM级别的AI预测性维护
  • 存储智能分层
    :热/温/冷数据自动在ASM磁盘间迁移
  • 量子安全加密
    :ASM元数据加密支持量子安全算法
  • 超大规模支持
    :单个磁盘组支持EB级容量
  • 边缘计算集成
    :ASM支持边缘节点的轻量化部署

26ai ASM预期限制(推测):

  • 单盘:无限制
  • 单磁盘组容量:256EB+
  • 单数据库数据文件:100万+
  • 节点数量:1024+节点

7.3 各版本限制汇总表

版本
单盘最大
磁盘组最大盘数
最大磁盘组数
最大文件数
关键变化
11g R2
2TB
10000
63
65533
ACFS/ADVM
12c R1
4PB(理论)
10000
63
65533
Flex ASM/AFD
12c R2
64TB+
10000
63
65533
NVMe支持
19c
64TB+
10000
63
65533
元数据备份/Scrubbing
21c
64TB+
10000
63
65533
K8s集成
23ai
128TB+
10000
63
2^48
AI集成/True Cache
26ai
无限制
更大
更大
100万+
全栈AI/智能分层
⚠️ 重要说明:表中的"理论最大值"是Oracle软件层面的限制,实际限制取决于:
1. 操作系统对块设备大小的限制
2. 存储阵列的LUN大小限制
3. 文件系统的限制
4. 网络带宽(RAC环境)

7.4 RAC环境下ASM的关键变化

11g RAC + ASM

11g版本,所有节点必须Oralce 实例和ASM实例一对一,每个GRID下一个ASM实例对接各自节点的DB实例,1对1服务。
  • 每个节点都运行独立的ASM实例
  • ASM实例之间通过心跳同步元数据
  • 节点故障时,其他节点的ASM实例接管其磁盘组管理

12c RAC + Flex ASM

12c Flex开始,可以允许某些节点不创建ASM实例,而是通过ASM代理远程访问。
  • Flex ASM允许不是每个节点都运行ASM实例
  • 通过ASM代理(Listener)访问远程ASM
  • 减少了资源占用,提高了灵活性
  • 默认Flex ASM基数为3
    (即最多允许同时3个ASM实例故障)

19c/23ai RAC + ASM

  • Flex ASM成为默认配置
  • 支持ASM Rolling Upgrade:逐节点升级ASM,无需全集群停机
  • ASM Filter Driver (AFD)
     更加成熟,取代ASMLib
  • 支持Flex Cluster:Hub节点和Leaf节点分离架构
  • 23ai引入True Cache:利用ASM管理的共享存储做分布式缓存

7.5 ASM最佳实践建议

1. 磁盘组规划

建议的磁盘组划分:
+DATA    → 数据文件、控制文件、在线日志(Normal冗余)
+FRA     → 归档日志、RMAN备份(Normal冗余)
+OCR     → 集群注册表、表决盘(Normal/High冗余,可共享)

2. 故障组设置

  • 每个故障组放在不同的物理磁盘/存储控制器下
  • RAC环境中,故障组最好跨越不同的存储机柜
  • 故障组大小尽量一致

3. AU(Allocation Unit)大小选择

AU大小
适用场景
1MB
默认值,适合大多数场景
2MB
小文件较多的场景
4MB
大文件为主的OLAP/数据仓库
8MB
超大型数据仓库
16MB
超大规模环境

4. Rebalance调优

-- 调整rebalance的并行度和速度
ALTER DISKGROUP DATA REBALANCE POWER 8 WAIT;
-- POWER范围: 0-1024, 默认11
-- 越大越快,但对IO影响越大
-- 建议: OLTP环境用4-8, 维护窗口用16-32


八、表空间告警升级机制与多库批量监控

8.1 告警升级机制

在真实的生产环境中,仅仅发送一条告警是不够的。一个成熟的监控体系应该有告警升级机制

可根据实际生产环境定义调整

实现告警升级的关键是状态文件,记录每次告警的时间和级别:

# 告警状态文件
ALERT_STATE_FILE=${LOG_DIR}/alert_state.json

# 检查告警频率
check_alert_frequency() {
    local ts_name=$1
    local alert_level=$2

    if [ -f "${ALERT_STATE_FILE}" ]; then
        local last_alert=$(grep "\"${ts_name}\":{\"level\":\"${alert_level}\"" ${ALERT_STATE_FILE} | \
            grep -o '"last_alert":[0-9]*' | cut -d: -f2)
        local now=$(date +%s)
        local diff=$((now - last_alert))

        # 同级别告警间隔至少30分钟
        if [ ${diff} -lt 1800 ]; then
            logf "INFO" "表空间 ${ts_name} ${alert_level}级别告警间隔不足30分钟,跳过"
            return 1
        fi
    fi
    return 0
}

8.2 多实例批量监控脚本

当你的环境中有多个Oracle实例时,逐个执行脚本效率太低。一个批量监控脚本可以同时检查所有实例:

#!/bin/bash
#=============================================================
# 脚本名称: oracle_multi_instance_monitor.sh
# 脚本功能: 多实例表空间批量监控
# 说明: 遍历服务器上所有Oracle实例,逐个检测
#=============================================================

LOG_DIR=/u01/scripts/log/tablespace_monitor
mkdir -p ${LOG_DIR}
LOG_FILE=${LOG_DIR}/multi_instance_monitor_$(date +%Y%m%d_%H%M%S).log

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$1] $2" | tee -a ${LOG_FILE}
}

# 获取本机所有运行的Oracle实例
get_running_instances() {
    # 方法1: 通过oratab
    if [ -f etc/oratab ]; then
        grep -v '^#' etc/oratab | grep -v '^$' | \
            awk -F: '{if($2!="") print $1":"$2}' | \
            while read entry; do
                local sid=$(echo ${entry} | cut -d: -f1)
                local home=$(echo ${entry} | cut -d: -f2)
                # 检查实例是否运行
                local pmon_pid=$(ps -ef | grep "ora_pmon_${sid}" | grep -v grep | awk '{print $2}')
                if [ -n "${pmon_pid}" ]; then
                    echo "${sid}:${home}"
                fi
            done
    fi

    # 方法2: 通过ps命令(备用)
    ps -ef | grep 'ora_pmon_' | grep -v grep | \
        awk '{print $NF}' | sed 's/ora_pmon_//' | \
        while read sid; do
            local home=$(ps -ef | grep "ora_pmon_${sid}" | grep -v grep | \
                awk '{print $8}' | xargs dirname | xargs dirname)
            echo "${sid}:${home}"
        done | sort -u
}

# 执行单个实例的监控
monitor_instance() {
    local sid=$1
    local home=$2

    log "INFO" "=========================================="
    log "INFO" "开始监控实例: ${sid}"
    log "INFO" "ORACLE_HOME: ${home}"
    log "INFO" "=========================================="

    export ORACLE_SID=${sid}
    export ORACLE_HOME=${home}
    export PATH=${home}/bin:${PATH}

    # 调用单实例监控脚本
    if [ -f u01/scripts/oracle_tablespace_monitor.sh ]; then
        u01/scripts/oracle_tablespace_monitor.sh
    else
        log "ERROR" "监控脚本不存在: u01/scripts/oracle_tablespace_monitor.sh"
    fi
}

# ===================== 主逻辑 =====================
log "INFO" "=========================================="
log "INFO" "多实例表空间批量监控开始"
log "INFO" "=========================================="

instance_list=$(get_running_instances)
instance_count=$(echo "${instance_list}" | wc -l)

log "INFO" "发现 ${instance_count} 个运行中的Oracle实例"

if [ -z "${instance_list}" ]; then
    log "WARN" "未发现运行中的Oracle实例"
    exit 0
fi

echo "${instance_list}" | while IFS=: read sid home; do
    monitor_instance "${sid}" "${home}"
done

log "INFO" "=========================================="
log "INFO" "多实例批量监控完成"
log "INFO" "=========================================="

8.3 数据文件增长趋势分析

除了实时监控,了解数据文件的增长趋势也很重要。这能帮助你提前预判容量需求:

-- =============================================
-- 表空间增长趋势分析(最近30天)
-- 依赖AWR快照数据
-- =============================================
SELECT 
    ts.name AS tablespace_name,
    TO_CHAR(snap.begin_interval_time, 'YYYY-MM-DD') AS snap_date,
    ROUND(MAX(tu.tablespace_size * dt.block_size 1024 1024), 2) AS total_mb,
    ROUND(MAX(tu.tablespace_usedsize * dt.block_size 1024 1024), 2) AS used_mb,
    ROUND(MAX(tu.tablespace_usedsize) MAX(tu.tablespace_size) * 100, 2) AS pct_used
FROM dba_hist_tbspc_space_usage tu,
     v$tablespace ts,
     dba_hist_snapshot snap,
     dba_tablespaces dt
WHERE tu.tablespace_id = ts.ts#
AND tu.snap_id = snap.snap_id
AND ts.name = dt.tablespace_name
AND snap.begin_interval_time >= SYSDATE - 30
GROUP BY ts.name, TO_CHAR(snap.begin_interval_time, 'YYYY-MM-DD')
ORDER BY ts.name, snap_date;

基于这个数据,可以计算每日增长量预计用尽天数

-- =============================================
-- 计算表空间每日增长量和预计用尽天数
-- =============================================
WITH growth_data AS (
    SELECT 
        ts.name AS ts_name,
        ROUND(AVG(daily_growth), 2) AS avg_daily_growth_mb,
        ROUND(MAX(current_used), 2) AS current_used_mb,
        ROUND(MAX(current_total), 2) AS current_total_mb
    FROM (
        SELECT 
            ts.ts# AS ts_id,
            ts.name,
            LAG(tu.tablespace_usedsize * dt.block_size 1024 1024) 
                OVER (PARTITION BY ts.ts# ORDER BY snap.snap_id) AS prev_used,
            tu.tablespace_usedsize * dt.block_size 1024 1024 AS curr_used,
            (tu.tablespace_usedsize * dt.block_size 1024 1024) - 
            LAG(tu.tablespace_usedsize * dt.block_size 1024 1024) 
                OVER (PARTITION BY ts.ts# ORDER BY snap.snap_id) AS daily_growth,
            LAST_VALUE(tu.tablespace_usedsize * dt.block_size 1024 1024) 
                OVER (PARTITION BY ts.ts# ORDER BY snap.snap_id 
                      ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS current_used,
            LAST_VALUE(tu.tablespace_size * dt.block_size 1024 1024) 
                OVER (PARTITION BY ts.ts# ORDER BY snap.snap_id 
                      ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS current_total
        FROM dba_hist_tbspc_space_usage tu,
             v$tablespace ts,
             dba_hist_snapshot snap,
             dba_tablespaces dt
        WHERE tu.tablespace_id = ts.ts#
        AND tu.snap_id = snap.snap_id
        AND ts.name = dt.tablespace_name
        AND snap.begin_interval_time >= SYSDATE - 7
    )
    WHERE daily_growth > 0
    GROUP BY ts.name
)
SELECT 
    ts_name,
    avg_daily_growth_mb,
    current_used_mb,
    current_total_mb,
    ROUND((current_total_mb - current_used_mb), 2) AS remaining_mb,
    CASE 
        WHEN avg_daily_growth_mb > 0 
        THEN ROUND((current_total_mb - current_used_mb) avg_daily_growth_mb, 0)
        ELSE 9999
    END AS days_until_full
FROM growth_data
ORDER BY days_until_full ASC;

💡 实践意义:如果某个表空间预计30天内用尽,现在就该规划扩容了,而不是等到90%告警才手忙脚乱。

九、生产环境注意事项与最佳实践

8.1 自动扩容的安全策略

自动扩容虽好,但不能无脑加。以下几个安全策略必须在脚本中体现:

策略1:磁盘空间余量检查

# 在添加数据文件之前,检查目标目录/ASM磁盘组的可用空间
# 建议保留至少20%的磁盘空间作为安全余量
if [ ${avail_space} -lt ${MIN_REQUIRED_SPACE} ]; then
    log "ERROR" "剩余空间不足,暂停自动扩容"
    send_dingtalk "🚨 空间不足" "存储空间即将用尽,请人工介入!"
    exit 1
fi

策略2:表空间文件数量限制

# 检查当前表空间已有多少数据文件
# 接近Oracle限制时,提前告警
local file_count=$(sqlplus -S "${DB_USER}" <<EOF
SELECT COUNT(*) FROM dba_data_files WHERE tablespace_name = '${ts_name}';
EOF
)

if [ ${file_count} -ge 60000 ]; then
    log "WARN" "表空间 ${ts_name} 数据文件数量(${file_count})接近上限,请关注"
fi

策略3:扩容次数限制

# 同一个表空间,一个维护窗口内最多自动扩容N次
# 避免磁盘瞬间被吃光
MAX_AUTOADD_COUNT=5

8.5 数据文件大小的选择建议

在自动扩容脚本中,每次添加多大的数据文件?这个看似简单的问题,其实有很多讲究。

建议:

表空间当前大小
建议增量
说明
< 100GB
10G
小表空间,增量适中
100GB - 500GB
20G
中等表空间
500GB - 1TB
50G
大表空间,减少文件碎片
> 1TB
100G
超大表空间,减少文件数量

原因分析:

  1. 文件数量
    :每个数据文件都需要Oracle维护元数据,文件太多会影响性能
  2. 碎片问题
    :如果增量太小,文件会分散在不同的磁盘位置
  3. 恢复时间
    :单个文件太大,RMAN恢复单个文件的时间会很长
  4. rebalance时间
    :ASM环境中,添加大文件后rebalance耗时更长

通用规则:

  • 单个数据文件不要超过32GB(8K块大小)
  • 一个表空间的数据文件数量控制在100个以内
  • ASM环境中,AU大小为4MB或8MB时,文件增量建议是AU的整数倍

8.6 监控脚本的部署架构

在大型企业中,推荐使用中心化的监控架构:

这个具体看各自部署了,现在也有很多基于AI Agent的各种运维skill,百花齐现,更多的通过蒸馏各大技术和开源平台的能力、资源,进行蒸馏、总结,产生的各种知识库,AI Skill,现在是AI Agent爆发的时期,也是各种技能、能力Skill化的时期,AI的发展已经从Token消耗转变为数字员工(技能Skill包)的形式,未来运维、DBA是否也会被Skill化?数据库运维、数据库优化,这些可通过具体指标输入、然后综合判断分析输出结果的技能,在我看来也是非常容易被AI Skill化的能力。
  • 控制节点通过SSH向各DB服务器下发监控脚本
  • 各节点执行结果回传到控制节点
  • 控制节点汇总分析后统一告警
  • 可以使用Ansible/SaltStack等工具实现

策略4:大表空间人工审核

# 超过一定大小的表空间,自动扩容前通知DBA确认
if [ ${current_size_gb} -gt 1024 ]; then
    log "WARN" "表空间 ${ts_name} 已超过1TB,自动扩容需人工确认"
    send_dingtalk "⚠️ 需人工确认" "表空间 ${ts_name} 已达 ${current_size_gb}GB,请确认是否扩容"
    continue  # 跳过自动扩容
fi

8.2 日志管理规范

日志不仅是排查问题的依据,也是审计的重要材料。

日志保留策略:

# 清理30天以前的日志
find ${LOG_DIR} -name "*.log" -mtime +30 -delete

# 日志文件命名规范
# tablespace_monitor_YYYYMMDD_HHMMSS.log
# 便于按日期查找

日志分级:

  • INFO
    :正常操作记录
  • WARN
    :需要关注但不影响功能
  • ERROR
    :操作失败,需要人工介入
  • DEBUG
    :详细的调试信息(默认不输出到终端,只写文件)

8.3 告警降噪策略

告警太多会让DBA麻木,几个降噪技巧:

  1. 聚合告警
    :一次检查只发一条汇总消息,不要每个表空间发一条
  2. 告警频率限制
    :同一问题24小时内只告警N次
  3. 自动恢复通知
    :扩容成功后通知已恢复正常
  4. 夜间静默
    :23:00-7:00的非紧急告警延迟到早上发送

8.4 监控大屏集成

如果你的团队有监控大屏(如Grafana + Prometheus),可以将表空间使用率数据推送到时序数据库:

# 推送数据到Prometheus Pushgateway
cat <<EOF | curl --data-binary @- http://pushgateway:9091/metrics/job/oracle_tablespace_monitor/instance/${ORACLE_SID}
# HELP oracle_tablespace_pct_used Tablespace real usage percentage
# TYPE oracle_tablespace_pct_used gauge
oracle_tablespace_pct_used{tablespace="USERS",instance="${ORACLE_SID}"} 92.35
oracle_tablespace_pct_used{tablespace="SYSTEM",instance="${ORACLE_SID}"} 45.67
oracle_tablespace_pct_used{tablespace="SYSAUX",instance="${ORACLE_SID}"} 78.23
EOF


九、常见问题FAQ

Q1: AUTOEXTEND ON的表空间还需要自动扩容吗?

需要。 AUTOEXTEND是在现有数据文件上自动增长,但文件有最大限制(通常是32GB)。当文件接近上限时,如果不添加新的数据文件,增长就会停止。

自动扩容(添加新数据文件)和自动扩展(AUTOEXTEND)是两个层面:

表空间扩容策略:
├── 第一层: AUTOEXTEND(自动扩展)
│   └── 文件级: 现有数据文件自动增长
│   └── 限制: 单文件最大32GB(8K块)

└── 第二层: ADD DATAFILE(自动扩容)
    └── 表空间级: 添加新数据文件
    └── 触发: 文件接近MAXBYTES时

Q2: 自动添加的数据文件放在哪里?

脚本会自动判断:

  • 如果现有数据文件在ASM(路径以 +
     开头)→ 新文件也放到同一个ASM磁盘组
  • 如果现有数据文件在文件系统 → 新文件放到同一个目录

不会跨存储类型添加。

Q3: 如何处理TEMP和UNDO表空间?

TEMP和UNDO表空间比较特殊:

  • TEMP表空间
    :通常不需要自动扩容(使用 dba_temp_files
    ,不是 dba_data_files
  • UNDO表空间
    :可以自动扩容,但要注意 UNDO_RETENTION
     的设置

脚本中可以增加排除逻辑:

# 排除不需要自动扩容的表空间
case "${ts_name}" in
    TEMP*|TEMP) 
        logf "INFO" "跳过TEMP表空间"
        continue
        ;;
    SYSTEM|SYSAUX)
        logf "INFO" "SYSTEM/SYSAUX表空间通常不需要自动扩容"
        continue
        ;;
esac

Q4: RAC环境下的注意事项

RAC环境中运行此脚本时:

  1. 只需在一个节点执行
    :因为所有节点看到的数据文件视图(dba_data_files
    )是一样的
  2. 执行节点的选择
    :建议选择primary实例节点,或者通过SCAN连接
  3. ASM磁盘组检查
    :在任何节点执行结果相同(ASM是共享的)

Q5: 如何测试脚本而不实际执行扩容?

可以增加一个 DRY_RUN
 模式:

# 在配置区域添加
DRY_RUN=true  # 设为true时只检测不扩容

# 在add_datafile函数中
if [ "${DRY_RUN}" = true ]; then
    log "INFO" "[DRY_RUN模式] 将执行: ALTER TABLESPACE ${tspace} ADD DATAFILE '${file}' SIZE ${size}"
    return 0
fi


十、总结

这篇文章从一个表空间监控的Shell脚本出发,深入到了:

  • ✅ 表空间真实使用率的计算方式(基于AUTOEXTEND MAXBYTES)
  • ✅ 文件系统 vs ASM磁盘组的智能存储判断
  • ✅ 数据文件自动命名防冲突机制
  • ✅ 完整的日志记录告警通知体系
  • ✅ ASM磁盘组的监控SQL冗余策略
  • ✅ 从11g到26ai的ASM版本演进和限制变化
  • ✅ RAC环境下ASM架构的关键差异

表空间监控是DBA的基本功,但做好并不简单。一个生产级别的脚本,需要考虑存储判断、命名冲突、日志审计、告警降噪等多个方面。希望这篇文章能帮你少踩几个坑。

如果这篇文章对你有帮助,点赞、在看、转发三连走起 🙌

下期预告:Oracle AWR报告深度解读与性能调优实战,敬请期待!


Acdante

一个专注于Oracle数据库的技术公众号
深耕数据库运维,分享实战经验
关注我,一起成长

往期回顾:

Oracle生产级别备份脚本分享——逻辑备份和物理备份(expdp&RMAN)【Oracle数据库分享--0x01】

Oracle数据加密技术演进与实践指南从10g到26ai的安全之道【Oracle数据库分享--0x02】

Oracle数据库表空间与数据文件实战指南【Oracle数据库分享--0x03】

Oracle ADG单机部署实战指南【Oracle数据库分享--0x04】

Oracle DataGuard搭建信息收集清单和ADG自动化部署脚本(单机版本)免费开放【Oracle数据库分享--0x05】



    本文为原创技术文章,如需转载请注明出处-add by Acdante。


    附录A:ASM磁盘组空间监控脚本(独立版)

    除了表空间监控,ASM磁盘组的空间也需要独立监控。下面提供一个ASM专项监控脚本:

    #!/bin/bash
    #=============================================================
    # 脚本名称: asm_diskgroup_monitor.sh
    # 脚本功能: ASM磁盘组空间监控告警
    # 适用版本: Oracle 11g/12c/19c/23ai
    # 说明: 独立的ASM磁盘组监控,可与表空间监控配合使用
    #=============================================================

    export ORACLE_HOME=/u01/app/grid/product/19.0.0/grid
    export ORACLE_SID=+ASM1
    export PATH=$ORACLE_HOME/bin:$PATH

    # 告警阈值(usable_file_mb低于此值告警,单位GB)
    WARNING_THRESHOLD=100
    CRITICAL_THRESHOLD=50

    # 通知配置
    DINGTALK_WEBHOOK="https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN"

    LOG_DIR=/u01/scripts/log/asm_monitor
    mkdir -p ${LOG_DIR}
    LOG_FILE=${LOG_DIR}/asm_monitor_$(date +%Y%m%d_%H%M%S).log

    log() {
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$1] $2" | tee -a ${LOG_FILE}
    }

    # 查询ASM磁盘组状态
    check_asm_diskgroups() {
        local sql_output=$(sqlplus -S "/ as sysasm" <<EOF
    SET HEADING OFF
    SET FEEDBACK OFF
    SET PAGESIZE 0
    SET LINESIZE 300
    SET TRIMOUT ON
    SET TRIMSPOOL ON
    SET COLSEP '|'

    SELECT 
        name,
        state,
        type,
        ROUND(total_mb/1024, 2),
        ROUND(free_mb/1024, 2),
        ROUND(usable_file_mb/1024, 2),
        CASE
            WHEN usable_file_mb < 0 THEN 'CRITICAL'
            WHEN usable_file_mb/1024 < ${CRITICAL_THRESHOLD} THEN 'CRITICAL'
            WHEN usable_file_mb/1024 < ${WARNING_THRESHOLD} THEN 'WARNING'
            ELSE 'NORMAL'
        END AS status
    FROM v\\$asm_diskgroup
    ORDER BY usable_file_mb ASC;
    EXIT;
    EOF
    )
        echo "${sql_output}"
    }

    # 检查ASM磁盘状态
    check_asm_disks() {
        local sql_output=$(sqlplus -S "/ as sysasm" <<EOF
    SET HEADING OFF
    SET FEEDBACK OFF
    SET PAGESIZE 0
    SET LINESIZE 300
    SET TRIMOUT ON
    SET TRIMSPOOL ON
    SET COLSEP '|'

    SELECT 
        dg.name,
        d.name,
        d.path,
        d.failgroup,
        d.state,
        d.mount_status,
        d.header_status,
        ROUND(d.total_mb/1024, 2),
        ROUND(d.free_mb/1024, 2),
        CASE
            WHEN d.state != 'NORMAL' THEN 'ABNORMAL'
            WHEN d.mount_status != 'CACHED' THEN 'NOT_CACHED'
            ELSE 'OK'
        END AS disk_status
    FROM v\\$asm_disk d, v\\$asm_diskgroup dg
    WHERE d.group_number = dg.group_number
    ORDER BY dg.name, d.name;
    EXIT;
    EOF
    )
        echo "${sql_output}"
    }

    # 主逻辑
    main() {
        log "INFO" "=========================================="
        log "INFO" "ASM磁盘组监控开始"
        log "INFO" "=========================================="

        # 检查磁盘组
        local dg_output=$(check_asm_diskgroups)
        local alert_msg=""
        local critical_found=false

        while IFS='|' read -r dg_name state dg_type total_gb free_gb usable_gb status; do
            [ -z "${dg_name}" ] && continue
            dg_name=$(echo "${dg_name}" | tr -d '[:space:]')
            status=$(echo "${status}" | tr -d '[:space:]')

            log "INFO" "磁盘组: ${dg_name}, 状态: ${state}, 类型: ${dg_type}, 总量: ${total_gb}GB, 可用: ${usable_gb}GB, 告警: ${status}"

            if [ "${status}" = "CRITICAL" ]; then
                alert_msg="${alert_msg}\n🔴 **${dg_name}**: 可用空间仅 ${usable_gb}GB (CRITICAL)"
                critical_found=true
            elif [ "${status}" = "WARNING" ]; then
                alert_msg="${alert_msg}\n🟡 **${dg_name}**: 可用空间 ${usable_gb}GB (WARNING)"
            fi
        done <<< "${dg_output}"

        # 检查磁盘异常
        local disk_output=$(check_asm_disks)
        local disk_alert=""

        while IFS='|' read -r dg_name disk_name disk_path fg state mount_st header_st total_gb free_gb disk_st; do
            [ -z "${dg_name}" ] && continue
            disk_st=$(echo "${disk_st}" | tr -d '[:space:]')

            if [ "${disk_st}" != "OK" ]; then
                disk_alert="${disk_alert}\n⚠️ 磁盘异常: ${disk_name} (${dg_name}) - ${disk_st}"
                critical_found=true
            fi
        done <<< "${disk_output}"

        # 发送告警
        if [ -n "${alert_msg}" ] || [ -n "${disk_alert}" ]; then
            local dingtalk_content="## 🔔 ASM磁盘组监控报告\n\n"
            dingtalk_content+="**检查时间**: $(date '+%Y-%m-%d %H:%M:%S')\n"
            dingtalk_content+="**节点**: $(hostname)\n\n"

            if [ -n "${alert_msg}" ]; then
                dingtalk_content+="### 磁盘组空间告警${alert_msg}\n\n"
            fi
            if [ -n "${disk_alert}" ]; then
                dingtalk_content+="### 磁盘状态异常${disk_alert}\n\n"
            fi

            # 发送钉钉通知
            curl -s -H "Content-Type: application/json" \
                -d "{\"msgtype\":\"markdown\",\"markdown\":{\"title\":\"ASM监控告警\",\"text\":\"${dingtalk_content}\"}}" \
                "${DINGTALK_WEBHOOK}" >/dev/null 2>&1

            if [ "${critical_found}" = true ]; then
                log "ERROR" "发现CRITICAL级别告警!"
            else
                log "WARN" "发现WARNING级别告警"
            fi
        else
            log "INFO" "✅ 所有ASM磁盘组状态正常"
        fi

        log "INFO" "=========================================="
        log "INFO" "ASM磁盘组监控完成"
        log "INFO" "=========================================="
    }

    main 2>&1 | tee -a ${LOG_FILE}


    附录B:Oracle版本与ASM单盘限制速查表

    以下表格汇总了从10g到26ai各版本中,ASM在单盘容量、磁盘数量等方面的限制:

    Oracle 10g R2

    项目
    限制值
    单个ASM磁盘最大
    2TB
    单个磁盘组最大磁盘数
    10,000
    最大磁盘组数
    63
    每个磁盘组最大文件数
    取决于DB版本
    ASM实例SGA
    共享,无独立SGA
    冗余类型
    External/Normal/High
    默认AU大小
    1MB

    Oracle 11g R2

    项目
    限制值
    单个ASM磁盘最大
    2TB(32位)/ 无限制(64位)
    单个磁盘组最大磁盘数
    10,000
    最大磁盘组数
    63
    ASM Filter Driver
    不支持(使用ASMLib)
    ACFS文件系统
    支持
    集成集群
    支持(GI集成)
    默认AU大小
    1MB

    Oracle 12c R1/R2

    项目
    限制值
    单个ASM磁盘最大
    4PB(理论)/ 实际64TB+
    单个磁盘组最大磁盘数
    10,000
    最大磁盘组数
    63
    Flex ASM
    ✅ 新增
    ASM Filter Driver
    ✅ 新增
    File Groups
    ✅ 新增
    Quota Groups
    ✅ 新增
    默认AU大小
    1MB(可调4MB/8MB/16MB)

    Oracle 19c

    项目
    限制值
    单个ASM磁盘最大
    无硬编码限制(64TB+实测)
    单个磁盘组最大磁盘数
    10,000
    最大磁盘组数
    63
    ASM Metadata自动备份
    ✅ 新增
    ASM Scrubbing
    ✅ 新增
    ASM克隆
    ✅ 新增
    Flex Redundancy
    ✅ 新增
    默认AU大小
    1MB(推荐4MB/8MB)

    Oracle 23ai

    项目
    限制值
    单个ASM磁盘最大
    128TB+(无硬编码限制)
    单个磁盘组最大磁盘数
    10,000+
    最大磁盘组数
    63+
    True Cache集成
    ✅ 新增
    AI向量索引存储优化
    ✅ 新增
    零停机ASM升级
    ✅ 新增
    智能故障组管理
    ✅ 新增
    默认AU大小
    1MB(推荐4MB/8MB)

    Oracle 26ai

    项目
    限制值
    单个ASM磁盘最大
    无限制(受OS/存储层面限制)
    全栈AI驱动运维
    ✅ 预期
    存储智能分层
    ✅ 预期
    量子安全加密
    ✅ 预期
    边缘计算支持
    ✅ 预期
    默认AU大小
    推荐8MB+
    📌 版本升级注意事项
    - 从11g升级到12c及以上版本,ASM元数据格式会自动转换
    - 建议在升级前备份ASM元数据:md_backup

    - 升级后可以调整AU大小(需要rebalance),但建议在建磁盘组时就选好
    - 12c以上的Flex ASM需要重新配置GI,不是简单的升级

    Oracle 26ai ASM相关特性梳理:

    项目
    核心值 / 规则
    说明
    三大兼容属性
    COMPATIBLE.ASMCOMPATIBLE.RDBMSCOMPATIBLE.ADVM
    分别控制:ASM 实例最低版本、数据库最低兼容、ADVM 卷支持
    属性大小规则
    COMPATIBLE.ASM ≥ 其余两个
    必须先升 ASM,再升 RDBMS/ADVM;单向提升,不可回退
    默认 / 最小版本
    普通冗余:ASM≥11.2.0.2FLEX/EXTENDED:ASM≥12.2.0.1
    26ai 沿用 12.2 之后的基线门槛
    单磁盘大小上限≥12.1 即可支持 >2TB 盘
    非 Exadata 环境:ASM≥12.1 且 RDBMS≥12.1,无人工封顶(受 OS / 存储限制)
    最大文件大小(AU=1MB)
    External:255TBNormal:93TBHigh:62TB
    RDBMS≥12.2 时可达此上限
    最大文件大小(AU=4MB)
    External/Normal:1022TBHigh:998TB
    RDBMS≥12.2 时可达此上限
    ADVM 启用条件
    ASM≥11.2 且 ADVM≥11.2
    需加载 ADVM 驱动,才可建卷
    Flex 盘组要求
    ASM≥12.2 且 RDBMS≥12.2
    26ai 推荐生产使用 Flex 架构
    查看方式
    VASMDISKGROUP<br>VASM_ATTRIBUTEASMCMD lsattr
    低版本 ASM=10.1 时用 V$ASM_DISKGROUP

    附录C:一个真实的生产案例

    最后分享一个我亲身经历的案例,让大家感受一下表空间监控的实际价值。

    背景

    某客户的核心交易数据库(Oracle 19c RAC,2节点),每天凌晨0点开始执行大批量的订单数据ETL。某天凌晨2点,值班DBA被钉钉告警叫醒——ORDER_TBS
     表空间使用率达到94%。

    问题排查

    1. 初步检查
      :登录数据库确认,ORDER_TBS
       确实达到了94%
    2. 增长分析
      :通过AWR发现,最近2小时该表空间增长了50GB(平时每小时增长约5GB)
    3. 根本原因
      :ETL脚本的一个临时表没有清理,数据累积了2天没有删除

    应急处理

    由于脚本已经配置了自动扩容(阈值90%),系统在90%时自动添加了一个10GB的数据文件,暂时缓解了压力。但94%说明增长速度超过了自动扩容的速度。

    人工干预:

    -- 1. 清理累积的临时数据
    DELETE FROM order_temp_batch WHERE batch_date < TRUNC(SYSDATE) - 2;
    COMMIT;

    -- 2. 手动添加更大的数据文件
    ALTER TABLESPACE order_tbs ADD DATAFILE 
        '+DATA/ORCL/DATAFILE/order_tbs_05.dbf' 
        SIZE 50G AUTOEXTEND ON NEXT 2G MAXSIZE 100G;

    -- 3. 重建临时表清理逻辑
    ALTER TABLE order_temp_batch ENABLE ROW MOVEMENT;
    ALTER TABLE order_temp_batch SHRINK SPACE;

    后续改进

    1. 增加ETL脚本的临时表自动清理逻辑
    2. 调整 ORDER_TBS
       的自动扩容增量从10G改为50G
    3. 增加了容量趋势预测,提前7天预警
    4. 增加了ETL期间的表空间增长速率告警(每小时增长超过20GB就告警)

    教训

    如果没有自动扩容脚本,那次故障会导致核心交易中断,预估损失超过500万元/小时。自动扩容虽然只是加了一个10G的数据文件,但它争取到的那30分钟,足够DBA排查和人工干预了。

    自动化不是取代DBA,是给DBA争取时间,将时间用于更有价值的事情上。


    Oracle 26ai 官方ASM磁盘组相关说明:

    Features Enabled By Disk Group Compatibility Attribute Settings

    This topic describes the Oracle ASM features enabled by valid combinations of the disk group compatibility attribute settings.

    The following list applies to Oracle ASM features enabled by valid combinations of the disk group compatibility attribute settings.

    • The value of COMPATIBLE.ASM must always be      greater than or equal to the value of COMPATIBLE.RDBMS and COMPATIBLE.ADVM.
    • Starting with Oracle      Grid Infrastructure 12.2.0.1 software, the minimum setting for COMPATIBLE.ASM is 11.2.0.2.
    • A value of not      applicable (n/a) means that the setting of the attribute has no effect on      the feature.
    • Oracle ASM features not      explicitly listed in the following table do not require advancing the disk      group compatibility attribute settings.
    • Oracle ASM features      explicitly identified by an operating system in the following table are      available on that operating system starting with the associated disk group      attribute settings.
    • If encryption is      configured for the first time on Oracle ASM 11Release 2 (11.2.0.3) on      Linux or if encryption parameters must be changed or a new volume      encryption key must be created following a software upgrade to Oracle ASM      11Release 2 (11.2.0.3) on Linux, then the disk group      compatibility attributes for ASM and ADVM must be set to 11.2.0.3 or higher.
    • Oracle ACFS does not      support encryption or replication with the following: Oracle AI Database      data files, control files, online redo logs, archived redo log files,      flashback logs, RMAN backups, and Oracle Data Pump dump file sets.
    • Oracle ACFS on Oracle      Exadata storage is supported starting with Oracle Grid Infrastructure      12.1.0.2 on Linux.
    • There may also be      minimum requirements for the database COMPATIBLE initialization parameter.

    Table 4-4 Oracle ASM features enabled by disk group compatibility attribute settings

    Disk Group Features Enabled

    COMPATIBLE.ASM

    COMPATIBLE.RDBMS

    COMPATIBLE.ADVM

    Support for larger   AU sizes (32 or 64 MB)

    >= 11.1

    >= 11.1

    n/a

    Attributes   are displayed in the V$ASM_ATTRIBUTE view

    >= 11.1

    n/a

    n/a

    Fast mirror resync

    >= 11.1

    >= 11.1

    n/a

    Variable size   extents

    >= 11.1

    >= 11.1

    n/a

    Exadata storage

    >= 11.1.0.7

    >= 11.1.0.7

    n/a

    OCR and voting   files in a disk group

    >= 11.2

    n/a

    n/a

    Sector size set to   nondefault value

    >= 11.2

    >= 11.2

    n/a

    Oracle ASM SPFILE   in a disk group

    >= 11.2

    n/a

    n/a

    Oracle ASM File   Access Control

    >= 11.2

    >= 11.2

    n/a

    ASM_POWER_LIMIT value up to 1024

    >= 11.2.0.2

    n/a

    n/a

    Content type of a   disk group

    >= 11.2.0.3

    n/a

    n/a

    Appliance mode for   Oracle Exadata (no fixed partnering)

    >= 11.2.0.4

    n/a

    n/a

    Replication status   of a disk group

    >= 12.1

    n/a

    n/a

    Managing a shared   password file in a disk group

    >= 12.1

    n/a

    n/a

    Greater than 2 TB   Oracle ASM disks without Oracle Exadata storage

    >= 12.1

    >= 12.1

    n/a

    Fixed partnering   for Oracle Exadata

    >= 12.1.0.2

    n/a

    n/a

    Appliance mode for   Oracle Data Appliance (ODA)

    >= 12.1.0.2

    n/a

    n/a

    Support for Oracle   Exadata sparse disk groups (sparse disk   groups not supported in Oracle ACFS)

    >= 12.1.0.2

    >= 12.1.0.2

    n/a

    Support for resync   checkpoint

    >= 12.1.0.2

    >= 12.1.0.2

    n/a

    LOGICAL_SECTOR_SIZE

    >= 12.2

    n/a

    n/a

    Altering sector   size

    >= 12.2

    n/a

    n/a

    Oracle ASM flex   and extended disk groups

    >= 12.2

    >= 12.2

    n/a

    SCRUB_ASYNC_LIMIT

    >= 12.2

    n/a

    n/a

    PREFERRED_READ.ENABLED

    Oracle Database   12c Release 2 (12.2) is required.

    >=12.2

    n/a

    n/a

    Converting normal   or high redundancy disk groups to flex disk groups without restricted mount

    >=18.0

    >=12.2

    n/a

    Oracle ASM flex   disk group support for multitenant cloning

    >=18.0

    >=18.0

    n/a

    Storage conversion   for member clusters

    >=18.0 and   <21.0

    n/a

    n/a

    Virtual Allocation   Metadata (VAM) enabled on non-sparse normal and high redundancy disk groups

    >=18.0

    n/a

    n/a

    Support for single   parity protection in Oracle ASM flex disk groups

    The   database COMPATIBLE initialization parameter must be set to   19.1 or greater.

    >=19.1

    n/a

    n/a

    Support for double   parity protection in Oracle ASM flex disk groups

    The   database COMPATIBLE initialization parameter must be set to   19.5 or greater.

    >=19.5

    n/a

    n/a

    来自<https://docs.oracle.com/en/database/oracle/oracle-database/26/ostmg/diskgroup-compatibility.html#GUID-9E283CAC-8359-4B4D-A9DE-3A9C52BCDD47


    本文为原创技术文章,如需转载请注明出处,谢谢。



    文章转载自Acdante,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

    评论