① 阶段一的计划确实是好计划、
② 阶段二它确实变成了坏计划、
③ 结果集大小始终不变(排除"数据变多所以本来就慢"的干扰)。
| 阶段 | 数据 | 优化器会选 | 实际耗时 |
|---|---|---|---|
| 阶段一 | 100 万行,C1=100 只有 10 行 | 索引扫描 + 回表 10 次 ✅ | 毫秒级 |
| 阶段二 | 再灌 400 万行,C1 全是 100(占比 80%) | 应该是全表扫描 ✅ | 用缓存旧计划 → 秒级/几十秒 ❌ |
执行以下SQL更新统计信息并清除缓存旧计划
DBMS_STATS.GATHER_TABLE_STATS(
OWNNAME => 'SYSDBA',
TABNAME => 'T_PLAN_DEMO',
ESTIMATE_PERCENT => 100,
BLOCK_SAMPLE => TRUE,
METHOD_OPT => 'FOR ALL COLUMNS SIZE AUTO',
DEGREE => 2,
GRANULARITY => 'ALL',
CASCADE => TRUE,
NO_INVALIDATE => FALSE
);
DROP TABLE IF EXISTS SYSDBA.T_PLAN_DEMO;
CREATE TABLE SYSDBA.T_PLAN_DEMO
(
ID INT,
C1 INT, -- 演示用的关键列:数据分布会由稀疏变极度倾斜
C2 INT,
PAD VARCHAR(200) -- 填充列:让表足够大,使回表代价明显
);
CREATE INDEX IDX_PLAN_DEMO_C1 ON SYSDBA.T_PLAN_DEMO (C1);
/* ================== 步骤 2:造「阶段一」数据 —— 100 万行,C1 高度稀疏 ==================
* C1 = 100 的行只有 10 行,其余各行 C1 值互不相同 → 选择性极佳
* ==================================================================================== */
DECLARE
v_rows CONSTANT INT := 200000;
v_base INT;
BEGIN
FOR i IN 0 .. 4 LOOP -- 5 批 × 20 万 = 100 万行
v_base := i * v_rows;
INSERT INTO SYSDBA.T_PLAN_DEMO (ID, C1, C2, PAD)
SELECT G,
CASE WHEN G <= 10 THEN 100 ELSE 1000000 + G END,
MOD(G * 7, 1000),
RPAD('A', 150, 'B')
FROM (SELECT v_base + LEVEL AS G FROM DUAL CONNECT BY LEVEL <= v_rows);
COMMIT;
END LOOP;
PRINT '阶段一数据完成:100 万行,其中 C1 = 100 的仅 10 行';
END;
/
SELECT C1, COUNT(*) AS CNT FROM SYSDBA.T_PLAN_DEMO WHERE C1 = 100 GROUP BY C1;
结果集截图如下
SELECT TABLE_NAME, NUM_ROWS, LAST_ANALYZED FROM DBA_TABLES
WHERE OWNER = 'SYSDBA' AND TABLE_NAME = 'T_PLAN_DEMO';
截图如下:
[dmdba@localhost.localdomain:/dm62/bin]$ ./disql SYSDBA/SYSDBA:5237
服务器[LOCALHOST:5237]:处于主库打开状态
登录使用时间 : 2.500(ms)
密钥过期时间:2026-09-23
disql V8
15:16:46 SYSDBA@LOCALHOST:5237 SQL>
call sf_set_session_para_value('MONITOR_SQL_EXEC',1);
set autotrace trace
SELECT COUNT(PAD) FROM SYSDBA.T_PLAN_DEMO WHERE C1 = 100;
从上图可以看出SELECT COUNT(PAD) SQL执行耗时1ms, 从25000行中筛选出10行
阶段二数据生成:新增 400 万行(C1 全为 100),总计约 500 万行
DECLARE
v_rows CONSTANT INT := 200000;
v_base INT;
BEGIN
FOR i IN 0 .. 19 LOOP -- 20 批 × 20 万 = 400 万行
v_base := 1000000 + i * v_rows;
INSERT INTO SYSDBA.T_PLAN_DEMO (ID, C1, C2, PAD)
SELECT G, 100, MOD(G * 7, 1000), RPAD('A', 150, 'B')
FROM (SELECT v_base + LEVEL AS G FROM DUAL CONNECT BY LEVEL <= v_rows);
COMMIT;
END LOOP;
PRINT '阶段二数据完成:新增 400 万行(C1 全为 100),总计约 500 万行';
END;
/
SELECT COUNT(PAD) FROM SYSDBA.T_PLAN_DEMO WHERE C1 = 100;
从上图中可以看出,4000010的结果集走了回表,耗时8s,计划应该走全表扫描
不带参数 NO_INVALIDATE ,默认为true,保留SQL执行计划缓存
DBMS_STATS.GATHER_TABLE_STATS(
OWNNAME => 'SYSDBA',
TABNAME => 'T_PLAN_DEMO',
ESTIMATE_PERCENT => 100,
BLOCK_SAMPLE => TRUE,
METHOD_OPT => 'FOR ALL COLUMNS SIZE AUTO',
DEGREE => 2,
GRANULARITY => 'ALL',
CASCADE => TRUE
);
stat 100 on T_PLAN_DEMO(C1)
SELECT TABLE_NAME, NUM_ROWS, LAST_ANALYZED FROM DBA_TABLES
WHERE OWNER = 'SYSDBA' AND TABLE_NAME = 'T_PLAN_DEMO';
SELECT COUNT(PAD) FROM SYSDBA.T_PLAN_DEMO WHERE C1 = 100;
SELECT CACHE_ITEM, SQLSTR FROM V$CACHEPLN WHERE SQLSTR LIKE '%T_PLAN_DEMO%';
SP_CLEAR_PLAN_CACHE(281470045964424);
SP_CLEAR_PLAN_CACHE(281470045923464);
SP_CLEAR_PLAN_CACHE(281470045513864);
SP_CLEAR_PLAN_CACHE();
DBMS_STATS.GATHER_TABLE_STATS(
OWNNAME => 'SYSDBA',
TABNAME => 'T_PLAN_DEMO',
ESTIMATE_PERCENT => 100,
BLOCK_SAMPLE => TRUE,
METHOD_OPT => 'FOR ALL COLUMNS SIZE AUTO',
DEGREE => 2,
GRANULARITY => 'ALL',
CASCADE => TRUE,
NO_INVALIDATE => FALSE
);
SELECT COUNT(PAD) FROM SYSDBA.T_PLAN_DEMO WHERE C1 = 100;
执行耗时719ms,走全表扫描
文章
阅读量
获赞
