您好,登錄后才能下訂單哦!
索引無效原因
最近遇到一個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_DATE 、 TO_NUMBER 、 TRUNC ...等等。
此時的解決辦法可以使用 函數索引 ,顧名思義就是把使用函數后的字段整體當成索引中的字段。
如下圖中的 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類型就行了,雖然看起來要改動的地方很多,其實解決了更大的問題。
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。