Oracle不使用索引的几种情况列举
本文介紹了幾種不使用索引的情況,本文實(shí)驗(yàn)的數(shù)據(jù)庫版本均為11.2.0.4
情況1:
我們?cè)谑褂靡粋€(gè)B*樹索引,而且謂詞中沒有使用索引的最前列。
如果這種情況,可以假設(shè)有一個(gè)表T,在T(x,y)上有一個(gè)索引。要做以下查詢:select * from t where y=5。此時(shí),優(yōu)化器就不打算使用T(x,y)上的索引,因?yàn)橹^詞中不涉及X列。在這種情況下,倘若使用索引,可能就必須查看每個(gè)索引條目,而優(yōu)化器通常更傾向于對(duì)T表做一個(gè)全表掃描。
zx@ORCL>create?table?t?as?select?rownum?x,rownum+1?y,rownum+2?z?from?dual?connect?by?level?<?100000;Table?created.zx@ORCL>select?count(*)?from?t;COUNT(*) ----------99999zx@ORCL>create?index?idx_t?on?t(x,y);Index?created.zx@ORCL>exec?dbms_stats.gather_table_stats(user,'T',cascade=>true);PL/SQL?procedure?successfully?completed.zx@ORCL>set?autotrace?traceonly?explain --where條件使用y=5 zx@ORCL>select?*?from?t?where?y=5;Execution?Plan ---------------------------------------------------------- Plan?hash?value:?1601196873-------------------------------------------------------------------------- |?Id??|?Operation?????????|?Name?|?Rows??|?Bytes?|?Cost?(%CPU)|?Time?????| -------------------------------------------------------------------------- |???0?|?SELECT?STATEMENT??|??????|?????1?|????15?|????80???(2)|?00:00:01?| |*??1?|??TABLE?ACCESS?FULL|?T????|?????1?|????15?|????80???(2)|?00:00:01?| --------------------------------------------------------------------------Predicate?Information?(identified?by?operation?id): ---------------------------------------------------1?-?filter("Y"=5) --where條件使用x=5 zx@ORCL>select?*?from?t?where?x=5;Execution?Plan ---------------------------------------------------------- Plan?hash?value:?1594971208------------------------------------------------------------------------------------- |?Id??|?Operation???????????????????|?Name??|?Rows??|?Bytes?|?Cost?(%CPU)|?Time?????| ------------------------------------------------------------------------------------- |???0?|?SELECT?STATEMENT????????????|???????|?????1?|????15?|?????3???(0)|?00:00:01?| |???1?|??TABLE?ACCESS?BY?INDEX?ROWID|?T?????|?????1?|????15?|?????3???(0)|?00:00:01?| |*??2?|???INDEX?RANGE?SCAN??????????|?IDX_T?|?????1?|???????|?????2???(0)|?00:00:01?| -------------------------------------------------------------------------------------Predicate?Information?(identified?by?operation?id): ---------------------------------------------------2?-?access("X"=5)但這并不完全排除使用索引。如果查詢是select x,y from t where y=5,優(yōu)化器就會(huì)注意到,它不必全面掃描表來得到X或Y(x和y都在索引中),對(duì)索引本身做一個(gè)民快速的全面掃描會(huì)更合適,因?yàn)檫@個(gè)索引一般比底層表小得多。還要注意,僅CBO能使用這個(gè)訪問路徑。
zx@ORCL>select?x,y?from?t?where?y=5;Execution?Plan ---------------------------------------------------------- Plan?hash?value:?2497555198------------------------------------------------------------------------------ |?Id??|?Operation????????????|?Name??|?Rows??|?Bytes?|?Cost?(%CPU)|?Time?????| ------------------------------------------------------------------------------ |???0?|?SELECT?STATEMENT?????|???????|?????1?|????10?|????81???(2)|?00:00:01?| |*??1?|??INDEX?FAST?FULL?SCAN|?IDX_T?|?????1?|????10?|????81???(2)|?00:00:01?| ------------------------------------------------------------------------------Predicate?Information?(identified?by?operation?id): ---------------------------------------------------1?-?filter("Y"=5)另一種情況下CBO也會(huì)使用T(x,y)上的索引,這就是索引跳躍式掃描。當(dāng)且僅當(dāng)索引的最前列(在上面的例子中最前列是x)只有很少的幾個(gè)不同值,而且優(yōu)化器了解這一點(diǎn),跳躍式掃描(skip scan)就能很好地發(fā)揮作用。例如,考慮(GEMDER,EMPNO)上的一個(gè)索引,其中GENDER可取值有M和F,而且EMPNO是唯一的。對(duì)于以下查詢:
select * from t where empno=5;
可以考慮使用T上的那個(gè)索引采用跳躍式掃描方法來滿足這個(gè)查詢,這說明從概念上講這個(gè)查詢會(huì)如下處理:
select * from t where GENDER='M' and empno=5
union all
select * from t where GENDER='F' and empno=5
它會(huì)跳躍式地掃描索引,以為這是兩個(gè)索引:一個(gè)對(duì)應(yīng)值M,另一個(gè)對(duì)應(yīng)值F。
zx@ORCL>create?table?t1?as?select?decode(mod(rownum,2),0,'M','F')?gender,all_objects.*?from?all_objects;Table?created.zx@ORCL>create?index?idx_t1?on?t1(gender,object_id);Index?created.zx@ORCL>exec?dbms_stats.gather_table_stats(user,'T1',cascade=>true);PL/SQL?procedure?successfully?completed.zx@ORCL>set?autotrace?traceonly?explain zx@ORCL>select?*?from?t1?where?object_id=42;Execution?Plan ---------------------------------------------------------- Plan?hash?value:?4072187533------------------------------------------------------------------------------------- |?Id??|?Operation???????????????????|?Name??|?Rows??|?Bytes?|?Cost?(%CPU)|?Time?????| ------------------------------------------------------------------------------------- |???0?|?SELECT?STATEMENT????????????|???????|?????1?|???100?|?????4???(0)|?00:00:01?| |???1?|??TABLE?ACCESS?BY?INDEX?ROWID|?T1????|?????1?|???100?|?????4???(0)|?00:00:01?| |*??2?|???INDEX?SKIP?SCAN???????????|?IDX_T1?|?????1?|???????|?????3???(0)|?00:00:01?| -------------------------------------------------------------------------------------Predicate?Information?(identified?by?operation?id): ---------------------------------------------------2?-?access("OBJECT_ID"=42)filter("OBJECT_ID"=42)INDEX SKIP SCAN 步驟告訴Oralce要跳躍式掃描這個(gè)索引,查詢GENDER值有改變的地方,并從那里開始向下讀樹,然后在所考慮的各個(gè)虛擬索引中查詢OBJECT_id=42。如果大幅增加GENDER的可取值,如下:
zx@ORCL>alter?table?t1?modify?GENDER?varchar2(2);Table?altered.zx@ORCL>update?t1?set?gender=(chr(mod(rownum,1024)));84656?rows?updated.zx@ORCL>commit;Commit?complete.zx@ORCL>exec?dbms_stats.gather_table_stats(user,'T1',cascade=>true);PL/SQL?procedure?successfully?completed.zx@ORCL>set?autotrace?traceonly?explain zx@ORCL>select?*?from?t1?where?object_id=42;Execution?Plan ---------------------------------------------------------- Plan?hash?value:?1601196873-------------------------------------------------------------------------- |?Id??|?Operation?????????|?Name?|?Rows??|?Bytes?|?Cost?(%CPU)|?Time?????| -------------------------------------------------------------------------- |???0?|?SELECT?STATEMENT??|??????|?????1?|???101?|???344???(1)|?00:00:05?| |*??1?|??TABLE?ACCESS?FULL|?T1???|?????1?|???101?|???344???(1)|?00:00:05?| --------------------------------------------------------------------------Predicate?Information?(identified?by?operation?id): ---------------------------------------------------1?-?filter("OBJECT_ID"=42)情況2:
在使用select count(*) from t查詢(或類似的查詢),而且在表T上有一個(gè)B*樹索引。不過,優(yōu)化器并不是統(tǒng)計(jì)索引條目,而是在全面掃描這個(gè)表(盡管索引比表要小)。在這種情況下,索引可能建立在一個(gè)允許有NULL值的列上。由于對(duì)于索引鍵完全為null的行不會(huì)建立相應(yīng)的索引條目,所以索引中的行數(shù)可能并不是表中的行數(shù)。這里優(yōu)化器的選擇是對(duì)的,如若不然,倘若它使用索引來統(tǒng)計(jì)行數(shù),則可能會(huì)得到一個(gè)錯(cuò)誤的答案。
情況3:
對(duì)于一個(gè)有索引的列,做以下查詢:
select * from t where function(indexed_column)=value;
卻發(fā)現(xiàn)沒有使用indexed_colum上的索引。原因是這個(gè)列上使用了函數(shù)。如果是對(duì)indexed_column的值建立了索引,而不是對(duì)function(indexed_column)的值建索引。在此不能使用這個(gè)索引。如果愿意,可以另外對(duì)函數(shù)建立索引。
zx@ORCL>select?*?from?t?where?mod(x,999)=1;Execution?Plan ---------------------------------------------------------- Plan?hash?value:?1601196873-------------------------------------------------------------------------- |?Id??|?Operation?????????|?Name?|?Rows??|?Bytes?|?Cost?(%CPU)|?Time?????| -------------------------------------------------------------------------- |???0?|?SELECT?STATEMENT??|??????|??1000?|?34000?|???153???(1)|?00:00:02?| |*??1?|??TABLE?ACCESS?FULL|?T????|??1000?|?34000?|???153???(1)|?00:00:02?| --------------------------------------------------------------------------Predicate?Information?(identified?by?operation?id): ---------------------------------------------------1?-?filter(MOD("X",999)=1)zx@ORCL>create?index?idx_t_f?on?t(mod(x,999));Index?created.zx@ORCL>exec?dbms_stats.gather_table_stats(USER,'T',cascade=>true);PL/SQL?procedure?successfully?completed.zx@ORCL>select?*?from?t?where?mod(x,999)=1;Execution?Plan ---------------------------------------------------------- Plan?hash?value:?4125918735--------------------------------------------------------------------------------------- |?Id??|?Operation???????????????????|?Name????|?Rows??|?Bytes?|?Cost?(%CPU)|?Time?????| --------------------------------------------------------------------------------------- |???0?|?SELECT?STATEMENT????????????|?????????|???100?|??3800?|???102???(0)|?00:00:02?| |???1?|??TABLE?ACCESS?BY?INDEX?ROWID|?T???????|???100?|??3800?|???102???(0)|?00:00:02?| |*??2?|???INDEX?RANGE?SCAN??????????|?IDX_T_F?|???100?|???????|?????1???(0)|?00:00:01?| ---------------------------------------------------------------------------------------Predicate?Information?(identified?by?operation?id): ---------------------------------------------------2?-?access(MOD("X",999)=1)情況4:
考慮以下情況,已經(jīng)對(duì)一個(gè)字符錢建立了索引。這個(gè)列只包含數(shù)據(jù)數(shù)據(jù)。如果使用以下語法來查詢:
select * from t where indexed_colum=5;
注意查詢中的數(shù)字5是常數(shù)5(而不是一個(gè)字符串),此時(shí)就沒有使用INDEXED_COLUMN上的索引。這是因?yàn)?#xff0c;前面的查詢等價(jià)于以下查詢:
select * from t where to_number(indexed_column)=5;
我們對(duì)這個(gè)列隱式地應(yīng)用了一個(gè)函數(shù),如情況3所述,這就會(huì)禁止使用這個(gè)索引。
zx@ORCL>create?table?t2?(x?char(1)?constraint?t2_pk?primary?key?,y?date);Table?created.zx@ORCL>insert?into?t2?values('5',sysdate);1?row?created.zx@ORCL>commit;Commit?complete.zx@ORCL>exec?dbms_stats.gather_table_stats(USER,'T2',cascade=>true);PL/SQL?procedure?successfully?completed.zx@ORCL>explain?plan?for?select?*?from?t2?where?x=5;Explained.zx@ORCL>select?*?from?table(dbms_xplan.display);PLAN_TABLE_OUTPUT ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Plan?hash?value:?1513984157-------------------------------------------------------------------------- |?Id??|?Operation?????????|?Name?|?Rows??|?Bytes?|?Cost?(%CPU)|?Time?????| -------------------------------------------------------------------------- |???0?|?SELECT?STATEMENT??|??????|?????1?|????12?|?????3???(0)|?00:00:01?| |*??1?|??TABLE?ACCESS?FULL|?T2???|?????1?|????12?|?????3???(0)|?00:00:01?| --------------------------------------------------------------------------Predicate?Information?(identified?by?operation?id): ---------------------------------------------------1?-?filter(TO_NUMBER("X")=5)Note ------?dynamic?sampling?used?for?this?statement?(level=2)可以看到,它會(huì)全面掃描表;另外即使我們對(duì)查詢給出了以下提示:
zx@ORCL>explain?plan?for?select?/*+?index(t2?t2_pk)?*/?*?from?t2?where?x=5;Explained.zx@ORCL>select?*?from?table(dbms_xplan.display);PLAN_TABLE_OUTPUT ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Plan?hash?value:?3365102699------------------------------------------------------------------------------------- |?Id??|?Operation???????????????????|?Name??|?Rows??|?Bytes?|?Cost?(%CPU)|?Time?????| ------------------------------------------------------------------------------------- |???0?|?SELECT?STATEMENT????????????|???????|?????1?|????10?|?????2???(0)|?00:00:01?| |???1?|??TABLE?ACCESS?BY?INDEX?ROWID|?T2????|?????1?|????10?|?????2???(0)|?00:00:01?| |*??2?|???INDEX?FULL?SCAN???????????|?T2_PK?|?????1?|???????|?????1???(0)|?00:00:01?| -------------------------------------------------------------------------------------Predicate?Information?(identified?by?operation?id): ---------------------------------------------------2?-?filter(TO_NUMBER("X")=5)在此使用了索引,但是并不像我們想像中那樣對(duì)索引完成唯一掃描(UNIQUE SCAN),而是完成了全面掃描(FULL SCAN)。原因從最后一行輸出可以看出:filter(TO_NUMBER("X")=5)。這里對(duì)這個(gè)數(shù)據(jù)庫列應(yīng)用了一個(gè)隱式函數(shù)。X中存儲(chǔ)的字符串必須轉(zhuǎn)換為一個(gè)數(shù)字,之后才能與值5進(jìn)行比較。在此無法把5轉(zhuǎn)換為一個(gè)串,因?yàn)槲覀兊腘LS(國家語言支持)設(shè)置會(huì)控制5轉(zhuǎn)換成串時(shí)的具體形式(而這是不確定的,不同的NLS設(shè)置會(huì)有不同的控制),所以應(yīng)當(dāng)把串轉(zhuǎn)為數(shù)據(jù)。而這樣一樣(由于應(yīng)用也函數(shù)),就無法使用索引來快速地查找這一行了。如果只是執(zhí)行串與串的比較:
zx@ORCL>explain?plan?for?select?*?from?t2?where?x='5';Explained.zx@ORCL>select?*?from?table(dbms_xplan.display);PLAN_TABLE_OUTPUT ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Plan?hash?value:?3897349516------------------------------------------------------------------------------------- |?Id??|?Operation???????????????????|?Name??|?Rows??|?Bytes?|?Cost?(%CPU)|?Time?????| ------------------------------------------------------------------------------------- |???0?|?SELECT?STATEMENT????????????|???????|?????1?|????12?|?????1???(0)|?00:00:01?| |???1?|??TABLE?ACCESS?BY?INDEX?ROWID|?T2????|?????1?|????12?|?????1???(0)|?00:00:01?| |*??2?|???INDEX?UNIQUE?SCAN?????????|?T2_PK?|?????1?|???????|?????1???(0)|?00:00:01?| -------------------------------------------------------------------------------------Predicate?Information?(identified?by?operation?id): ---------------------------------------------------2?-?access("X"='5')14?rows?selected.不出所料,這會(huì)得到我們期望的INDEX UNIQUE SCAN,而且可以看到這里沒有應(yīng)用函數(shù)。一定要盡可能地避免隱式轉(zhuǎn)換。
還經(jīng)常出現(xiàn)一個(gè)關(guān)于日期的問題,如果做以下查詢:
select * from t where trunc(date_col)=trunc(sysdate);
而且發(fā)現(xiàn)這個(gè)查詢沒有使用DATE_COL上的索引,為了解決這個(gè)問題,可以對(duì)trunc(date_col)建立索引,或者使用區(qū)間比較運(yùn)算符來查詢(也許這是更容易的做法)。下面來看對(duì)日期使用大于或小于運(yùn)算符的一個(gè)例子。可以認(rèn)識(shí)到以下條件:
trunc(date_col)=trunc(sysdate)
與下面的條件是一樣的:
date_col>= trunc(sysdate) and date_col<trunc(sysdate+1)
如果可能的話,倘若謂詞中有函數(shù),盡量不要對(duì)數(shù)據(jù)庫列應(yīng)用這些函數(shù)。這樣做不僅可以使用更多的索引,還能減少處理數(shù)據(jù)庫所需的工作。使用轉(zhuǎn)換的條件查詢時(shí)只會(huì)計(jì)算一次TRUNC值,然后就能使用索引來查找滿足條件的值。使用trunc(date_col)=trunc(sysdate)時(shí),trunc(date_col)則必須對(duì)整個(gè)表(而不是索引)中的每一行計(jì)算一次。
情況5:
另一種情況,如果使用了索引,實(shí)際上反而會(huì)更慢。Oracle(對(duì)于CBO而言)只會(huì)在合理地時(shí)候才使用索引。
zx@ORCL>create?table?t3?(x,y?null,primary?key?(x)?)?as?select?rownum?x,object_name?y?from?all_objects;Table?created.zx@ORCL>exec?dbms_stats.gather_table_stats(USER,'T3',cascade=>true);PL/SQL?procedure?successfully?completed.zx@ORCL>set?autotrace?traceonly?explain? --運(yùn)行一個(gè)查詢查詢相對(duì)較少的數(shù)據(jù) zx@ORCL>select?count(y)?from?t3?where?x<50;Execution?Plan ---------------------------------------------------------- Plan?hash?value:?1961899233---------------------------------------------------------------------------------- |?Id??|?Operation?????????|?Name?????????|?Rows??|?Bytes?|?Cost?(%CPU)|?Time?????| ---------------------------------------------------------------------------------- |???0?|?SELECT?STATEMENT??|??????????????|?????1?|?????5?|?????2???(0)|?00:00:01?| |???1?|??SORT?AGGREGATE???|??????????????|?????1?|?????5?|????????????|??????????| |*??2?|???INDEX?RANGE?SCAN|?SYS_C0017451?|????49?|???245?|?????2???(0)|?00:00:01?| ----------------------------------------------------------------------------------Predicate?Information?(identified?by?operation?id): ---------------------------------------------------2?-?access("X"<50) --運(yùn)行一個(gè)查詢查詢相對(duì)較多的數(shù)據(jù) zx@ORCL>select?count(y)?from?t3?where?x<50000;Execution?Plan ---------------------------------------------------------- Plan?hash?value:?463314188--------------------------------------------------------------------------- |?Id??|?Operation??????????|?Name?|?Rows??|?Bytes?|?Cost?(%CPU)|?Time?????| --------------------------------------------------------------------------- |???0?|?SELECT?STATEMENT???|??????|?????1?|????30?|???117???(1)|?00:00:02?| |???1?|??SORT?AGGREGATE????|??????|?????1?|????30?|????????????|??????????| |*??2?|???TABLE?ACCESS?FULL|?T3???|?50000?|??1464K|???117???(1)|?00:00:02?| ---------------------------------------------------------------------------Predicate?Information?(identified?by?operation?id): ---------------------------------------------------2?-?filter("X"<50000)這個(gè)例子顯示出優(yōu)化器不一定會(huì)使用索引,而且實(shí)際上,它會(huì)做出正確的選擇。對(duì)查詢調(diào)優(yōu)時(shí),如果發(fā)現(xiàn)你認(rèn)為本該使用的某個(gè)索引實(shí)際上并沒有用到,就不要冒然強(qiáng)制使用這個(gè)索引,而應(yīng)該先做個(gè)測試,并證明使用這個(gè)索引后確實(shí)會(huì)加快速度(通過耗用時(shí)間和I/O次數(shù)來評(píng)判),然后再考慮讓CBO就范(強(qiáng)制它使用這個(gè)索引)。總得先給出個(gè)理由吧。
情況6:
有一段時(shí)間沒有分析表了。這些表起先很小,但等到查看時(shí),它們已經(jīng)增長得非常大。現(xiàn)在索引就有很有意義(盡管原先并非如此)。如果此時(shí)分析這個(gè)表,就會(huì)使用索引。
如果沒有正確的統(tǒng)計(jì)信息,CBO將無法做出正確的決定。
以上介紹了6種不使用索引的情況,歸根結(jié)底原因通常就是“不能使用索引,使用索引會(huì)返回不正確的結(jié)果”,或者“不應(yīng)該使用,如果使用了索引,性能會(huì)變得很糟糕”。
參考:《9I10G11G編程藝術(shù) ?深入數(shù)據(jù)庫體系結(jié)構(gòu)》
MOS文檔:Diagnosing Why a Query is Not Using an Index (文檔 ID 67522.1)
轉(zhuǎn)載于:https://blog.51cto.com/hbxztc/1893241
總結(jié)
以上是生活随笔為你收集整理的Oracle不使用索引的几种情况列举的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微信运营中踩的坑
- 下一篇: 走进AngularJs(六) 服务