Oracle 为新创建的索引提供了错误的统计信息

问题描述 投票:0回答:1

我使用的是Oracle 19c 我有一个包含大约 50M 记录的表 我有以下疑问

 select /*+ gather_plan_statistics */  id  from MY_TABLE mytable WHERE
 mytable.STARTDATE <= to_date('20032024','DDMMYYYY') AND mytable.ENDDATE >= to_date('20032024','DDMMYYYY');

它给出了以下计划

SQL_ID  98f475hv01xwg, child number 0
-------------------------------------
select   /*+ gather_plan_statistics */           id from 
MY_TABLE mytable  WHERE  mytable.STARTDATE <= 
to_date('20032024','DDMMYYYY') AND mytable.ENDDATE >= 
to_date('20032024','DDMMYYYY')
 
Plan hash value: 1075412610
 
-------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                 | Name                  | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem |
-------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT          |                       |      1 |        |     50 |00:00:00.02 |      28 |     15 |       |       |          |
|*  1 |  TABLE ACCESS STORAGE FULL| MY_TABLE |      1 |     33M|     50 |00:00:00.02 |      28 |     15 |  1028K|  1028K| 3096K (0)|
-------------------------------------------------------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - storage(("MYTABLE"."ENDDATE">=TO_DATE(' 2024-03-20 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND "MYTABLE"."STARTDATE"<=TO_DATE(' 
              2024-03-20 00:00:00', 'syyyy-mm-dd hh24:mi:ss')))
       filter(("MYTABLE"."ENDDATE">=TO_DATE(' 2024-03-20 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND "MYTABLE"."STARTDATE"<=TO_DATE(' 
              2024-03-20 00:00:00', 'syyyy-mm-dd hh24:mi:ss')))

为什么预期行数 (33M) 比实际行数大得多?oracle 从哪里获取此统计数据?

我决定在 COLUMN ENDDATE 上创建索引:

create index MY_TABLE_TEST on MY_TABLE(ENDDATE);

我运行了相同的查询,但 Oracle 仍在使用全表访问! 我添加了一个提示来强制我的新索引:

 select  /*+ INDEX(mytable,MY_TABLE_TEST) */
      /*+ gather_plan_statistics */
 id  from MY_TABLE mytable
 
WHERE  mytable.STARTDATE <= to_date('20032024','DDMMYYYY')
AND mytable.ENDDATE >= to_date('20032024','DDMMYYYY');

我得到了以下计划:

SQL_ID  0knxk82yhkruw, child number 0
-------------------------------------
select /*+ INDEX(mytable,MY_TABLE_TEST) */  /*+ 
gather_plan_statistics */            id  from 
MY_TABLE mytable  WHERE  mytable.STARTDATE <= 
to_date('20032024','DDMMYYYY') AND mytable.ENDDATE >= 
to_date('20032024','DDMMYYYY')
 
Plan hash value: 2267489923
 
-----------------------------------------------------------------------------------
| Id  | Operation                           | Name                       | E-Rows |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                            |        |
|*  1 |  TABLE ACCESS BY INDEX ROWID BATCHED| MY_TABLE      |     33M|
|*  2 |   INDEX RANGE SCAN                  | MY_TABLE_TEST |     33M|
-----------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter("MYTABLE"."STARTDATE"<=TO_DATE(' 2024-03-20 00:00:00', 
              'syyyy-mm-dd hh24:mi:ss'))
   2 - access("MYTABLE"."ENDDATE">=TO_DATE(' 2024-03-20 00:00:00', 
              'syyyy-mm-dd hh24:mi:ss'))
 
Note
-----
   - Warning: basic plan statistics not available. These are only collected when:
       * hint 'gather_plan_statistics' is used for the statement or
       * parameter 'statistics_level' is set to 'ALL', at session or system level

我很惊讶:

  • 为什么我不再像第一个查询那样获得 A-Rows 列
  • 为什么我的索引范围扫描期望相同的 33M 记录,尽管实际上,它应该只返回几千条记录 - 这是我期望获得准确统计数据的新索引
oracle indexing query-optimization
1个回答
0
投票

Oracle 在整理其计划时,使用其已收集的统计数据来预测查询中每个行源的基数。您可以在

dba_tab_statistics
dba_tab_col_statistics
中查看这些统计数据。如果缺少这些,它还可以使用动态采样在解析时读取表的一部分,以推断丢失的统计信息。

您要求提供截至给定日期的所有当前行,这意味着必须考虑两个不同的日期列(开始和结束)之间的相互关系,而不是独立地考虑。这对于 Oracle 来说很难预测,因为它不会在任何正常的统计数据中存储两列之间的相对差异。它不知道您的大多数行是否活跃多年,或仅活跃几分钟。如果只有几分钟,它会预测一个很小的结果。如果持续多年,它将预测一个非常大的结果。它所知道的是,有大量列的开始日期早于 2024 年日期,大量行的结束日期晚于 2024 年日期,因此它只是将两个概率相乘并高估。这种事情经常发生。优化器并不完美,因为统计数据并不能告诉它一切。

您可以使用

cardinality
opt_estimate
提示来提示查询以纠正其假设,或者您可以使用列组上的扩展统计信息,看看是否有帮助:

未使用索引的原因是 Oracle 认为您将获得 3300 万行 - 相对于整个表而言,行数太多,无法证明索引访问效率低得多。如果您要更正其对行数的估计,并且它知道只有 50 行,那么它将使用索引。

select dbms_stats.create_extended_stats(null,'MY_TABLE','(STARTDATE,ENDDATE)') 
  from dual
© www.soinside.com 2019 - 2024. All rights reserved.