oracle 索引在数据库查询中的使用

oracle index usage on database queries

我得到了如下审核table。

create table "AUDIT_LOG" 
         (  
            "AUDIT_ID" NVARCHAR2(70), 
            "PAYMENT_IDENTIFICATION_ID" NVARCHAR2(70), 
            "ACCOUNT_NUMBER" NVARCHAR2(100)
             PRIMARY KEY ("AUDIT_ID")
         );

我的指数低于

  1. payment_idx on ("PAYMENT_IDENTIFICATION_ID")
  2. payment_id_idx on ("PAYMENT_IDENTIFICATION_ID", "AUDIT_ID")
  3. system_index on primary key AUDIT_ID

以下是我正在使用的查询

Query1 : 
Select *  FROM
    AUDIT_LOG
 WHERE
    PAYMENT_IDENTIFICATION_ID = 
    'ID124'
    AND 
    AUDIT_ID<>'ecfdc2c3-87eb-48c9-b53c'; 

Query2 :
Select *  FROM
    AUDIT_LOG
 WHERE
    PAYMENT_IDENTIFICATION_ID = 
    'ID124'
    AND 
    AUDIT_ID='ecfdc2c3-87eb-48c9-b53c'; 

第一个查询解释计划显示了索引 payment_id_idx 的用法,具有 BY INDEX ROWID BATCHED 选项。

然而,第二个查询解释计划显示 system_index 在主键 AUDIT_ID 上的用法,并带有 BY INDEX ROWID BATCHED 选项。

我认为在两个查询中都应该使用索引 payment_id_idx。 知道为什么第二个查询不使用复合索引 payment_id_idx。 非常感谢任何帮助。

让我们尝试模拟一个与您类似的场景。

SQL> alter session set current_schema=test ;

Session altered.

SQL> create table "AUDIT_LOG"
         (
            "AUDIT_ID" NVARCHAR2(70),
            "PAYMENT_IDENTIFICATION_ID" NVARCHAR2(70),
            "ACCOUNT_NUMBER" NVARCHAR2(100)
         );  2    3    4    5    6

Table created.

SQL> alter table audit_log add primary key ( audit_id ) ;

Table altered.

SQL> create index payment_idx on audit_log ("PAYMENT_IDENTIFICATION_ID");

Index created.

SQL> create index payment_id_idx on audit_log ("PAYMENT_IDENTIFICATION_ID", "AUDIT_ID");

Index created.

现在让我们插入一些演示数据,但要考虑以下几点:

  • AUDIT_ID 以 IDxxx 的形式是唯一的(其中 xxx 取值从 1 到 1M )

  • PAYMENT_IDENTIFICATION_ID 采用 LPAD 和字母形式的 10 个不同值。这里的想法是生成 10 个不同的值

  • ACCOUNT_NUMBER是lpad中一个字母一个字母的随机字符串,填满70个字符。

因此

declare 
begin 
for i in 1 .. 1000000 
loop 
    insert into audit_log values 
    ( 'ID'||i||'' , 
      case when i between 1 and 100000       then lpad('A',50,'A') 
           when i between 100001 and 200000  then lpad('B',50,'B') 
           when i between 200001 and 300000  then lpad('C',50,'C')
           when i between 300001 and 400000  then lpad('D',50,'D')
           when i between 400001 and 500000  then lpad('E',50,'E')
           when i between 500001 and 600000  then lpad('F',50,'F')
           when i between 600001 and 700000  then lpad('G',50,'G')
           when i between 700001 and 800000  then lpad('H',50,'H')
           when i between 800001 and 900000  then lpad('I',50,'I')
           when i between 900001 and 1000000 then lpad('J',50,'J')
      end ,
      lpad(dbms_random.string('U',1),70,'B')
    );  
end loop;
commit;
end;
/

第一次查询

SQL> set autotrace traceonly lines 220 pages 400
SQL> Select *  FROM
    AUDIT_LOG
 WHERE
    PAYMENT_IDENTIFICATION_ID = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    AND
    AUDIT_ID <> 'ID123482';   2    3    4    5    6

100000 rows selected.


Execution Plan
----------------------------------------------------------
Plan hash value: 272803615

---------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |             |   100K|    20M|  3767   (1)| 00:00:01 |
|*  1 |  TABLE ACCESS BY INDEX ROWID BATCHED| AUDIT_LOG   |   100K|    20M|  3767   (1)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | PAYMENT_IDX |   100K|       |  1255   (1)| 00:00:01 |
---------------------------------------------------------------------------------------------------

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

   1 - filter("AUDIT_ID"<>U'ID123482')
   2 - access("PAYMENT_IDENTIFICATION_ID"=U'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
              AAA')


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
      16982  consistent gets
       2630  physical reads
     134596  redo size
   12971296  bytes sent via SQL*Net to client
      73843  bytes received via SQL*Net from client
       6668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
     100000  rows processed

第二次查询

SQL> set autotrace traceonly lines 220 pages 400 
SQL> Select *  FROM
    AUDIT_LOG
 WHERE
    PAYMENT_IDENTIFICATION_ID = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'
    AND
    AUDIT_ID ='ID578520';   2    3    4    5    6


Execution Plan
----------------------------------------------------------
Plan hash value: 303326437

--------------------------------------------------------------------------------------------
| Id  | Operation                   | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |              |     1 |   219 |     3   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS BY INDEX ROWID| AUDIT_LOG    |     1 |   219 |     3   (0)| 00:00:01 |
|*  2 |   INDEX UNIQUE SCAN         | SYS_C0076603 |     1 |       |     2   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------

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

   1 - filter("PAYMENT_IDENTIFICATION_ID"=U'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
              FFFFFFFFFF')
   2 - access("AUDIT_ID"=U'ID578520')


Statistics
----------------------------------------------------------
          9  recursive calls
          6  db block gets
          9  consistent gets
          7  physical reads
       1080  redo size
        945  bytes sent via SQL*Net to client
        515  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

谓词信息为您提供了大量有关访问路径的信息:

在第一个查询中:

1 - filter("AUDIT_ID"<>U'ID123482')
2 - access("PAYMENT_IDENTIFICATION_ID"=U'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
              AAA')

访问由“=”运算符确定,在这种情况下,索引 PAYMENT_IDX 的范围扫描是最佳方法。过滤器是针对所有匹配访问条件的行而来的,从AUDIT_ID.

中的值过滤那些<>

在第二个查询中:

1 - filter("PAYMENT_IDENTIFICATION_ID"=U'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
              FFFFFFFFFF')
 2 - access("AUDIT_ID"=U'ID578520')

访问是通过主键索引进行的,因为您使用 = 作为运算符,所以没有比使用 PK 索引更好的查找行的方法了。这就是为什么你有一个 INDEX_UNIQUE_SCAN。过滤器来自 table 访问,因为 Oracle 已经从唯一主键索引中确定了行。实际上,该条件不是必需的,因为除非您查找 1 行或没有行。

与第一个查询一样,您从主键索引创建 <>,Oracle 将使用其他索引。假设(如示例中所示)您只有很少的不同值。请记住,如果使用 PK 索引,它将在第一步中检索 999999 行,然后应用过滤器,这比使用第二个索引效率低得多。

如果强制CBO使用PK指标,可以看到

SQL> Select /*+INDEX(a,SYS_C0076603) */ *  FROM
    AUDIT_LOG a
 WHERE
    PAYMENT_IDENTIFICATION_ID = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    AND
    AUDIT_ID <> 'ID123482';   2    3    4    5    6

100000 rows selected.


Execution Plan
----------------------------------------------------------
Plan hash value: 3265638686

----------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |              |   100K|    20M|   207K  (1)| 00:00:17 |
|*  1 |  TABLE ACCESS BY INDEX ROWID BATCHED| AUDIT_LOG    |   100K|    20M|   207K  (1)| 00:00:17 |
|*  2 |   INDEX FULL SCAN                   | SYS_C0076603 |   999K|       |  3212   (1)| 00:00:01 |
----------------------------------------------------------------------------------------------------

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

   1 - filter("PAYMENT_IDENTIFICATION_ID"=U'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
              AA')
   2 - filter("AUDIT_ID"<>U'ID123482')


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
     218238  consistent gets
      18520  physical reads
    1215368  redo size
   12964630  bytes sent via SQL*Net to client
      73873  bytes received via SQL*Net from client
       6668  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
     100000  rows processed