我使用的是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
我很惊讶:
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