Skip to content

Oracle 统计信息:优化器的「眼睛」

你有没有遇到过这种情况:

同样的 SQL,昨天快今天慢。

明明加了索引,Oracle 就是不用。

表里只有 100 条数据,Oracle 却做了全表扫描。

这些问题的答案,都在统计信息里。


什么是统计信息?

统计信息是 Oracle 收集的关于表、索引、列的数据特征:

统计信息内容:
├── 表统计
│   ├── 行数(NUM_ROWS)
│   ├── 块数(BLOCKS)
│   └── 平均行长度(AVG_ROW_LEN)
├── 列统计
│   ├── 唯一值数量(NUM_DISTINCT)
│   ├── 空值数量(NULLS)
│   └── 数据分布(直方图)
└── 索引统计
    ├── 唯一值(NUM_DISTINCT)
    ├── 叶块数(LEAF_BLOCKS)
    └── 聚簇因子(CLUSTERING_FACTOR)

统计信息的作用

Oracle 优化器根据统计信息选择最优执行计划:

sql
-- 没有统计信息:优化器「瞎猜」
SELECT * FROM employees WHERE department_id = 50;
-- 优化器假设返回 1000 行
-- 可能选择全表扫描

-- 有统计信息:优化器「看数据」
SELECT * FROM employees WHERE department_id = 50;
-- 优化器知道只有 50 行
-- 可能选择索引扫描

查看统计信息

查看表统计

sql
-- 查看表统计信息
SELECT table_name, num_rows, blocks, avg_row_len, 
       sample_size, last_analyzed
FROM user_tables
WHERE table_name = 'EMPLOYEES';

查看列统计

sql
-- 查看列统计信息
SELECT column_name, num_distinct, num_nulls, 
       density, histogram, sample_size, last_analyzed
FROM user_tab_columns
WHERE table_name = 'EMPLOYEES'
ORDER BY column_id;

查看直方图

sql
-- 查看直方图信息
SELECT column_name, endpoint_number, endpoint_value
FROM user_tab_histograms
WHERE table_name = 'EMPLOYEES'
  AND column_name = 'DEPARTMENT_ID'
ORDER BY endpoint_number;

查看索引统计

sql
-- 查看索引统计信息
SELECT index_name, blevel, leaf_blocks, distinct_keys,
       avg_leaf_blocks_per_key, avg_data_blocks_per_key,
       clustering_factor, last_analyzed
FROM user_indexes
WHERE table_name = 'EMPLOYEES';

收集统计信息

DBMS_STATS 基本用法

sql
-- 收集表统计信息
BEGIN
    DBMS_STATS.GATHER_TABLE_STATS(
        ownname => USER,           -- 用户名
        tabname => 'EMPLOYEES',    -- 表名
        cascade => TRUE,           -- 同时收集索引统计
        estimate_percent => 10,    -- 采样比例
        method_opt => 'FOR ALL COLUMNS SIZE AUTO'  -- 直方图策略
    );
END;
/

-- 收集模式统计信息
BEGIN
    DBMS_STATS.GATHER_SCHEMA_STATS(
        ownname => USER,
        estimate_percent => 10,
        cascade => TRUE
    );
END;
/

-- 收集数据库统计信息
BEGIN
    DBMS_STATS.GATHER_DATABASE_STATS(
        estimate_percent => 10,
        cascade => TRUE
    );
END;
/

GATHER_INDEX_STATS

sql
-- 收集索引统计
BEGIN
    DBMS_STATS.GATHER_INDEX_STATS(
        ownname => USER,
        indname => 'IDX_EMP_DEPT',
        estimate_percent => 10
    );
END;
/

自动统计信息

查看自动统计任务

sql
-- 查看自动统计信息收集任务
SELECT task_name, status, enabled
FROM dba_autotask_client
WHERE client_name = 'auto optimizer stats collection';

-- 查看任务历史
SELECT client_name, window_name, window_start_time, 
       window_duration, tasks_success, tasks_failed
FROM dba_autotask_client_history
ORDER BY window_start_time DESC;

手动控制自动任务

sql
-- 禁用自动统计收集
BEGIN
    DBMS_AUTO_TASK_ADMIN.DISABLE(
        client_name => 'auto optimizer stats collection',
        operation => NULL,
        window_name => NULL
    );
END;
/

-- 启用自动统计收集
BEGIN
    DBMS_AUTO_TASK_ADMIN.ENABLE(
        client_name => 'auto optimizer stats collection',
        operation => NULL,
        window_name => NULL
    );
END;
/

直方图(Histogram)

什么是直方图?

直方图记录列值的数据分布,帮助优化器准确估算基数:

sql
-- 没有直方图:优化器假设均匀分布
-- 10000 行,10 个部门,平均每部门 1000 行
-- 实际:9 个部门各 10 行,1 个部门 9910 行
-- 优化器估算错误,选择全表扫描

-- 有直方图:优化器知道实际分布
CREATE INDEX idx_emp_dept ON employees(department_id);
-- Oracle 自动收集直方图(数据倾斜时)

直方图类型

类型说明适用场景
HEIGHT BALANCED等高直方图有限个不同值
FREQUENCY频率直方图值很少(<254)

直方图收集策略

sql
-- 自动(Oracle 推荐)
BEGIN
    DBMS_STATS.GATHER_TABLE_STATS(
        USER, 'EMPLOYEES',
        method_opt => 'FOR ALL COLUMNS SIZE AUTO'
    );
END;
/

-- 只收集有数据的列
BEGIN
    DBMS_STATS.GATHER_TABLE_STATS(
        USER, 'EMPLOYEES',
        method_opt => 'FOR COLUMNS SIZE AUTO department_id'
    );
END;
/

-- 手动指定桶数
BEGIN
    DBMS_STATS.GATHER_TABLE_STATS(
        USER, 'EMPLOYEES',
        method_opt => 'FOR COLUMNS SIZE 50 department_id'
    );
END;
/

统计信息管理

查看统计信息历史

sql
-- 查看统计信息历史
SELECT table_name, stattype_locked, stats_update_time
FROM user_tab_stats_history
WHERE table_name = 'EMPLOYEES';

-- 查看统计信息更新时间
SELECT table_name, last_analyzed
FROM user_tables
WHERE table_name = 'EMPLOYEES';

恢复统计信息

sql
-- 恢复到之前版本的统计信息
BEGIN
    DBMS_STATS.RESTORE_TABLE_STATS(
        ownname => USER,
        tabname => 'EMPLOYEES',
        as_of_timestamp => SYSDATE - 1  -- 恢复到一天前
    );
END;
/

-- 删除过时的统计信息
BEGIN
    DBMS_STATS.PURGE_STATS(
        before_timestamp => SYSDATE - 30  -- 删除 30 天前的统计信息
    );
END;
/

锁定统计信息

sql
-- 锁定表统计信息(防止自动收集覆盖)
BEGIN
    DBMS_STATS.LOCK_TABLE_STATS(
        ownname => USER,
        tabname => 'EMPLOYEES'
    );
END;
/

-- 解锁表统计信息
BEGIN
    DBMS_STATS.UNLOCK_TABLE_STATS(
        ownname => USER,
        tabname => 'EMPLOYEES'
    );
END;
/

统计信息问题排查

问题一:统计信息过期

sql
-- 查看数据量与统计信息对比
SELECT table_name, 
       num_rows "统计行数",
       (SELECT COUNT(*) FROM employees) "实际行数"
FROM user_tables
WHERE table_name = 'EMPLOYEES';

-- 如果差异超过 10%,需要重新收集统计信息

问题二:统计信息缺失

sql
-- 查看哪些表没有统计信息
SELECT table_name
FROM user_tables
WHERE num_rows IS NULL;

-- 收集统计信息
BEGIN
    DBMS_STATS.GATHER_TABLE_STATS(USER, 'EMPLOYEES');
END;
/

问题三:直方图缺失

sql
-- 查看哪些列需要直方图
SELECT column_name, num_distinct, histogram
FROM user_tab_columns
WHERE table_name = 'EMPLOYEES'
  AND num_distinct > 1
  AND histogram = 'NONE';

-- 收集直方图
BEGIN
    DBMS_STATS.GATHER_TABLE_STATS(
        USER, 'EMPLOYEES',
        method_opt => 'FOR ALL COLUMNS SIZE AUTO'
    );
END;
/

性能调优场景

场景:批量导入后

sql
-- 批量导入数据后,必须收集统计信息
INSERT /*+ APPEND */ INTO employees SELECT * FROM source_employees;
COMMIT;

-- 收集统计信息
BEGIN
    DBMS_STATS.GATHER_TABLE_STATS(USER, 'EMPLOYEES');
END;
/

场景:分区表统计

sql
-- 收集分区统计
BEGIN
    DBMS_STATS.GATHER_TABLE_STATS(
        ownname => USER,
        tabname => 'ORDERS',
        partname => 'P_2024_Q1',  -- 指定分区
        cascade => TRUE
    );
END;
/

-- 收集全局统计
BEGIN
    DBMS_STATS.GATHER_TABLE_STATS(
        ownname => USER,
        tabname => 'ORDERS',
        granularity => 'GLOBAL',
        cascade => TRUE
    );
END;
/

面试高频问题

Q1: 统计信息过期会导致什么问题?

优化器基于过期的统计信息选择执行计划,可能选择错误的执行计划(如应该用索引却用全表扫描),导致 SQL 性能下降。

Q2: 什么是直方图?什么时候需要?

直方图记录列值的数据分布,帮助优化器准确估算基数。当列值分布不均匀(倾斜)时,需要直方图。Oracle 11g+ 会自动决定是否收集直方图。

Q3: 如何处理统计信息导致的问题?

恢复之前的统计信息(DBMS_STATS.RESTORE_TABLE_STATS)、手动收集新的统计信息、锁定统计信息防止自动覆盖。


总结

统计信息是优化器决策的基础:

统计类型内容重要性
表统计行数、块数、平均行长度
列统计唯一值、空值数
直方图数据分布中(倾斜时高)
索引统计聚簇因子、叶块数

保持统计信息准确,是 SQL 性能稳定的前提。


下一步

基于 VitePress 构建