Skip to content

Oracle 数据库膨胀分析

数据库膨胀概述

数据库膨胀是指数据库对象(如表、索引、分区等)占用的存储空间超过了实际数据所需的空间,导致存储资源浪费和性能下降的现象。数据库膨胀主要分为表膨胀和索引膨胀两种类型,通常由以下原因引起:

  • 频繁的 DML 操作(插入、更新、删除)
  • 不恰当的表设计
  • 缺少定期维护
  • 低效的存储管理

数据库膨胀会带来以下问题:

  • 存储资源浪费
  • 查询性能下降
  • 备份和恢复时间延长
  • 索引维护成本增加
  • I/O 性能下降

表膨胀分析

表膨胀的原因

  1. 删除操作:当表中的数据被删除后,Oracle 不会立即释放空间,而是将这些空间标记为可用,导致表占用的空间大于实际数据量
  2. 更新操作:特别是对变长列的更新,可能导致行迁移或行链接
  3. 分区表:分区表中的历史分区可能包含大量已删除的数据
  4. LOB 列:LOB 列的存储管理不当可能导致空间浪费
  5. 回滚段:长时间运行的事务可能导致回滚段膨胀

表膨胀识别方法

使用 DBMS_SPACE 包分析表空间使用

sql
-- 分析表的空间使用情况
DECLARE
    v_seg_size NUMBER;
    v_used_size NUMBER;
    v_free_size NUMBER;
    v_free_blocks NUMBER;
BEGIN
    DBMS_SPACE.SPACE_USAGE(
        segment_owner => 'SCOTT',
        segment_name => 'EMP',
        segment_type => 'TABLE',
        total_blocks => v_seg_size,
        used_blocks => v_used_size,
        expired_blocks => NULL,
        unexpired_blocks => NULL
    );
    
    DBMS_OUTPUT.PUT_LINE('Total blocks: ' || v_seg_size);
    DBMS_OUTPUT.PUT_LINE('Used blocks: ' || v_used_size);
    DBMS_OUTPUT.PUT_LINE('Free blocks: ' || (v_seg_size - v_used_size));
    DBMS_OUTPUT.PUT_LINE('Bloat ratio: ' || ROUND((v_seg_size - v_used_size) / v_seg_size * 100, 2) || '%');
END;
/

查看表的实际数据量与占用空间

sql
-- 查看表的实际数据量与占用空间
SELECT
    t.table_name,
    t.num_rows,
    t.avg_row_len,
    ROUND((t.num_rows * t.avg_row_len) / 1024 / 1024, 2) AS estimated_data_mb,
    ROUND(s.bytes / 1024 / 1024, 2) AS actual_size_mb,
    ROUND((s.bytes - (t.num_rows * t.avg_row_len)) / s.bytes * 100, 2) AS bloat_pct
FROM
    dba_tables t
    JOIN dba_segments s ON t.owner = s.owner AND t.table_name = s.segment_name
WHERE
    t.owner = 'SCOTT'
    AND t.table_name = 'EMP';

分析表的行迁移和行链接

sql
-- 分析表的行迁移和行链接
ANALYZE TABLE SCOTT.EMP LIST CHAINED ROWS;

SELECT
    COUNT(*) AS chained_rows_count
FROM
    CHAINED_ROWS
WHERE
    table_name = 'EMP' AND owner_name = 'SCOTT';

表膨胀处理方法

1. 重建表

sql
-- 方法 1:使用 CREATE TABLE AS SELECT
CREATE TABLE SCOTT.EMP_NEW AS SELECT * FROM SCOTT.EMP;
DROP TABLE SCOTT.EMP;
ALTER TABLE SCOTT.EMP_NEW RENAME TO EMP;

-- 方法 2:使用 ALTER TABLE MOVE
ALTER TABLE SCOTT.EMP MOVE;
-- 重建索引
ALTER INDEX SCOTT.PK_EMP REBUILD;

2. 收缩表(10g+)

sql
-- 启用行移动
ALTER TABLE SCOTT.EMP ENABLE ROW MOVEMENT;
-- 收缩表
ALTER TABLE SCOTT.EMP SHRINK SPACE;
-- 收缩表及索引
ALTER TABLE SCOTT.EMP SHRINK SPACE CASCADE;
-- 仅收缩表空间,不调整高水位线
ALTER TABLE SCOTT.EMP SHRINK SPACE COMPACT;

3. 分区表维护

sql
-- 重建分区
ALTER TABLE SCOTT.SALES MOVE PARTITION SALES_Q1_2023;
-- 收缩分区
ALTER TABLE SCOTT.SALES SHRINK SPACE PARTITION SALES_Q1_2023;
-- 合并分区
ALTER TABLE SCOTT.SALES MERGE PARTITIONS SALES_Q1_2023, SALES_Q2_2023 INTO PARTITION SALES_H1_2023;

4. LOB 列优化

sql
-- 分析 LOB 列空间使用
SELECT
    table_name,
    column_name,
    ROUND((bytes / 1024 / 1024), 2) AS lob_size_mb
FROM
    dba_lobs
WHERE
    owner = 'SCOTT' AND table_name = 'EMP';

-- 重建 LOB 段
ALTER TABLE SCOTT.EMP MOVE LOB(EMP_PHOTO) STORE AS (TABLESPACE USERS);

索引膨胀分析

索引膨胀的原因

  1. 频繁的 DML 操作:特别是删除和更新操作,会导致索引叶节点出现空洞
  2. 不恰当的索引设计:过多的索引或不合理的索引结构
  3. 低效的索引维护:缺少定期的索引重建或重组
  4. 序列生成器:使用递增序列生成索引键,可能导致索引右倾斜
  5. 并行 DML 操作:并行 DML 可能导致索引碎片

索引膨胀识别方法

查看索引的空间使用情况

sql
-- 查看索引的空间使用情况
SELECT
    i.index_name,
    i.table_name,
    i.leaf_blocks,
    i.distinct_keys,
    ROUND((i.leaf_blocks * 8192) / 1024 / 1024, 2) AS index_size_mb,
    ROUND((i.leaf_blocks * 8192) / i.distinct_keys, 2) AS bytes_per_key
FROM
    dba_indexes i
WHERE
    i.owner = 'SCOTT' AND i.table_name = 'EMP';

使用 DBMS_SPACE 包分析索引空间

sql
-- 分析索引的空间使用
DECLARE
    v_seg_size NUMBER;
    v_used_size NUMBER;
BEGIN
    DBMS_SPACE.SPACE_USAGE(
        segment_owner => 'SCOTT',
        segment_name => 'PK_EMP',
        segment_type => 'INDEX',
        total_blocks => v_seg_size,
        used_blocks => v_used_size
    );
    
    DBMS_OUTPUT.PUT_LINE('Index: PK_EMP');
    DBMS_OUTPUT.PUT_LINE('Total blocks: ' || v_seg_size);
    DBMS_OUTPUT.PUT_LINE('Used blocks: ' || v_used_size);
    DBMS_OUTPUT.PUT_LINE('Bloat ratio: ' || ROUND((v_seg_size - v_used_size) / v_seg_size * 100, 2) || '%');
END;
/

分析索引碎片

sql
-- 分析索引碎片
SELECT
    owner,
    index_name,
    blevel,
    leaf_blocks,
    distinct_keys,
    avg_leaf_blocks_per_key,
    avg_data_blocks_per_key,
    clustering_factor
FROM
    dba_indexes
WHERE
    owner = 'SCOTT' AND table_name = 'EMP';

索引膨胀处理方法

1. 重建索引

sql
-- 方法 1:使用 ALTER INDEX REBUILD
ALTER INDEX SCOTT.PK_EMP REBUILD;

-- 方法 2:使用 ALTER INDEX REBUILD ONLINE(允许并发 DML)
ALTER INDEX SCOTT.PK_EMP REBUILD ONLINE;

-- 方法 3:使用 ALTER INDEX COALESCE(合并叶节点,不重建索引结构)
ALTER INDEX SCOTT.PK_EMP COALESCE;

2. 分区索引维护

sql
-- 重建分区索引
ALTER INDEX SCOTT.IDX_SALES_DATE REBUILD PARTITION SALES_Q1_2023;

-- 合并分区索引
ALTER INDEX SCOTT.IDX_SALES_DATE COALESCE PARTITION SALES_Q1_2023;

3. 索引优化

  • 删除不必要的索引
  • 重新设计不合理的索引
  • 考虑使用复合索引替代多个单列索引
  • 对于频繁更新的列,考虑减少索引或使用位图索引(如果适合)

数据库膨胀监控

定期生成膨胀报告

创建表膨胀监控脚本

sql
-- 创建表膨胀监控表
CREATE TABLE dba_table_bloat_monitor (
    snapshot_time TIMESTAMP,
    owner VARCHAR2(30),
    table_name VARCHAR2(30),
    num_rows NUMBER,
    avg_row_len NUMBER,
    estimated_data_mb NUMBER,
    actual_size_mb NUMBER,
    bloat_pct NUMBER,
    chained_rows_count NUMBER
);

-- 创建表膨胀监控存储过程
CREATE OR REPLACE PROCEDURE monitor_table_bloat IS
BEGIN
    -- 清空临时表
    EXECUTE IMMEDIATE 'TRUNCATE TABLE CHAINED_ROWS';
    
    -- 分析所有表的行迁移和行链接
    FOR t IN (SELECT owner, table_name FROM dba_tables WHERE owner NOT IN ('SYS', 'SYSTEM', 'SYSMAN', 'DBSNMP')) LOOP
        BEGIN
            EXECUTE IMMEDIATE 'ANALYZE TABLE ' || t.owner || '.' || t.table_name || ' LIST CHAINED ROWS';
        EXCEPTION
            WHEN OTHERS THEN
                NULL;
        END;
    END LOOP;
    
    -- 插入监控数据
    INSERT INTO dba_table_bloat_monitor
    SELECT
        SYSTIMESTAMP,
        t.owner,
        t.table_name,
        t.num_rows,
        t.avg_row_len,
        ROUND((t.num_rows * t.avg_row_len) / 1024 / 1024, 2) AS estimated_data_mb,
        ROUND(s.bytes / 1024 / 1024, 2) AS actual_size_mb,
        ROUND((s.bytes - (t.num_rows * t.avg_row_len)) / s.bytes * 100, 2) AS bloat_pct,
        (SELECT COUNT(*) FROM CHAINED_ROWS cr WHERE cr.owner_name = t.owner AND cr.table_name = t.table_name) AS chained_rows_count
    FROM
        dba_tables t
        JOIN dba_segments s ON t.owner = s.owner AND t.table_name = s.segment_name
    WHERE
        t.owner NOT IN ('SYS', 'SYSTEM', 'SYSMAN', 'DBSNMP')
        AND t.num_rows > 0;
    
    COMMIT;
END;
/

创建索引膨胀监控脚本

sql
-- 创建索引膨胀监控表
CREATE TABLE dba_index_bloat_monitor (
    snapshot_time TIMESTAMP,
    owner VARCHAR2(30),
    index_name VARCHAR2(30),
    table_name VARCHAR2(30),
    leaf_blocks NUMBER,
    distinct_keys NUMBER,
    index_size_mb NUMBER,
    bytes_per_key NUMBER,
    blevel NUMBER,
    clustering_factor NUMBER
);

-- 创建索引膨胀监控存储过程
CREATE OR REPLACE PROCEDURE monitor_index_bloat IS
BEGIN
    -- 插入监控数据
    INSERT INTO dba_index_bloat_monitor
    SELECT
        SYSTIMESTAMP,
        i.owner,
        i.index_name,
        i.table_name,
        i.leaf_blocks,
        i.distinct_keys,
        ROUND((i.leaf_blocks * 8192) / 1024 / 1024, 2) AS index_size_mb,
        ROUND((i.leaf_blocks * 8192) / i.distinct_keys, 2) AS bytes_per_key,
        i.blevel,
        i.clustering_factor
    FROM
        dba_indexes i
    WHERE
        i.owner NOT IN ('SYS', 'SYSTEM', 'SYSMAN', 'DBSNMP')
        AND i.leaf_blocks > 0
        AND i.distinct_keys > 0;
    
    COMMIT;
END;
/

设置膨胀告警阈值

根据业务需求和系统特点,设置合理的膨胀告警阈值,例如:

  • 表膨胀率超过 50% 告警
  • 索引膨胀率超过 60% 告警
  • 行迁移和行链接数量超过 1000 告警

19c 和 21c 版本差异

数据库膨胀管理新特性

Oracle 19c 新特性

  • 自动索引优化:Oracle 19c 引入了自动索引功能,能够自动识别和优化索引,减少索引膨胀
  • 分区表增强:改进了分区表的维护功能,包括在线分区移动和合并
  • LOB 存储优化:改进了 LOB 列的存储管理,减少 LOB 段膨胀

Oracle 21c 新特性

  • 实时膨胀检测:Oracle 21c 提供了实时数据库膨胀检测功能,能够自动识别膨胀的表和索引
  • 自动表收缩:引入了自动表收缩功能,能够根据预设策略自动收缩膨胀的表
  • 智能索引重建:改进了索引重建算法,减少了索引重建的时间和资源消耗
  • 新视图 V$OBJECT_BLOAT:提供了实时的对象膨胀信息

新的膨胀分析工具

Oracle 19c 增强

  • DBMS_SPACE 包增强:改进了空间使用分析功能,提供了更详细的膨胀信息
  • AWR 报告增强:在 AWR 报告中添加了表和索引膨胀的统计信息

Oracle 21c 新增强

  • DBMS_SPACE.OBJECT_BLOAT_STATUS:新的存储过程,用于评估对象的膨胀状态
  • 自动诊断工作流:能够自动生成膨胀分析报告和修复建议

生产环境最佳实践

预防数据库膨胀

  1. 合理的表设计

    • 选择合适的数据类型,避免使用过大的数据类型
    • 合理设计表结构,避免过多的列
    • 对于频繁更新的表,考虑使用较小的块大小
  2. 优化 DML 操作

    • 批量处理 DML 操作,减少事务次数
    • 避免频繁的删除和更新操作
    • 对于大量删除操作,考虑使用 TRUNCATE 替代 DELETE
  3. 合理的索引设计

    • 只创建必要的索引
    • 合理设计复合索引,考虑列的顺序和选择性
    • 对于频繁更新的列,谨慎创建索引
  4. 定期收集统计信息

    • 定期收集表和索引的统计信息
    • 使用 DBMS_STATS 包自动收集统计信息
    • 确保统计信息的准确性,以便优化器生成正确的执行计划

定期维护计划

  1. 每周任务

    • 运行表和索引膨胀监控脚本
    • 分析膨胀报告,识别需要维护的对象
  2. 每月任务

    • 重建或收缩膨胀率超过 50% 的表
    • 重建或合并膨胀率超过 60% 的索引
    • 分析行迁移和行链接,采取相应措施
  3. 季度任务

    • 全面分析数据库膨胀情况
    • 调整维护策略和告警阈值
    • 优化表和索引设计

膨胀处理注意事项

  1. 维护窗口选择

    • 选择业务低峰期进行维护操作
    • 对于大型表和索引,考虑使用在线操作
    • 提前通知相关业务团队
  2. 备份策略

    • 在进行维护操作前,确保有有效的备份
    • 对于关键业务表,考虑进行测试环境验证
  3. 性能影响评估

    • 评估维护操作对系统性能的影响
    • 对于 RAC 环境,考虑节点负载均衡
    • 监控维护操作的进度和资源使用
  4. 索引重建顺序

    • 先重建主键索引
    • 然后重建唯一索引
    • 最后重建普通索引

常见问题 (FAQ)

如何区分正常的空间使用和数据库膨胀?

正常的空间使用是指数据和索引实际占用的空间,而数据库膨胀是指对象占用的空间超过了实际数据所需的空间。可以通过以下方法区分:

  • 比较表的实际大小与估算大小(基于行数和平均行长度)
  • 分析表的行迁移和行链接数量
  • 查看索引的叶节点使用率和键密度
  • 监控对象大小的增长趋势

什么时候需要重建表或索引?

通常在以下情况下需要重建表或索引:

  • 表膨胀率超过 50%
  • 索引膨胀率超过 60%
  • 行迁移和行链接数量较多
  • 查询性能明显下降
  • 备份和恢复时间过长

重建表和收缩表有什么区别?

  • 重建表:创建一个新表,将原表数据复制到新表,然后删除原表,重命名新表。此操作会释放表占用的空间,但需要重建索引。
  • 收缩表:通过移动行来消除表中的空洞,调整高水位线,释放未使用的空间。此操作不需要重建索引(使用 CASCADE 选项时会自动调整索引)。

如何减少索引膨胀?

减少索引膨胀的方法包括:

  • 优化 DML 操作,减少删除和更新操作
  • 定期重建或合并索引
  • 合理设计索引,避免过多的索引
  • 使用合适的索引类型,如位图索引或函数索引
  • 考虑使用分区索引,分散索引负载

19c 和 21c 在数据库膨胀管理方面有什么主要区别?

Oracle 21c 在数据库膨胀管理方面相比 19c 有以下主要增强:

  • 引入了实时膨胀检测功能
  • 提供了自动表收缩功能
  • 改进了索引重建算法
  • 新增了 V$OBJECT_BLOAT 视图,提供实时膨胀信息
  • 增强了 DBMS_SPACE 包,提供了更详细的膨胀分析功能
  • 引入了智能索引重建功能

如何监控数据库膨胀趋势?

监控数据库膨胀趋势的方法包括:

  • 定期运行膨胀监控脚本,收集历史数据
  • 分析 AWR 报告中的表和索引大小变化
  • 使用 OEM 或其他监控工具设置膨胀告警
  • 定期生成膨胀趋势报告,预测未来的膨胀情况

总结

数据库膨胀是 Oracle 数据库中常见的性能问题,会导致存储资源浪费和性能下降。DBA 需要定期监控和分析数据库膨胀情况,采取相应的处理措施,如重建表、收缩表、重建索引等。

预防数据库膨胀同样重要,包括合理的表设计、优化 DML 操作、合理的索引设计和定期收集统计信息。在生产环境中,DBA 应该建立完善的膨胀监控和维护计划,确保数据库系统高效、稳定地运行。

随着 Oracle 版本的升级,数据库膨胀管理功能不断增强,DBA 应充分利用这些新特性,提高数据库膨胀管理的效率和效果。通过合理的设计、优化和维护,可以有效减少数据库膨胀,提高数据库系统的性能和可靠性。