Oracle 了解索引对其有显著影响的查询的特征

Oracle 了解索引对其有显著影响的查询的特征,oracle,indexing,rdbms,Oracle,Indexing,Rdbms,我试图给出一个例子,说明索引可以对查询执行时间产生显著的(数量级)影响。经过几个小时的反复试验,我仍处于第一步。也就是说,即使执行计划显示使用索引,速度也不会很大 因为我意识到我最好有一个大的表作为索引,以便发挥作用,所以我编写了以下脚本(使用Oracle 11g Express): 然后我尝试了许多查询,例如: select count(*) from many_students M where M.city = '5467 City'; 及 还有一些其他的 我看过这篇文章,认为我的问

我试图给出一个例子,说明索引可以对查询执行时间产生显著的(数量级)影响。经过几个小时的反复试验,我仍处于第一步。也就是说,即使执行计划显示使用索引,速度也不会很大

因为我意识到我最好有一个大的表作为索引,以便发挥作用,所以我编写了以下脚本(使用Oracle 11g Express):

然后我尝试了许多查询,例如:

select count(*) 
from many_students M 
where M.city = '5467 City'; 

还有一些其他的

我看过这篇文章,认为我的问题符合回复中的要求。然而,在建立索引后,我尝试的所有查询都没有显示出显著的改善:
createindex-myindex对许多大学学生(城市)进行测试


我是否缺少一些特征来区分一个索引对其有显著影响的查询?它是什么?

当数据库不需要访问表中的每一行来获取结果时,索引确实会发光。因此,
COUNT(*)
不是最好的例子。以此为例:

alter session set statistics_level = 'ALL';
create table mytable as select * from all_objects;
select * from mytable where owner = 'SYS' and object_name = 'DUAL';

---------------------------------------------------------------------------------------
| Id  | Operation         | Name    | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |         |      1 |        |    300 |00:00:00.01 |      12 |
|   1 |  TABLE ACCESS FULL| MYTABLE |      1 |  19721 |    300 |00:00:00.01 |      12 |
---------------------------------------------------------------------------------------
因此,在这里,数据库执行完整的表扫描(
table ACCESS full
),这意味着它必须访问数据库中的每一行,这意味着它必须从磁盘加载每个块。大量I/O。优化器猜测将找到15000行,但我知道只有一行

与此进行比较:

create index myindex on mytable( owner, object_name );
select * from mytable where owner = 'SYS' and object_name = 'JOB$';
select * from table( dbms_xplan.display_cursor( null, null, 'ALLSTATS LAST' ));

----------------------------------------------------------------------------------------------------------
| Id  | Operation                   | Name    | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |
----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |         |      1 |        |      1 |00:00:00.01 |       3 |      2 |
|   1 |  TABLE ACCESS BY INDEX ROWID| MYTABLE |      1 |      2 |      1 |00:00:00.01 |       3 |      2 |
|*  2 |   INDEX RANGE SCAN          | MYINDEX |      1 |      1 |      1 |00:00:00.01 |       2 |      2 |
----------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("OWNER"='SYS' AND "OBJECT_NAME"='JOB$')
这里,因为有一个索引,所以它执行
索引范围扫描
,以查找与我们的条件匹配的表的rowid。然后,它转到表本身(
table ACCESS BY INDEX ROWID
),只查找我们需要的行,因为它有一个ROWID,所以可以高效地查找这些行

更妙的是,如果您碰巧正在查找完全位于索引中的内容,则扫描甚至不必返回到基表。索引足够了:

select count(*) from mytable where owner = 'SYS';
select * from table( dbms_xplan.display_cursor( null, null, 'ALLSTATS LAST' ));

------------------------------------------------------------------------------------------------
| Id  | Operation         | Name    | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |
------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |         |      1 |        |      1 |00:00:00.01 |      46 |     46 |
|   1 |  SORT AGGREGATE   |         |      1 |      1 |      1 |00:00:00.01 |      46 |     46 |
|*  2 |   INDEX RANGE SCAN| MYINDEX |      1 |   8666 |   9294 |00:00:00.01 |      46 |     46 |
------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("OWNER"='SYS')

因为我的查询涉及所有者列,而所有者列包含在索引中,所以它永远不需要返回到基表来查找那里的任何内容。因此,索引扫描就足够了,然后它进行聚合以计算行数。这种情况有点不太完美,因为索引是开着的(owner,object_name),而不仅仅是owner,但它肯定比在主表上执行完整的表扫描要好。

当数据库不需要访问表中的每一行来获取结果时,索引真的会发光。因此,
COUNT(*)
不是最好的例子。以此为例:

alter session set statistics_level = 'ALL';
create table mytable as select * from all_objects;
select * from mytable where owner = 'SYS' and object_name = 'DUAL';

---------------------------------------------------------------------------------------
| Id  | Operation         | Name    | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |         |      1 |        |    300 |00:00:00.01 |      12 |
|   1 |  TABLE ACCESS FULL| MYTABLE |      1 |  19721 |    300 |00:00:00.01 |      12 |
---------------------------------------------------------------------------------------
因此,在这里,数据库执行完整的表扫描(
table ACCESS full
),这意味着它必须访问数据库中的每一行,这意味着它必须从磁盘加载每个块。大量I/O。优化器猜测将找到15000行,但我知道只有一行

与此进行比较:

create index myindex on mytable( owner, object_name );
select * from mytable where owner = 'SYS' and object_name = 'JOB$';
select * from table( dbms_xplan.display_cursor( null, null, 'ALLSTATS LAST' ));

----------------------------------------------------------------------------------------------------------
| Id  | Operation                   | Name    | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |
----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |         |      1 |        |      1 |00:00:00.01 |       3 |      2 |
|   1 |  TABLE ACCESS BY INDEX ROWID| MYTABLE |      1 |      2 |      1 |00:00:00.01 |       3 |      2 |
|*  2 |   INDEX RANGE SCAN          | MYINDEX |      1 |      1 |      1 |00:00:00.01 |       2 |      2 |
----------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("OWNER"='SYS' AND "OBJECT_NAME"='JOB$')
这里,因为有一个索引,所以它执行
索引范围扫描
,以查找与我们的条件匹配的表的rowid。然后,它转到表本身(
table ACCESS BY INDEX ROWID
),只查找我们需要的行,因为它有一个ROWID,所以可以高效地查找这些行

更妙的是,如果您碰巧正在查找完全位于索引中的内容,则扫描甚至不必返回到基表。索引足够了:

select count(*) from mytable where owner = 'SYS';
select * from table( dbms_xplan.display_cursor( null, null, 'ALLSTATS LAST' ));

------------------------------------------------------------------------------------------------
| Id  | Operation         | Name    | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |
------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |         |      1 |        |      1 |00:00:00.01 |      46 |     46 |
|   1 |  SORT AGGREGATE   |         |      1 |      1 |      1 |00:00:00.01 |      46 |     46 |
|*  2 |   INDEX RANGE SCAN| MYINDEX |      1 |   8666 |   9294 |00:00:00.01 |      46 |     46 |
------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("OWNER"='SYS')

因为我的查询涉及所有者列,而所有者列包含在索引中,所以它永远不需要返回到基表来查找那里的任何内容。因此,索引扫描就足够了,然后它进行聚合以计算行数。这个场景有点不完美,因为索引是开着的(owner,object_name),而不仅仅是owner,但它肯定比在主表上进行全表扫描要好。

测试用例是一个很好的开始,但它还需要一些东西来获得明显的性能差异:

  • 实际数据大小。两个小值的一百万行是一个小表。对于这样一个表,好的和坏的执行计划之间的性能差异可能没有多大关系

    下面的脚本将使表大小加倍,直到达到6400万行。在我的机器上大约需要20分钟。(为了加快速度,对于较大的尺寸,您可以创建表
    nologging
    ,并在插入中添加
    /*+append*/
    提示

    --Increase the table to 64 million rows.  This took 20 minutes on my machine.
    insert into many_students select * from many_students;
    insert into many_students select * from many_students;
    insert into many_students select * from many_students;
    insert into many_students select * from many_students;
    insert into many_students select * from many_students;
    insert into many_students select * from many_students;
    commit;
    
    --The table has about 1.375GB of data.  The actual size will vary.
    select bytes/1024/1024/1024 gb from dba_segments where segment_name = 'MANY_STUDENTS';
    
  • 收集统计信息。总是在大型表更改后收集统计信息。除非优化器具有表、列和索引统计信息,否则它无法很好地完成其工作

    begin
        dbms_stats.gather_table_stats(user, 'MANY_STUDENTS');
    end;
    /
    
  • 使用提示强制执行好计划和坏计划。通常应避免使用优化器提示。但要快速比较不同的计划,它们有助于修复坏计划

    例如,这将强制进行全表扫描:

    select /*+ full(M) */ count(*) from many_students M where M.city = '5467 City';
    
    但您还需要验证执行计划:

    explain plan for select /*+ full(M) */ count(*) from many_students M where M.city = '5467 City';
    select * from table(dbms_xplan.display);
    
  • 刷新缓存。缓存可能是索引和完整表扫描查询花费相同时间的罪魁祸首。如果表完全放在内存中,则读取所有行的时间可能几乎太少,无法测量。与解析查询或发送简单结果的时间相比,这个数字可能会相形见绌它通过网络传播

    此命令将强制Oracle从缓冲区缓存中删除几乎所有内容。这将帮助您测试“冷”系统。(您可能不希望在生产系统上运行此语句。)

    但是,这不会刷新操作系统或SAN缓存。而且可能该表真的适合生产环境中的内存。如果需要测试快速查询,可能需要将其放入PL/SQL循环中

  • 多次交替运行。在后台会发生很多事情,比如缓存和其他进程。由于系统上发生了一些不相关的更改,因此很容易得到不好的结果

    也许第一次运行需要额外的时间才能将内容放入缓存。或者
    --Seconds: 0.02, 0.02, 0.03, 0.234, 0.02
    alter system flush buffer_cache;
    select count(*) from many_students M where M.city = '5467 City';
    
    --Seconds: 4.07, 4.21, 4.35, 3.629, 3.54
    alter system flush buffer_cache;
    select /*+ full(M) */ count(*) from many_students M where M.city = '5467 City';