索引列上的不同值

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。 (可能还是简化了一点)。

以上是关于索引列上的不同值的主要内容,如果未能解决你的问题,请参考以下文章

优化大量列上的不同值

SQL - 计算不同列上的重复次数

具有重复值的列上的数据库索引

具有不同排序方向的多列上的Sql server聚集索引

组合唯一约束

熊猫合并:合并同一列上的两个数据框,但保留不同的列