让我们尝试模拟一个与您类似的场景。
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。过滤器来自表访问,因为 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