有索引,為什麼Oracle還是選擇全表掃描
阿新 • • 發佈:2021-06-18
一、概述
sql語句執行慢,大部分情況下建個索引就快了,但有些時候索引好像不起作用,這是什麼原因導致的呢?結合日常經驗,我總結了以下索引用不到的情況。
- 返回的行數比例大
- 不等於,not in,is null
- 列上有運算
- 列上有函式
- 隱式轉換
- 列在組合索引的中間或右邊
- 統計資訊不正確
- 索引失效
二、環境準備
oracle 11g資料庫
create table test as select * from dba_objects; update test set generated = null where object_id < 20; alter table test add str_id varchar2(10); update test set str_id = object_id; create index idx_1 on test(object_id); create index idx_2 on test(temporary); create index idx_3 on test(generated); create index idx_4 on test(created); create index idx_5 on test(str_id); create index idx_6 on test(object_type,data_object_id); create index idx_7 on test(namespace); begin dbms_stats.gather_table_stats(ownname => 'SCOTT', tabname => 'TEST', estimate_percent => dbms_stats.auto_sample_size, cascade => true, method_opt => 'for columns temporary size auto, namespace size 1', no_invalidate => false); end; /
三、開始實驗
1) 返回的行數比例大
返回行數多的不走索引
a. select * from test where object_id > 20000; // 索引
b. select * from test where object_id > 20; // 全表
select count(*) from test; // 13544行
select count(*) from test where object_id > 20; // 13525行
select count(*) from test where object_id > 20000; // 0行
返回的行數除以表的總行數的值越小,表示列的選擇率越高,使用索引的概率就越大
2) 不等於,not in,is null
test表的temporary列只有兩個值('Y','N‘),以下兩條sql返回的結果一樣,<>不走索引
a. select * from test where temporary = 'Y'; // 索引
b. select * from test where temporary <> 'N'; // 全表
以下兩條sql返回的結果一樣,not in不走索引
c. select * from test where temporary in ('Y'); // 索引 d. select * from test where temporary not in ('N'); // 全表
generated上面有索引,而且返回出來的行也很少,但是is null不走索引
select count(*) from test where generated is null; // 18行
e. select * from test where generated is null; // 全表
3) 列上有運算
以下兩條sql返回的結果一樣,但列上有運算的不走索引
a. select * from test where object_id + 1 > 20000; // 全表
b. select * from test where object_id > 20000 - 1; // 索引
4) 列上有函式
以下兩條sql執行結果一樣,但列上有函式的不走索引
a. select * from test where to_char(CREATED, 'yyyy-mm-dd hh24:mi:ss') = '2021-04-01 22:24:03'; // 全表
b. select * from test where CREATED = to_date('2021-04-01 22:24:03', 'yyyy-mm-dd hh24:mi:ss'); // 索引
5) 隱式轉換
以下兩條sql結果一樣,發生隱式轉換的不走索引
a. select * from test where str_id = '3'; // 索引
b. select * from test where str_id = 3; // 全表
當比較一個字元型和數值型的值時,ORACLE會把字元型的值隱式轉換為數值型,執行計劃裡面顯示filter(TO_NUMBER("STR_ID")=3),對應就是這個列上自動加了to_number函式
如果列是數字型別呢,以下兩個sql都會走索引,這是因為'3'會變成3,沒有發生列的隱式轉換
c. select * from test where object_id = '3'; // 索引
d. select * from test where object_id = 3; // 索引
6) 列在組合索引的中間或右邊
data_object_id上有索引,而且返回的行數也很少,但是由於該列值不在索引的左邊,所以無法走索引
a. select count(*) from test where data_object_id = 3; // 1行
b. select * from test where data_object_id = 3; // 全表
7) 統計資訊不正確
select count(*), namespace from test group by namespace;
以下sql明顯應該走全表掃描,但是卻誤走了索引
a. select * from test where namespace = 1; // 索引
重新收集列的統計資訊
begin
dbms_stats.gather_table_stats(ownname => 'SCOTT',
tabname => 'TEST',
estimate_percent => dbms_stats.auto_sample_size,
cascade => true,
method_opt => 'for columns namespace size auto',
no_invalidate => false);
end;
/
現在執行計劃就正確了,走全表,因為返回的行數多
b. select * from test where namespace = 1; // 全表
以下是查統計資訊sql
表 select table_name,num_rows,blocks,partitioned,tablespace_name,last_analyzed from user_tables where table_name=trim(upper('&table_name'));
列 select table_name,column_name,num_distinct,num_nulls,num_buckets,histogram,last_analyzed from user_tab_col_statistics where table_name=trim(upper('&table_name')) order by 1,2,3;\r
8) 索引無效
索引有時候會因為資料庫的操作而失效,例如移動表
alter table test move;
以下sql應走索引,卻走了全表
a. select * from test where object_id = 3; // 全表
檢視索引狀態都是UNUSABLE
select a.*,b.status,b.last_analyzed from
(select table_name,index_name,listagg(column_name,',') within group(order by column_position) as columns from user_ind_columns group by table_name,index_name) a,
(select table_name,index_name,status,last_analyzed from user_indexes) b
where a.table_name=b.table_name and a.index_name=b.index_name and a.table_name=trim(upper('&table_name')) order by 1,2,3;
重建索引
alter index IDX_2 rebuild online;
再次執行sql,執行計劃就正確了,走索引
b. select * from test where object_id = 3; // 索引
四、總結
- 我們在編寫sql的時候,首要考慮sql執行的準確性,其次也需要考慮sql執行的效率。
- 並不是索引越多越好,使用索引也並不一定會加快sql的查詢效率,這些都需要我們去不斷學習。