近期在客户现场屡次遇到由于统计信息过旧导致执行计划选错引发的数据库性能问题,今天做个总结:
谓词越界常见发生在where谓词是时间字段的,总的来说统计信息记录的是一个过旧的时间,而SQL传入的时间是一个最新的时间范围(往往是<time time1<c<time2),由于统计信息不全,按照CBO计算出来的结果集就很小,在多表关联的情况下,CBO就会选择认为的最优的关联方式,而实际执行时发现不是那么回事,有大量结果集需要扫描,就会爆发SQL性能问题。
谓词越界就是select的谓词的条件不在统计信息low_value 和 high_value 之间,在实际选择结果集要大于CBO记录的结果集数量,即实际的selectivity偏大,这种情况下CBO评估出来的selectivity会出现严重的偏差,导致CBO选错执行计划。
测试验证
下面做一组测试,从执行计划cost看谓词越界的发生过程,先插入部分数据
DECLARE
i INT;
BEGIN
i := 78179;
WHILE(i < 100000)
LOOP
i := i + 1;
INSERT INTO test_obj(object_id) VALUES(i);
COMMIT;
END LOOP;
END;
/
查看此时的num_rows:
TEST@PROD1> select count(*) from test_obj;
COUNT(*)
----------
94283
TEST@PROD1> select max(object_ID),dump(max(object_id),16) from test_obj;
MAX(OBJECT_ID) DUMP(MAX(OBJECT_ID),16)
-------------- ----------------------------------------
100000 Typ=2 Len=2: c3,b
TEST@PROD1> select min(object_ID),dump(min(object_id),16) from test_obj;
MIN(OBJECT_ID ) DUMP(MIN(OBJECT_ID),16)
------------------------------ ----------------------------------------
2 Typ=2 Len=2: c1,3 --C103
不收集统计信息,此时统计列统计信息过旧,HIGH_VALUE依然是原来的值78179
TEST@PROD1> select low_value ,high_value,num_distinct,num_nulls from DBA_TAB_COL_STATISTICS where table_name='TEST_OBJ' and owner='TEST';
Distinct Number
LOW_VALUE HIGH_VALUE Values Nulls
------------------------------ ------------------------------ ------------ ----------
C103 C3085250 72,462(原值) 0
查询结果返回2081行结果集。
TEST@PROD1> select count(*) from test_obj where object_id between 78200 and 81000;
COUNT(*)
----------
2801
计算公式为:
selectivity=((VAL2 - VAL1) / (HIGH_VALUE - LOW_VALUE)+2 / NUM_DISTINCT) * null_adjust
null_adjust=(NUM_ROES - NUM_NULLS) / NUM_ROES
计算结果为:
TEST@PROD1> select round(((81000-78200)/(100000-2)+2/94283)*(94283-0)/94283*94283) from dual;
ROUND(((81000-78200)/(100000-2)+2/94283)*(94283-0)/94283*94283)
---------------------------------------------------------------
2642
查看结果集发现dictionary值为1,这明显是一个错误的执行计划,由于统计信息过旧,已经低于谓词条件区间(谓词过界)导致CBO低估了查询成本。
TEST@PROD1> select count(*) from test_obj where object_id between 78200 and 81000;
Execution Plan
----------------------------------------------------------
Plan hash value: 2217143630
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 5 | 289 (1)| 00:00:04 |
| 1 | SORT AGGREGATE | | 1 | 5 | | |
|* 2 | TABLE ACCESS FULL| TEST_OBJ | 1 | 5 | 289 (1)| 00:00:04 |
-------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("OBJECT_ID">=78200 AND "OBJECT_ID"<=81000)
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
1117 consistent gets
0 physical reads
0 redo size
423 bytes sent via SQL*Net to client
419 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
重新收集统计信息再次查看执行计划。
TEST@PROD1> exec dbms_stats.gather_table_stats('test','test_obj');
TEST@PROD1> select low_value ,high_value,num_distinct,num_nulls from DBA_TAB_COL_STATISTICS where table_name='TEST_OBJ' and owner='TEST';
Distinct Number
LOW_VALUE HIGH_VALUE Values Nulls
-------------------- -------------------- ------------ ----------
C103 C30B 94,283 0
此时统计信息HIGH_VALUE已经和最初计算的值相等,Typ=2 Len=2: c3,b。再次查看执行计划,此时CBO已经能够产生了正确的执行计划了。
执行计划为:
TEST@PROD1> select count(*) from test_obj where object_id between 78200 and 81000;
Execution Plan
----------------------------------------------------------
Plan hash value: 2217143630
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 5 | 314 (1)| 00:00:04 |
| 1 | SORT AGGREGATE | | 1 | 5 | | |
|* 2 | TABLE ACCESS FULL| TEST_OBJ | 2642 | 13210 | 314 (1)| 00:00:04 |
-------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("OBJECT_ID">=78200 AND "OBJECT_ID"<=81000)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
1117 consistent gets
0 physical reads
0 redo size
423 bytes sent via SQL*Net to client
419 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
谓词越界主要发生在大表,按照Oracle统计信息收集机制,表的数据变化量达到10%以上才会进行统计信息收集,大表不常收集统计信息就容易爆发谓词越界
预防方式:
可对关键表实行按谓词查询条件分区,即按天或者按月分区可规避此问题发生。
最后修改时间:2021-03-10 17:53:30
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。
评论
可以用cardinality hint干预一下
4年前

评论
我碰到的一些情况,即使俺查询条件分区也无法解决这个问题,大多表都是时间越新,数据越热,而如果表的数据量变化不到10%,无法收集统计信息,那么热的数据查询就是个问题。
4年前

2
相关阅读
【纯干货】Oracle 19C RU 19.27 发布,如何快速升级和安装?
Lucifer三思而后行
790次阅读
2025-04-18 14:18:38
Oracle RAC 一键安装翻车?手把手教你如何排错!
Lucifer三思而后行
664次阅读
2025-04-15 17:24:06
Oracle数据库一键巡检并生成HTML结果,免费脚本速来下载!
陈举超
593次阅读
2025-04-20 10:07:02
【ORACLE】你以为的真的是你以为的么?--ORA-38104: Columns referenced in the ON Clause cannot be updated
DarkAthena
549次阅读
2025-04-22 00:13:51
【活动】分享你的压箱底干货文档,三篇解锁进阶奖励!
墨天轮编辑部
534次阅读
2025-04-17 17:02:24
【ORACLE】记录一些ORACLE的merge into语句的BUG
DarkAthena
510次阅读
2025-04-22 00:20:37
一页概览:Oracle GoldenGate
甲骨文云技术
499次阅读
2025-04-30 12:17:56
火焰图--分析复杂SQL执行计划的利器
听见风的声音
472次阅读
2025-04-17 09:30:30
OR+DBLINK的关联SQL优化思路
布衣
383次阅读
2025-05-05 19:28:36
3月“墨力原创作者计划”获奖名单公布
墨天轮编辑部
382次阅读
2025-04-15 14:48:05
目录