字數 3507閱讀 1275評論 1贊 2數據庫
做者:邱峙微信
對於SQL調優,要調就調到極致,小編並非處女座,而是由於在一個併發量很大的業務系統中,對於頻繁執行的單條SQL性能的提高,可能對總體數據庫的性能提高都有很大的意義。
可是遇到order by字段後面的字段,特別是當這個字段不在過濾條件中時,小編就會內心打鼓,是加到索引裏面呢,仍是不加到索引裏面呢,加進去會不會沒有起到提高性能的做用,反而讓索引變得更加複雜,給系統帶來沒必要要的額外負擔,「偷雞不成蝕把米」,開個玩笑。可是若是直接忽略掉這個問題,極可能這個提高系統性能的機會就被錯過了。
因此今天小編就和你們探討一下,面對order by字段後面的條件,特別是這個條件不在過濾條件中時,到底要不要加入索引中,對於SQL調優這筆帳,索引中加入order by字段,是賺了仍是賠了❓併發
空話很少說,先來一個小實驗,熱一下身。經過屢次複製dba_objects中的數據,生成測試表T1,大約1000萬行數據。作一個簡單的查詢,查詢T1表中object_id最小的10行數據,select * from (select * from T1 order by object_id) where rownum<=10,耗時‘Elapsed: 00:00:35.92’,執行計劃以下:
執行計劃中能夠看到,先做了一個全表掃,取到告終果集11M行(能夠粗略理解爲11百萬行,這個測試表T1行數爲11943842)。而後做了一個排序,截取最小的10條記錄,最後返回結果。下面咱們在object_id字段上建一個索引I_T1_ORDER3,做一個比較。
耗時從剛纔的35秒,直接降到了 ‘Elapsed: 00:00:00.01’,提高性能的效果很是明顯。索引和執行計劃以下:
從執行計劃中能夠看到,優化器直接從索引中找到了最小的10條記錄,而後回表取得結果集返回。相比上一個執行計劃,省去了全表掃描,省去了排序,因此執行時間和系統資源消耗都大大減小。
在這裏做一個簡單的分析,首先索引和數據不一樣,是按照有序的排列存儲的,當結果集要求按照順序取得一部分數據時,索引的功效會體現的很是明顯,本次查詢就是要取得object_id最小的10條記錄。其次,創建索引系統只須要消耗一次資源完成排序過程,而若是沒有索引,執行不一樣的語句可能每次都要經歷排序的過程,會消耗更多的系統資源。從這個實驗看,在order by字段建索引是很是划算的,並且order by字段並不必定非要加入到where條件中也能夠生效。性能
這裏小編要和你們分享一個本身踩到的「坑」,就是小編起初在建了索引I_T1_ORDER3後,這條查詢語句的執行計劃並不選擇索引,增長了hint提示也不選擇,小編都有點懷疑人生了,明顯使用索引會好,爲何優化器恰恰不選擇索引呢,並且是加了hint也不走。在修改object_id列爲非空屬性(NOT NULL)後,優化器才選擇了這個索引。小編這裏是這麼理解的,若是這一列存在NULL值,NULL值是沒有大小這一說法的,並且不會被保存在索引中。若是優化器沒法肯定該列沒有NULL值,爲了保證結果集的準確性,寧願選擇更慢的全表掃描,也不會選擇走可能存在NULL的索引,即便用戶指定了hint也不會選擇(這裏的幾句話有點繞,你們耐心讀一下)。從這一點來看,開發Oracle優化器的小夥伴是很是靠譜的。測試
上面的實驗中order by字段加入索引的做用很是明顯。但是在實際生產環境中,能有這麼簡單的SQL來給DBA調優的機會並很少,實際生產中的SQL每每要更復雜一些。下面咱們就把測試變得複雜一點,複製測試表T1,生成測試表T2,查詢object_type相似INDEX中object_id最小的10條記錄,select * from (select * from T2 where object_type like '%INDEX%' order by object_id) where rownum<=10。
這條語句比第一個實驗中多了過濾條件,可是使用了like方法。按一般的經驗建索引首先會考慮where條件後的字段,可是在使用like的過濾條件上創建索引,效果可能並很差。但是若是這條語句是業務系統中執行頻率很是高的語句呢,咱們仍是硬着頭皮優化一下吧。先看一下沒有索引的狀況。
執行時間「Elapsed: 00:00:08.75」,接近9s,從執行計劃中看到,先是全表掃描過濾出了1597K條(1597K約163萬條)記錄,而後做了個排序,返回object_id最小的10條記錄。
這樣的執行效率在生產系統中是不能接受的,可是在like列上建索引效果可能並很差,本着敬業的精神,仍是試一下吧。在僅有的兩個條件 object_type和object_id上建一個複合索引I_T2_ORDER2,並
加入hint提示,結果以下:
執行時間「Elapsed: 00:00:17.25」,比剛纔9秒還多花了8秒。從執行計劃中能夠看到,先是在索引I_T2_ORDER2中定位到1597K條記錄,而後回表取得1597K記錄的結果集,再排序取到object_id最小的10條記錄。與上一個執行計劃相比,反而增長了一個讀索引的步驟,因此係統資源消耗更多,執行時間也更長,並且雖然order by字段加入到索引中,並無省去排序的步驟。在這裏這個索引建的就有點虧了。
「理想很豐滿,現實很骨感」,看來SQL變得複雜之後,order by字段在索引裏面果真不靈了,這招很差使。不要着急,我們分析一下,爲何很差使了。你們都知道索引是樹狀結構,如今I_T2_ORDER2索引中有兩個字段,這個索引結構大概是這個樣子的,以下圖。
你們能夠看到,對應INDEX節點下面的object_id「3,9,13」是有序的, INDEX PARTITION節點也相似。可是把INDEX節點和INDEX PARTITION節點對應的object_id放到一塊兒,「3,9,13…2,15,17」,就變得無序了,因此優化器雖然使用了索引,但不得再也不作一遍排序,order by索引的功效並無發揮出來。
看到這裏是否是有點灰心了,這條語句無法優化了。看下本文的標題,換個角度想一下,說不定這條語句還有救。與測試表T1同樣,在object_id上建一個索引I_T2_ORDER3試一下。
執行時間從17s,直接變爲「Elapsed: 00:00:00.01」,從執行計劃能夠看到,優化器經過索引過濾了817條記錄後獲得了想要的10條結果,以後回表取得結果返回。與上面的執行計劃相比,時間消耗和資源消耗都大大減小。
這裏咱們簡單分析一下,索引I_T2_ORDER3是按照object_id有序排列的,當優化器按序處理到817條記錄時,就已經獲得了想要的object_type相似INDEX,object_id最小的10條記錄,而後回表取到結果並返回,省去了全表掃描以及排序的消耗,因此效率大大提高。索引結構以下圖。
執行時間和系統消耗,都大大減小,那麼到這裏咱們是否是能夠交差了。再看一下咱們文章的開頭,「對於SQL調優,要調就調到極致」, 「對於頻繁執行的單條SQL性能的提高,對總體數據庫的性能提高都有很大的意義」。咱們再想一下還可不能夠更優。小編在這裏又建了一個索引I_T2_ORDER4,再執行這條查詢語句。
執行時間「Elapsed: 00:00:00.01」,從執行計劃中能夠看到,優化器經過索引直接定位到了想要的10條記錄,回表取得10條記錄並返回。最終結果只有10條記錄,優化器也只處理了10條記錄,幾乎沒有任何的資源浪費。I_T2_ORDER4索引的結構圖以下,能夠看到,過濾條件已經在索引中存儲了,因此優化器能夠在索引中直接定位到最終的10條記錄。
到這裏,從建索引的角度出發,小編認爲這條SQL的優化能夠交差了。優化
最後小編想說的是,遇到相似order by字段是否加入索引的問題,或者其餘一些你們猶豫的問題,能夠大膽的嘗試,並打開思路,從不一樣的角度考慮,多作測試,不要錯過任何一個提高性能的機會。
對於order by字段加入索引自己這個問題,若是最終的結果集是以order by字段爲條件篩選的,將order by字段加入索引,並放在索引中正確的位置,會有明顯的性能提高。不過這裏要注意小編前面提到的那個坑,order by字段須要是非空的屬性,不然會無效。
好了,今天的分享就到這裏,你們能夠關注咱們的專欄。spa