索引列上的不同值
Posted
技术标签:
【中文标题】索引列上的不同值【英文标题】:Distinct values on indexed column 【发布时间】:2013-09-28 11:50:06 【问题描述】:我有一个有 1.15 亿行的表。其中一列已编入索引(在下面的解释计划中称为“my_index”的索引)并且不可为空。此外,到目前为止,此列只有一个不同的值。
当我这样做时
select distinct my_col from my_table;
,需要230秒,非常长。这是解释计划。
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
| 0 | SELECT STATEMENT | | 1 | 3 | 22064 (90)| 00:03:23 |
| 1 | SORT UNIQUE NOSORT| | 1 | 3 | 22064 (90)| 00:03:23 |
| 2 | INDEX FULL SCAN | my_index | 115M| 331M| 2363 (2)| 00:00:22 |
既然列只有一个不同的值,为什么要花这么长时间?为什么 Oracle 不只检查索引条目并快速发现该列只有一个可能值?在上面的解释计划中,索引扫描似乎需要 22 秒,但这个需要很长时间的“SORT UNIQUE NOSORT”是什么?
提前感谢您的帮助
【问题讨论】:
Orcale 版本:Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production 【参考方案1】:重新分析表格。
EXEC dbms_stats.gather_table_stats('owner','table_name',cascade=>true,method_opt=>'FOR ALL INDEXED COLUMNS SIZE ');
更改索引类型
1.15 亿行中有一个不同的值??!!这就是所谓的低基数,对于“正常”B-Tree 索引不太好考虑位图索引。 (如果你有 B-tree)
重构查询
如果您确定不会向此列添加新值,请删除 distinct 子句,而是按照 Abhijith 所说的那样使用。
【讨论】:
重建查询不是一个选项,因为当有 3 或 4 个不同的值时,此查询才有意义。但是,当不同的值很少时,使用位图绝对是一个好主意。我会尝试。但是,我仍然不明白这个“SORT UNIQUE NOSORT”阶段是什么,它需要超过 3 分钟才能对单个值进行排序(当然在我的理解中:),我想它还有很多其他的事情,但我不明白什么) 默认计划是对索引进行串行完整扫描,允许优化器绕过“排序顺序”,因为“排序唯一”。 +1 但总的来说FOR ALL INDEXED
is not recommended.
顺便说一下,在发布这个问题之前,统计数据是最新的【参考方案2】:
SORT UNIQUE NOSORT
不会花费太长时间。您正在查看来自错误执行计划的估计值,这可能是优化器参数不合理的结果。例如,将参数OPTIMIZER_INDEX_COST_ADJ 设置为1 而不是默认的100 可以产生类似的计划。您的查询运行缓慢很可能是因为您的数据库很忙或很慢。
发布的执行计划有什么问题?
发布的执行计划似乎不合理。检索数据应该比简单地丢弃重复数据花费更长的时间。消费者操作SORT UNIQUE NOSORT
可以与生产者操作INDEX FULL SCAN
几乎同时启动。通常他们应该几乎同时完成。问题中的执行计划显示了优化器估计。下面的活动报告屏幕截图显示了非常相似查询的实际时间线。所有步骤几乎同时开始和停止。
合理计划的示例设置
下面是一个非常相似的设置,但配置非常简单。读取的行数(1.15 亿)和返回的行数(1)相同,段大小几乎完全相同(329MB 对 331MB)。该计划显示几乎所有时间都花在INDEX FULL SCAN
上。
drop table test1 purge;
create table test1(a number not null, b number, c number) nologging;
begin
for i in 1 .. 115 loop
insert /*+ append */ into test1 select 1, level, level
from dual connect by level <= 1000000;
commit;
end loop;
end;
/
create index test1_idx on test1(a);
begin
dbms_stats.gather_table_stats(user, 'TEST1');
end;
/
explain plan for select /*+ index(test1) */ distinct a from test1;
select * from table(dbms_xplan.display);
Plan hash value: 77032494
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 3 | 244K (4)| 00:48:50 |
| 1 | SORT UNIQUE NOSORT| | 1 | 3 | 244K (4)| 00:48:50 |
| 2 | INDEX FULL SCAN | TEST1_IDX | 115M| 329M| 237K (1)| 00:47:30 |
--------------------------------------------------------------------------------
重新制定一个糟糕的计划
--Set optimizer_index_cost_adj to a ridiculously low value.
--This changes the INDEX FULL SCAN estimate from 47 minutes to 29 seconds.
alter session set optimizer_index_cost_adj = 1;
--Changing the CPUSPEEDNW to 800 will exactly re-create the time estimate
--for SORT UNIQUE NOSORT. This value is not ridiculous, and it is not
--something you should normally change. But it does imply your CPUs are
--slow. My 2+ year-old desktop had an original score of 1720.
begin
dbms_stats.set_system_stats( 'CPUSPEEDNW', 800);
end;
/
explain plan for select /*+ index(test1) */ distinct a from test1;
select * from table(dbms_xplan.display);
Plan hash value: 77032494
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 3 | 16842 (86)| 00:03:23 |
| 1 | SORT UNIQUE NOSORT| | 1 | 3 | 16842 (86)| 00:03:23 |
| 2 | INDEX FULL SCAN | TEST1_IDX | 115M| 329M| 2389 (2)| 00:00:29 |
--------------------------------------------------------------------------------
如何调查
检查参数。
select name, value from v$parameter where name like 'optimizer_index%'
NAME VALUE
---- -----
optimizer_index_cost_adj 1
optimizer_index_caching 0
还要检查系统统计信息。
select * from sys.aux_stats$;
+---------------+------------+-------+------------------+
| SNAME | PNAME | PVAL1 | PVAL2 |
+---------------+------------+-------+------------------+
| SYSSTATS_INFO | STATUS | | COMPLETED |
| SYSSTATS_INFO | DSTART | | 09-23-2013 17:52 |
| SYSSTATS_INFO | DSTOP | | 09-23-2013 17:52 |
| SYSSTATS_INFO | FLAGS | 1 | |
| SYSSTATS_MAIN | CPUSPEEDNW | 800 | |
| SYSSTATS_MAIN | iosEEKTIM | 10 | |
| SYSSTATS_MAIN | IOTFRSPEED | 4096 | |
| SYSSTATS_MAIN | SREADTIM | | |
| SYSSTATS_MAIN | MREADTIM | | |
| SYSSTATS_MAIN | CPUSPEED | | |
| SYSSTATS_MAIN | MBRC | | |
| SYSSTATS_MAIN | MAXTHR | | |
| SYSSTATS_MAIN | SLAVETHR | | |
+---------------+------------+-------+------------------+
要找出真正花费时间的地方,请使用活动报告之类的工具。
select dbms_sqltune.report_sql_monitor(sql_id => '5s63uf4au6hcm',
type => 'active') from dual;
【讨论】:
真的很棒很棒的答案!非常感谢您提供如此准确和知识渊博的答案。明天将在工作中尝试您的建议并通知您。【参考方案3】:如果该列只有几个不同的值,请尝试压缩索引:
create index my_index on my_table (my_col) compress;
这将只存储该列的每个不同值一次,有望减少查询的执行时间。
作为奖励:使用它来查看用于查询的实际计划:
select /*+ gather_plan_statistics */ distinct my_col from my_table;
SELECT * FROM table(DBMS_XPLAN.DISPLAY_CURSOR);
gather_plan_statistics 提示将收集更多数据(执行将花费更长的时间),但没有它也可以工作。有关详细信息,请参阅 DBMS_XPLAN.DISPLAY_CURSOR 的文档。
【讨论】:
【参考方案4】:仔细查看解释计划。
-
它会扫描整个索引以了解您要获取的内容
然后应用 distinct 函数(尝试检索唯一值)。尽管您说只有一个唯一值,但它必须扫描整个索引才能获取值。 Oracle 不知道索引中只有一个不同的值。您可以限制 rownum = 1 以获得快速答案。
试试这个以获得快速答案
select my_col from my_table where rownum = 1;
在分布非常少的列上添加索引是非常不利的。这对表格和整个应用程序都是不利的。这没有任何意义
【讨论】:
是的,我同意在此列中只有一个值是没有意义的。我会说更多:拥有专栏根本没有意义。但是在不久的将来,此列将有 3 或 4 个其他不同的值。我只是想知道为什么阶段 1 需要这么多时间,而子阶段 2 给它的结果集只有一个值。 @Comencau - 计划中的 ID 1 没有给出只有一个值的结果集;步骤 ID 2 将返回索引中的每个值,步骤 ID 1 将其减少为单个值。您可以从rows
列看到 - ID 2 产生 115m 行,ID 1 对它们进行排序并产生 1 行。步骤 ID 0 不需要时间 - time
列是累积的。从索引中获取 115m 值需要 22 秒,对它们进行排序以删除重复项需要 3:01。 (可能还是简化了一点)。以上是关于索引列上的不同值的主要内容,如果未能解决你的问题,请参考以下文章