为啥 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_statistics
和user_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 中使用下划线执行全表扫描,而使用 % 则利用索引?