为啥 Oracle 在应该使用索引时使用全表扫描?

Posted

技术标签:

【中文标题】为啥 Oracle 在应该使用索引时使用全表扫描?【英文标题】:Why is Oracle using full table scan when it should use an index?为什么 Oracle 在应该使用索引时使用全表扫描? 【发布时间】:2015-07-27 13:07:28 【问题描述】:

我正在对 Oracle 中的查询计划进行一些实验,我有下表:

--create a table to use
create table SKEWED_DATA(
  EMP_ID int,
  DEPT int,
  COL2 int,
  CONSTRAINT SKEWED_DATA_PK PRIMARY KEY (EMP_ID)
);
--add an index on dept
create index SKEWED_DATA_INDEX1 on SKEWED_DATA(DEPT);

然后我插入 100 万行数据,其中 999,999 行的部门 ID 为 1,1 行的部门 ID 为 99。

在计算表的统计信息之前,Oracle Autotrace 显示在运行以下查询时,它对两者都使用了索引扫描:

select AVG(COL2) from SKEWED_DATA D where DEPT = 1;
select AVG(COL2) from SKEWED_DATA D where DEPT = 99;

据我了解,在这种情况下,对部门 id 1 使用全表扫描,对部门 id 2 使用索引扫描会更有效。

然后我运行以下命令为表生成统计信息:

execute DBMS_STATS.GATHER_TABLE_STATS ('HARRY','SKEWED_DATA'); 

查询dba_tab_statisticsuser_tab_col_statistics 确认已收集统计数据和直方图。

现在对以下查询运行自动跟踪会显示对两者的全表扫描!

select AVG(COL2) from SKEWED_DATA D where DEPT = 1;
select AVG(COL2) from SKEWED_DATA D where DEPT = 99;

我的问题是:当只有 1 行具有此值时,为什么 Oracle 对部门 id 99 使用全表扫描?

更新

我尝试运行 dept 99 的查询并提示强制 Oracle 使用索引,虽然 Autotrace 认为它的效率较低,但它所花费的时间为 0.001 秒,而使用全表扫描时为 0.03 秒,从而证明(我认为?)我的理论,即 Oracle 应该在这种情况下使用索引。

select /*+ INDEX(D SKEWED_DATA_INDEX1) */ AVG(COL2) from SKEWED_DATA D where DEPT = 99;

【问题讨论】:

尝试DBMS_STATS.GATHER_TABLE_STATS ('HARRY','SKEWED_DATA', cascade => true ); - cascade option 收集有关此表的索引的统计信息。没有它,您可能有表的统计信息,但没有索引的统计信息。 @MT0 感谢您的建议,但这似乎没有什么不同。很奇怪…… 我没有看到您报告的内容; dept = 1 始终进行全表扫描(包括在收集统计信息之前),并且 dept = 99 始终进行索引范围扫描(统计之前和之后,有或没有级联)。我只能让两者都使用带有显式cascade => false 的索引范围扫描。这是在 11.2.0.3 中,所以可能是补丁级别差异/错误修复? 看起来 11.2.0.2 确实如你所说:before stats 和 after stats;和with cascade。 @AlexPoole 请查看更新后的问题 - 我认为我可能已经解决了... 【参考方案1】:

好的,我想我可能已经解决了。当我有 999,999 行的第 1 行和第 1 行的第 99 行时,我通过运行以下查询检查了直方图桶的数量:

select COLUMN_NAME, HISTOGRAM, NUM_BUCKETS, NUM_DISTINCT from USER_TAB_COL_STATISTICS where  TABLE_NAME = 'SKEWED_DATA';

这表明有 2 个不同的值,但只有 1 个桶。如果我将收集的统计信息更改为:

execute DBMS_STATS.GATHER_TABLE_STATS('HARRY','SKEWED_DATA',estimate_percent=>100);

然后它正确地提出了 2 个存储桶,并且自动跟踪显示了“正确”的执行计划。所以,我猜是因为我的数据极度“偏斜”,Oracle 无法为其生成正确的统计数据,除非estimate_percent 很大。

有趣的是,如果我的偏斜数据稍微少一些(例如,大约 2-3% 的部门 ID 为 99 的记录)即使我将估计百分比保留为默认值,Oracle 也会正确处理它。

因此,这个故事的寓意似乎是:如果您有这样可笑地扭曲的数据并且 Oracle 没有使用正确的执行计划,请尝试使用 estimate_percent 参数。

【讨论】:

以上是关于为啥 Oracle 在应该使用索引时使用全表扫描?的主要内容,如果未能解决你的问题,请参考以下文章

技术分享 为啥 SELECT 查询选择全表扫描,而不走索引?

为啥 Spanner 在 LIKE 中使用下划线执行全表扫描,而使用 % 则利用索引?

Oracle数据访问和索引的使用

oracle在组合索引上,只使用部分列进行查询(查询时必须包含前导列,否则会走全表扫描)

Oracle解释计划解析——Oracle做全表访问

警惕 Oracle 索引优化时陷阱之无效的索引范围扫描(INDEX RANGE SCAN)导致的全表扫描