1. 程式人生 > 其它 >Oracle Index 索引無效原因

Oracle Index 索引無效原因

技術標籤:oracle 索引失效索引失效資料庫

索引無效原因

最近遇到一個SQL語句的效能問題,修改功能之前的執行時間平均為0.3s,可是新增新功能後,時間達到了4~5s。雖然幾張表的資料量都比較大(都在百萬級以上),但是也都有正確建立索引,不知道到底慢在了哪裡,下面展開調查。

經過幾次排除,把問題範圍縮小在索引上,首先在確定索引本身沒有問題的前提下,考慮索引有沒有被使用到,那麼新的問題來了,怎麼知道指定索引是否被啟用。

判斷索引是否被執行

1. 分析索引

即將索引至於監控狀態下,對索引進行分析。如下對ID_TT_SHOHOU_HIST_002索引進行分析

alter index ID_TT_SHOHOU_HIST_002 monitoring usage;

2. 檢視v$object_usage檢視中記錄的資訊

select * from v$object_usage;

詳細資訊
欄位依次為:

  • INDEX_NAME --索引名
  • TABLE_NAME --表名
  • MONITORING --是否被監控
  • USED--是否被啟用
  • START_MONITORING --監控開始時間
  • END_MONITORING --監控結束時間

如上圖,雖然索引已經被引用,但是速度依舊很慢,莫非是雖然啟用了索引,但是又被其他的一些原因拖慢了速度,繼續調查。
調查途中,收集到一些Oracle 資料庫不走索引的原因分享給大家

不走索引的原因

1. 在索引列上使用函式時不會使用索引

例如常見的,TO_CHAR

TO_DATETO_NUMBERTRUNC...等等。
此時的解決辦法可以使用函式索引,顧名思義就是把使用函式後的欄位整體當成索引中的欄位。
如下圖中的TO_CHAR(SHOHOU_DATE, 'YYYYMMDD')就是一個函式索引,因為日期欄位中含有時分秒,進行日期比較的時候,必須轉化成固定的格式。

CREATE INDEX ID_TT_SHOHOU_HIST_003
ON TT_SHOHOU_HIST
(DEL_FLG,TO_CHAR(SHOHOU_DATE, 'YYYYMMDD'), SHOHOU_ID)
TABLESPACE SALESPA_INDEX

2. 索引的列進行隱式的型別轉換

SELECT * FROM TABLE WHERE INDEX_COLUM = 5

上面語句中的INDEX_COLUM欄位型別為VARCHAR2,這時就會發生隱式型別轉換,類似於

SELECT * FROM TABLE WHERE TO_NUMBER(INDEX_COLUM) = 5

3. WHERE 子句中使用不等於操作

不等於操作包括:<>,!=,NOT colum >= ?,NOT colum <= ?
替代方式可以使用OR, colum <> 0 =====> colum > 0 or colum < 0;

4. 使用 IS NULL 和 IS NOT NULL

替代方式:函式索引
通過nvl(b,c)將為空的欄位轉為不為空的c值,再在函式nvl(b,c)上建立函式索引
轉換前

SELECT * FROM A WHERE B = NULL

轉換後

SELECT * FROM A WHERE NVL(B,C) = C

5. 組合索引

組合索引:由多個列構成的索引。如

CREATE INDEX INDEX_EMP ON EMP (COL1,COL2,COL3,...)

INDEX_EMP則為複合索引,COL1為引導列。進行查詢時,可以使用WHERE COL1 = ?,也可以使用WHERE COL1 = ? AND COL2 = ?,這樣的限制條件都會使用索引,但是WHERE COL2 = ?,不會使用索引,所以限制條件中包含引導列時,該限制條件才會使用組合索引。

經過一番調查,我使用的SQL語句檢索條件中對時間列進行TO_CHAR(TTSH.SHOHOU_DATE, 'YYYYMMDD')格式化日期,去除掉時分秒。再建立函式索引後仍然沒有起到優化加速的效果,仔細觀察發現在使用TO_CHAR格式化時間之後,又進行TO_DATE轉為時間格式和其他子查詢的欄位進行比較。然後很快想到,建立一個TO_DATE(TO_CHAR(TTSH.SHOHOU_DATE, 'YYYYMMDD'), 'YYYYMMDD')這樣的函式索引,結果缺失提高了不少的執行速度,從4~5s縮短到了0.5s左右。

但是這只是在PL/SQL軟體中執行SQL提高了速度,實際專案執行仍然是4~5s,使用語句檢視索引的使用狀況時,發現並沒有使用索引,但是在PL/SQL軟體中確實呼叫了索引,這至今都是未解之謎,如果有大神知道原因希望能幫我解答一下這個疑問。

既然不能自動呼叫,只能強制讓SQL走指定索引了,強制的方法如下

SELECT語句後加入/*+INDEX(TTSH ID_TT_SHOHOU_HIST_002)*/,其中TTSH是表的別名(當表有別名的時候,必須在索引前加入表的別名)

SELECT /*+INDEX(TTSH ID_TT_SHOHOU_HIST_002)*/ 
TO_DATE(TO_CHAR(TTSH.SHOHOU_DATE, 'YYYYMMDD'), 'YYYYMMDD') AS SHOHOU_DATE 
FROM TT_SHOHOU_HIST TTSH
WHERE ...

至此,SQL的效率問題已經解決了,但是這不是最好的解決方案。
首先,目前的索引中已經存在包含TO_CHAR(TTSH.SHOHOU_DATE, 'YYYYMMDD')的函式索引,又再建立一個TO_DATE(TO_CHAR(TTSH.SHOHOU_DATE, 'YYYYMMDD'), 'YYYYMMDD'),看著就很難受
其次,強制使用索引的方法需要在SQL中指定索引名,假如資料庫中的索引名發生變更,還需去更改SQL。
最好的方法是把索引欄位的TO_DATE去掉,統一使用TO_CHAR的索引。

AND CAL.CALENDER = TO_DATE(TO_CHAR(TTSH.SHOHOU_DATE, 'YYYYMMDD'), 'YYYYMMDD')

上面的部分語句因為CALENDER欄位是DATE型別,所以比較時使用了TO_DATE,其實只要把CALENDER轉化成CHAR型別就行了,雖然看起來要改動的地方很多,其實解決了更大的問題。

參考部落格地址:

https://blog.csdn.net/u010662668/article/details/61920199
https://blog.csdn.net/chenjian98306/article/details/50331803
https://www.cnblogs.com/jianggc/articles/2029854.html