座右铭:糟糕的统计数据比没有统计数据更糟糕
TLDR您的统计数据已过时,需要重新收集。如果您创建索引,则会自动收集 index 统计信息,但不会收集与您的案例相关的表格统计信息。
让我们使用以下脚本模拟您的示例,以创建表格并用 1000 个精装本和 200 个平装本填充它。
create table CARTE
(cod int,
autor VARCHAR2(100),
titlu VARCHAR2(100),
pret NUMBER,
nume_editura VARCHAR2(100),
tip_coperta VARCHAR2(100)
);
insert into CARTE
(cod,autor,titlu,pret,nume_editura,tip_coperta)
select rownum,
'Author #'||rownum ,
'Book #'||rownum,
3.23,
'Penguin Random Number',
case when rownum <=1000 then 'hardcover'
else 'paperback' end
from dual connect by level <= 1200;
commit;
这使新表没有优化器对象统计信息,您可以使用以下仅返回 NULLs 的查询来验证它
select NUM_ROWS, LAST_ANALYZED from user_tables where table_name = 'CARTE';
那么,我们来看看Oracle对表的印象是什么:
EXPLAIN PLAN SET STATEMENT_ID = 'jara1' into plan_table FOR
select * from CARTE
where tip_coperta = 'paperback'
;
--
SELECT * FROM table(DBMS_XPLAN.DISPLAY('plan_table', 'jara1','ALL'));
上面的脚本为查询paberbacks 生成执行计划,您会看到Rows 很好(= 200)。这怎么可能?
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 200 | 46800 | 5 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| CARTE | 200 | 46800 | 5 (0)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("TIP_COPERTA"='paperback')
解释在计划输出的注释中-使用了动态采样。
基本上,Oracle 在解析查询时会执行一个附加查询来估计带有过滤谓词的行数。
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
动态采样对于很少使用的表来说很好,但是如果表是定期查询的,我们需要优化器统计以节省动态采样的开销。
所以让我们收集统计数据
exec dbms_stats.gather_table_stats(ownname=>user, tabname=>'CARTE' );
现在您看到统计信息已收集,总行数很好,并且
在列统计中创建了一个frequency 直方图 - 这对于估计具有特定值的记录数很重要!
select NUM_ROWS, LAST_ANALYZED from user_tables where table_name = 'CARTE';
NUM_ROWS LAST_ANALYZED
---------- -------------------
1200 09.01.2021 16:48:26
select NUM_DISTINCT,HISTOGRAM from user_tab_columns where table_name = 'CARTE' and column_name = 'TIP_COPERTA';
NUM_DISTINCT HISTOGRAM
------------ ---------------
2 FREQUENCY
让我们检查一下统计信息在执行计划中的工作情况
EXPLAIN PLAN SET STATEMENT_ID = 'jara1' into plan_table FOR
select * from CARTE
where tip_coperta = 'paperback'
;
--
SELECT * FROM table(DBMS_XPLAN.DISPLAY('plan_table', 'jara1','ALL'));
基本上我们看到同样正确的结果
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 200 | 12400 | 5 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| CARTE | 200 | 12400 | 5 (0)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("TIP_COPERTA"='paperback')
现在我们从表格中删除除了四个“平装本”之外的所有内容
delete from CARTE
where tip_coperta = 'paperback' and cod > 1004;
commit;
select count(*) from CARTE
where tip_coperta = 'paperback'
COUNT(*)
----------
4
通过此操作,统计数据过时,并根据过时的数据给出错误的结果。在重新收集统计信息之前,将出现此错误结果。
EXPLAIN PLAN SET STATEMENT_ID = 'jara1' into plan_table FOR
select * from CARTE
where tip_coperta = 'paperback'
;
--
SELECT * FROM table(DBMS_XPLAN.DISPLAY('plan_table', 'jara1','ALL'));
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 200 | 12400 | 5 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| CARTE | 200 | 12400 | 5 (0)| 00:00:01 |
---------------------------------------------------------------------------
制定一项政策,使您的统计信息保持最新!