首先我們要知道的一點就是CBO的代碼oracle是不會對我們公開的,起碼現在是。所以本文中的結論不一定適用所有的版本。在應用本文的結論之前最好先試一下。
ok 下面就是本文的結論,當你在where語句中使用不等於或者not in時候,oracle 傾向於忽略索引。 比如:
SQL> Select * from test where text<>'star'; ID TEXT ---------- ------------ 4939426 sun
這條語句即使在test上有索引,oracle也仍然會忽略。
接下來我們分析證明一下這是為什么。 其實,我認為oracle這么做是有道理的。一般我們在寫SQL的時候,如果用了 <>,也就是不等於,通常都是說選取結果集中的很大一部分。我們可以感受一下平時我們的思維方式和和習慣確實是這樣的。比如我們說要"找出這些人中不是姓李的","找出這些車中不是大眾的"。這一般來說是要返回結果集中很大一部分的。Oracle認為如果是這樣,那么用索引不如全表掃描迅速,所以這種情況根本就不考慮索引,直接采用全表掃描。 而且oracle認為,如果你知道你的<>會返回少量的結果,那么你應該會調整你的SQL 用 (< or >)來代替。
下面我們驗證一下。
首先創建表。一個很大的表。
SQL> select * from v$version; BANNER -------------------------------------------------------------------------------- Oracle Database 10g Enterprise Edition Release 10.2.0.5.0 - 64bi PL/SQL Release 10.2.0.5.0 - Production CORE 10.2.0.5.0 Production TNS for Solaris: Version 10.2.0.5.0 - Production NLSRTL Version 10.2.0.5.0 - Production SQL> create table test as select rownum id, 'star' text from dba_objects,v$session; Table created.
驗證一下這個表是不是很大,然后插入一條數據。
SQL> select count(*) from test; COUNT(*) ---------- 4939425 SQL> insert into test values (4939426,'sun'); 1 row created. SQL> commit; Commit complete.
創建索引,並收集統計信息。
SQL> create index test_i on test(text); Index created. SQL> exec dbms_stats.gather_table_stats(ownname=> 'SYS', tabname=> 'TEST' ,cascade=> true); PL/SQL procedure successfully completed.
進行10053trace
SQL> alter session set tracefile_identifier='haha'; Session altered. SQL> ALTER SESSION SET EVENTS='10053 trace name context forever, level 1'; Session altered. SQL> Select * from test where text<>'star'; ID TEXT ---------- ------------ 4939426 sun SQL> ALTER SESSION SET EVENTS '10053 trace name context off'; Session altered.
現在我們看一下10053的結果。
*************************************** BASE STATISTICAL INFORMATION *********************** Table Stats:: Table: TEST Alias: TEST #Rows: 4944655 #Blks: 10780 AvgRowLen: 10.00 Index Stats:: Index: TEST_I Col#: 2 LVLS: 2 #LB: 10468 #DK: 1 LB/K: 10468.00 DB/K: 19790.00 CLUF: 19790.00 *************************************** SINGLE TABLE ACCESS PATH ----------------------------------------- BEGIN Single Table Cardinality Estimation ----------------------------------------- Column (#2): TEXT(CHARACTER) AvgLen: 5.00 NDV: 1 Nulls: 0 Density: 1 Table: TEST Alias: TEST Card: Original: 4944655 Rounded: 1 Computed: 1.00 Non Adjusted: 1.00 ----------------------------------------- END Single Table Cardinality Estimation ----------------------------------------- Access Path: TableScan Cost: 3098.02 Resp: 3098.02 Degree: 0 Cost_io: 2360.00 Cost_cpu: 2153524223 Resp_io: 2360.00 Resp_cpu: 2153524223 Best:: AccessPath: TableScan Cost: 3098.02 Degree: 1 Resp: 3098.02 Card: 1.00 Bytes: 0 *************************************** OPTIMIZER STATISTICS AND COMPUTATIONS *************************************** GENERAL PLANS *************************************** Considering cardinality-based initial join order. Permutations for Starting Table :0 *********************** Join order[1]: TEST[TEST]#0 *********************** Best so far: Table#: 0 cost: 3098.0217 card: 1.0000 bytes: 10 (newjo-stop-1) k:0, spcnt:0, perm:1, maxperm:80000 ********************************* Number of join permutations tried: 1 ********************************* Final - All Rows Plan: Best join order: 1 Cost: 3098.0217 Degree: 1 Card: 1.0000 Bytes: 10 Resc: 3098.0217 Resc_io: 2360.0000 Resc_cpu: 2153524223 Resp: 3098.0217 Resp_io: 2360.0000 Resc_cpu: 2153524223 kkoipt: Query block SEL$1 (#0) ******* UNPARSED QUERY IS ******* SELECT "TEST"."ID" "ID","TEST"."TEXT" "TEXT" FROM "SYS"."TEST" "TEST" WHERE "TEST"."TEXT"<>'star' kkoqbc-subheap (delete addr=ffffffff7b11c008, in-use=11712, alloc=26392) kkoqbc-end : call(in-use=15256, alloc=49184), compile(in-use=37792, alloc=40520) apadrv-end: call(in-use=15256, alloc=49184), compile(in-use=38608, alloc=40520) sql_id=192f8vs3fqvpc. Current SQL statement for this session: Select * from test where text<>'star' ============ Plan Table ============ -------------------------------------+-----------------------------------+ | Id | Operation | Name | Rows | Bytes | Cost | Time | -------------------------------------+-----------------------------------+ | 0 | SELECT STATEMENT | | | | 3098 | | | 1 | TABLE ACCESS FULL | TEST | 1 | 10 | 3098 | 00:00:38 | -------------------------------------+-----------------------------------+ Predicate Information: ---------------------- 1 - filter("TEXT"<>'star')
注意access pass 這里
Access Path: TableScan Cost: 3098.02 Resp: 3098.02 Degree: 0 Cost_io: 2360.00 Cost_cpu: 2153524223 Resp_io: 2360.00 Resp_cpu: 2153524223 Best:: AccessPath: TableScan Cost: 3098.02 Degree: 1 Resp: 3098.02 Card: 1.00 Bytes: 0
根本就沒有去calculate index的開銷。 所以執行計划就是全表掃描。
之前也懷疑過<>這種方式會不走索引,但是不知道為什么沒有當回事,這次一定要記住 非常有用。
