oracle直方图测试

1.测试环境准备

bash 复制代码
CREATE USER hist_test IDENTIFIED BY hist_test;
GRANT CONNECT, RESOURCE TO hist_test;
GRANT SELECT_CATALOG_ROLE TO hist_test;
GRANT ANALYZE ANY TO hist_test;
GRANT EXECUTE ON DBMS_STATS TO hist_test;
ALTER USER hist_test QUOTA UNLIMITED ON USERS;

2.构造一个状态列严重倾斜的表,status = 1 占绝大多数,其余值各少量。

bash 复制代码
CREATE TABLE t_hist_test (
  id     NUMBER,
  status NUMBER,
  info   VARCHAR2(100)
);

-- 插入倾斜数据:status=1 有 100000 行,status=2..100 各 10 行
BEGIN
  FOR i IN 1..100000 LOOP
    INSERT INTO t_hist_test VALUES(i, 1, 'bulk_' || i);
  END LOOP;
  FOR j IN 2..100 LOOP
    FOR k IN 1..10 LOOP
      INSERT INTO t_hist_test VALUES(100000 + j*10 + k, j, 'rare_' || j);
    END LOOP;
  END LOOP;
  COMMIT;
END;
/

CREATE INDEX idx_hist_status ON t_hist_test(status);

3.步骤一:无直方图时的基线行为

bash 复制代码
先用 ANALYZE(仅测试用,生产禁用)或显式指定 SIZE 1 收集统计信息,确保 status 列没有直方图。

sql
-- 清除可能存在的直方图并重新收集(SIZE 1 表示不建直方图)
EXEC DBMS_STATS.GATHER_TABLE_STATS('HIST_TEST', 'T_HIST_TEST', method_opt => 'FOR COLUMNS SIZE 1 STATUS');
PL/SQL procedure successfully completed.
bash 复制代码
-- 确认直方图状态为 NONE
SELECT column_name, histogram, num_buckets 
FROM   dba_tab_col_statistics 
WHERE  owner='HIST_TEST' AND table_name='T_HIST_TEST' AND column_name='STATUS';
COLUMN_NAME
--------------------------------------------------------------------------------
HISTOGRAM                      NUM_BUCKETS
------------------------------ -----------
STATUS
NONE                                     1
bash 复制代码
执行查询并观察计划:

sql
-- 查询倾斜值(大量数据)
EXPLAIN PLAN FOR SELECT * FROM t_hist_test WHERE status = 1;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 100264795

-------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name            | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                 |  1010 | 19190 |     6   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| T_HIST_TEST     |  1010 | 19190 |     6   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | IDX_HIST_STATUS |  1010 |       |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------

   2 - access("STATUS"=1)

14 rows selected.


-- 查询稀有值(少量数据)
EXPLAIN PLAN FOR SELECT * FROM t_hist_test WHERE status = 50;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 100264795

-------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name            | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                 |  1010 | 19190 |     6   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| T_HIST_TEST     |  1010 | 19190 |     6   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | IDX_HIST_STATUS |  1010 |       |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------

   2 - access("STATUS"=50)

14 rows selected.

预期:没有直方图时,优化器假设 status 分布均匀(选择性 = 1/NDV ≈ 1/100),两个查询的预估行数(E-Rows)会非常接近(约 1000 行),很可能对两个查询都选择索引扫描,或者都选择全表扫描,无法区分数据量的巨大差异。

4.步骤二:生成频率直方图(Frequency)

bash 复制代码
由于 status 的 NDV(不同值数量)约为 100,小于默认桶数 254,Oracle 应自动生成频率直方图。

sql
-- 强制对 status 收集直方图(默认 SIZE AUTO 在列被查询后也会自动收集)

SQL> EXEC DBMS_STATS.GATHER_TABLE_STATS ('HIST_TEST', 'T_HIST_TEST', method_opt => 'FOR COLUMNS SIZE AUTO STATUS');

PL/SQL procedure successfully completed.


-- 验证直方图类型
SELECT
  column_name,
  histogram,
  num_buckets
FROM
  dba_tab_col_statistics
WHERE
  owner = 'HIST_TEST'
  AND table_name = 'T_HIST_TEST'
  AND column_name = 'STATUS';
  
 COLUMN_NAME          HISTOGRAM                      NUM_BUCKETS
-------------------- ------------------------------ -----------
STATUS               FREQUENCY                              100

-- 预期 HISTOGRAM = FREQUENCY,NUM_BUCKETS ≈ 100
查看直方图桶详情:

sql
SELECT
  endpoint_number,
  endpoint_value
FROM
  dba_tab_histograms
WHERE
  owner = 'HIST_TEST'
  AND table_name = 'T_HIST_TEST'
  AND column_name = 'STATUS'
ORDER BY
  endpoint_number;
在频率直方图中,endpoint_number 是累积频率。如果 status=1 的桶的 endpoint_number 远大于前一个桶,说明优化器能识别出它是"Popular Value"(高频值)。
ENDPOINT_NUMBER ENDPOINT_VALUE
--------------- --------------
         100000              1
         100010              2
         100020              3
         100030              4
         100040              5
         100050              6
         100060              7
         100070              8
         100080              9
         100090             10
         100100             11

ENDPOINT_NUMBER ENDPOINT_VALUE
--------------- --------------
         100110             12
         100120             13
         100130             14
         100140             15
         100150             16
         100160             17
         100170             18
         100180             19
         100190             20
         100200             21
         100210             22

ENDPOINT_NUMBER ENDPOINT_VALUE
--------------- --------------
         100220             23
         100230             24
         100240             25
         100250             26
         100260             27
         100270             28
         100280             29
         100290             30
         100300             31
         100310             32
         100320             33

ENDPOINT_NUMBER ENDPOINT_VALUE
--------------- --------------
         100330             34
         100340             35
         100350             36
         100360             37
         100370             38
         100380             39
         100390             40
         100400             41
         100410             42
         100420             43
         100430             44

ENDPOINT_NUMBER ENDPOINT_VALUE
--------------- --------------
         100440             45
         100450             46
         100460             47
         100470             48
         100480             49
         100490             50
         100500             51
         100510             52
         100520             53
         100530             54
         100540             55

ENDPOINT_NUMBER ENDPOINT_VALUE
--------------- --------------
         100550             56
         100560             57
         100570             58
         100580             59
         100590             60
         100600             61
         100610             62
         100620             63
         100630             64
         100640             65
         100650             66

ENDPOINT_NUMBER ENDPOINT_VALUE
--------------- --------------
         100660             67
         100670             68
         100680             69
         100690             70
         100700             71
         100710             72
         100720             73
         100730             74
         100740             75
         100750             76
         100760             77

ENDPOINT_NUMBER ENDPOINT_VALUE
--------------- --------------
         100770             78
         100780             79
         100790             80
         100800             81
         100810             82
         100820             83
         100830             84
         100840             85
         100850             86
         100860             87
         100870             88

ENDPOINT_NUMBER ENDPOINT_VALUE
--------------- --------------
         100880             89
         100890             90
         100900             91
         100910             92
         100920             93
         100930             94
         100940             95
         100950             96
         100960             97
         100970             98
         100980             99

ENDPOINT_NUMBER ENDPOINT_VALUE
--------------- --------------
         100990            100

5.步骤三:对比执行计划变化(核心验证)

bash 复制代码
再次执行步骤一中的两个查询,观察 E-Rows(预估行数)和实际执行计划。

sql
EXPLAIN PLAN FOR
SELECT
  *
FROM
  t_hist_test
WHERE
  status = 1;

SELECT
  *
FROM
  TABLE (DBMS_XPLAN.DISPLAY);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 1532874825

---------------------------------------------------------------------------------
| Id  | Operation         | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |             |   100K|  1855K|   103   (1)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T_HIST_TEST |   100K|  1855K|   103   (1)| 00:00:01 |
---------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

   1 - filter("STATUS"=1)

13 rows selected.


EXPLAIN PLAN FOR SELECT * FROM t_hist_test WHERE status = 50;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 100264795

-------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name            | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                 |    10 |   190 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| T_HIST_TEST     |    10 |   190 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | IDX_HIST_STATUS |    10 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------

   2 - access("STATUS"=50)


预期:有了频率直方图后:

status = 1:优化器预估行数接近 100000,可能选择全表扫描(因为返回大量数据,索引成本高)。

status = 50:优化器预估行数接近 10,很可能选择索引扫描。

如果两次查询的计划确实不同,且符合数据分布,说明直方图生效。
复制代码
**7.注意事项**

```bash
1.SIZE AUTO 的触发条件:Oracle 默认只在列被查询过之后,才会在下次统计信息收集时为该列生成直方图。如果直接收集统计信息但从未查询过该表,可能不会生成直方图。

2.ANALYZE 不生成直方图:测试中若使用 ANALYZE TABLE ... COMPUTE STATISTICS,不会产生直方图,必须用 DBMS_STATS。

3.绑定变量的干扰:如果测试 SQL 使用绑定变量,直方图的效果依赖于绑定变量窥探。对于分布差异极大的值,可能需要观察多个 child cursor 的执行计划,而不是只看第一次硬解析的结果。

4.生产环境慎用 SIZE 254 强制:除非明确确认某列需要高精度直方图,否则优先使用 SIZE AUTO,避免统计信息收集开销和潜在的计划波动。
相关推荐
Henry-SAP1 小时前
SAP PP中的核心业务流程——离散制造、重复制造、流程制造三大模式解析
前端·数据库·人工智能·制造
Pniubi1 小时前
mysql 一张表可以有多少列
数据库·mysql
阡陌数智1 小时前
Extended Thinking 深度拆解:Claude Opus 5.5 推理范式、Token 计费陷阱与 Agent 工程落地
数据库
skywalk81632 小时前
不用 SSH、不装数据库:我用 1 个 Python 脚本 + Git,把 15 个 AI Agent 串成了一条流水线
数据库·人工智能·python·ssh·集群
Decay__2 小时前
19_MySQL 告警与自动化运维 —— 告警分级收敛 / Webhook / 定时巡检 / 脚本库
运维·数据库·sql·mysql·架构
代码不停2 小时前
Spring Aop
java·数据库·spring
keyipatience2 小时前
MySQL 学习(4)常用数据类型
数据库·mysql
达梦产品与服务2 小时前
SQLark V4.2 更新 | 新增TiDB、SSL安全连接、数据字典、清空/截断表等40+项功能已上线!
数据库·安全·tidb·ssl
码农阿豪3 小时前
SQL Server数据迁移完成后,凌晨的数据库告警该怎么查?
数据库·sqlserver