Oracle主键与索引NOT IN性能

时间:2013-06-14 09:04:24

标签: performance oracle indexing primary-key

我有以下用例:

表格存储来自人的变更数据和原始数据。我的查询旨在为每个人只获取一行:如果有更改的数据,则为原始数据。

我用100k行数据和2k更改数据填充了表格。在我的表上使用主键时,查询运行时间不到半秒。如果我在表上放置索引而不是主键,则查询运行速度非常慢。所以我会使用主键,毫无疑问。

我的问题是:为什么PK方法比带索引的方法快得多?

代码在这里:

drop table up_data cascade constraints purge;
/
create table up_data(
 pk integer,
 hp_nr integer,
 up_nr integer,
 ps_flag varchar2(1),
 ps_name varchar2(100)
 -- comment this out and uncomment the index below.
, constraint pk_up_data primary key (pk,up_nr) 
);
/
-- insert some data
insert into up_data
select rownum, 1, 0, 'A', 'tester_' || to_char(rownum) 
from dual 
connect by rownum < 100000;
/
-- insert some changed data
-- change ps_flag = 'B' and mark it with a change number in up_nr
insert into up_data
select rownum, 1, 1, 'B', 'tester_' || to_char(rownum) 
from dual 
connect by rownum < 2000;
/
-- alternative(?) to the primary key
-- CREATE INDEX idx_up_data ON up_data(pk, up_nr);
/

select语句如下所示:

select count(*)
from
(
  select *
  from up_data u1 
  where up_nr = 1 
  or (up_nr = 0 
      and pk not in (select pk from up_data where up_nr = 1)
      )
) u

该陈述可能是优化的目标,但目前它将保持这样。

1 个答案:

答案 0 :(得分:7)

创建主键约束时,Oracle还会创建一个索引以同时支持此约束。主键索引与基本索引有几个重要区别,即:

  • 此中的所有值都保证是唯一的
  • 表格行(构成PK的列)中没有空值

这些原因是您看到的性能差异的关键。使用您的设置,我得到以下查询计划:

--fast version with PK
explain plan for 
select count(*)
from
(
  select *
  from up_data u1 
  where up_nr = 1 
  or (up_nr = 0 
      and pk not in (select pk from up_data where up_nr = 1)
      )
) u
/
select * from table(dbms_xplan.display(NULL, NULL,'BASIC +ROWS'));

-----------------------------------------------------                                                                                                                                                                                                                                                        
| Id  | Operation              | Name       | Rows  |                                                                                                                                                                                                                                                        
-----------------------------------------------------                                                                                                                                                                                                                                                        
|   0 | SELECT STATEMENT       |            |     1 |                                                                                                                                                                                                                                                        
|   1 |  SORT AGGREGATE        |            |     1 |                                                                                                                                                                                                                                                        
|   2 |   FILTER               |            |       |                                                                                                                                                                                                                                                        
|   3 |    INDEX FAST FULL SCAN| PK_UP_DATA |   103K|                                                                                                                                                                                                                                                        
|   4 |    INDEX UNIQUE SCAN   | PK_UP_DATA |     1 |                                                                                                                                                                                                                                                        
-----------------------------------------------------     

alter table up_data drop constraint pk_up_data;
CREATE INDEX idx_up_data ON up_data(pk, up_nr);
/

--slow version with normal index
explain plan for
select count(*)
from
(
  select *
  from up_data u1 
  where up_nr = 1 
  or (up_nr = 0 
      and pk not in (select pk from up_data where up_nr = 1)
      )
) u
/

select * from table(dbms_xplan.display(NULL, NULL,'BASIC +ROWS'));

------------------------------------------------------                                                                                                                                                                                                                                                       
| Id  | Operation              | Name        | Rows  |                                                                                                                                                                                                                                                       
------------------------------------------------------                                                                                                                                                                                                                                                       
|   0 | SELECT STATEMENT       |             |     1 |                                                                                                                                                                                                                                                       
|   1 |  SORT AGGREGATE        |             |     1 |                                                                                                                                                                                                                                                       
|   2 |   FILTER               |             |       |                                                                                                                                                                                                                                                       
|   3 |    INDEX FAST FULL SCAN| IDX_UP_DATA |   103K|                                                                                                                                                                                                                                                       
|   4 |    INDEX FAST FULL SCAN| IDX_UP_DATA |  1870 |                                                                                                                                                                                                                                                       
------------------------------------------------------ 

最大的区别在于快速版本在表数据的第二次访问中使用了INDEX UNIQUE SCAN,而不是INDEX FAST FULL SCAN。

From the Oracle docs(强调我的):

  

与索引范围扫描相比,索引唯一扫描必须具有   与索引键关联的0或1 rowid。数据库   当谓词引用所有列时执行唯一扫描   在使用等于运算符的UNIQUE索引键中。索引唯一扫描   找到第一条记录后立即停止处理,因为没有   第二条记录是可能的。

此停止处理的优化证明是此示例中的重要因素。查询的快速版本:

  • 完全扫描~103,000个索引条目
  • 对于这些中的每一个,在PK索引中找到一个匹配的行,并进一步停止处理第二个索引

慢速版:

  • 完全扫描~103,000个索引条目
  • 对于其中的每一个执行103,000行的另一次扫描以查找是否存在任何匹配。

所以要比较完成的工作:

  • 使用PK,我们有一个快速全扫描,然后是103,000个一个索引值的查找
  • 使用正常索引,我们有一个快速全扫描然后103,000次扫描103,000个索引条目 - 几个数量级的工作量更多!

在此示例中,主键的唯一性和索引值的非空值都是获得性能优势所必需的:

-- create index as unique - we still get two fast full scans
drop index index idx_up_data;
create unique index idx_up_data ON up_data(pk, up_nr);

explain plan for 
select count(*)
from
(
  select *
  from up_data u1 
  where up_nr = 1 
  or (up_nr = 0 
      and pk not in (select pk from up_data where up_nr = 1)
      )
) u
/
select * from table(dbms_xplan.display(NULL, NULL,'BASIC +ROWS'));

------------------------------------------------------                                                                                                                                                                                                                                                       
| Id  | Operation              | Name        | Rows  |                                                                                                                                                                                                                                                       
------------------------------------------------------                                                                                                                                                                                                                                                       
|   0 | SELECT STATEMENT       |             |     1 |                                                                                                                                                                                                                                                       
|   1 |  SORT AGGREGATE        |             |     1 |                                                                                                                                                                                                                                                       
|   2 |   FILTER               |             |       |                                                                                                                                                                                                                                                       
|   3 |    INDEX FAST FULL SCAN| IDX_UP_DATA |   103K|                                                                                                                                                                                                                                                       
|   4 |    INDEX FAST FULL SCAN| IDX_UP_DATA |  1870 |                                                                                                                                                                                                                                                       
------------------------------------------------------     

-- now the columns are not null, we see the index unique scan
alter table up_data modify (pk not null, up_nr not null);

explain plan for
select count(*)
from
(
  select *
  from up_data u1 
  where up_nr = 1 
  or (up_nr = 0 
      and pk not in (select pk from up_data where up_nr = 1)
      )
) u
/

select * from table(dbms_xplan.display(NULL, NULL,'BASIC +ROWS'));

------------------------------------------------------                                                                                                                                                                                                                                                       
| Id  | Operation              | Name        | Rows  |                                                                                                                                                                                                                                                       
------------------------------------------------------                                                                                                                                                                                                                                                       
|   0 | SELECT STATEMENT       |             |     1 |                                                                                                                                                                                                                                                       
|   1 |  SORT AGGREGATE        |             |     1 |                                                                                                                                                                                                                                                       
|   2 |   FILTER               |             |       |                                                                                                                                                                                                                                                       
|   3 |    INDEX FAST FULL SCAN| IDX_UP_DATA |   103K|                                                                                                                                                                                                                                                       
|   4 |    INDEX UNIQUE SCAN   | IDX_UP_DATA |     1 |                                                                                                                                                                                                                                                       
------------------------------------------------------