3

我有一个有 115 M 行的表。其中一列已编入索引(在下面的解释计划中称为“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”是什么?

预先感谢您的帮助

4

4 回答 4

3

重新分析表。

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 所说的那样使用。

于 2013-09-23T14:45:16.437 回答
3

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;
于 2013-09-23T23:09:58.347 回答
1

如果该列只有几个不同的值,请尝试压缩索引:

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 的文档。

于 2013-11-11T23:55:50.767 回答
0

仔细查看解释计划。

  1. 它扫描整个索引以了解您要获取的内容
  2. 然后应用 distinct 函数(尝试检索唯一值)。尽管您说只有一个唯一值,但它必须扫描整个索引才能获取值。Oracle 不知道索引中只有一个不同的值。您可以限制 rownum = 1 以获得快速答案。

试试这个以获得快速答案

select my_col from my_table where rownum = 1;

在分布非常少的列上添加索引是非常不利的。这对表格和整个应用程序都是不利的。这没有任何意义

于 2013-09-23T14:25:59.210 回答