天天看點

淺析SQL SERVER執行計劃中的各類怪相

在檢視執行計劃或調優過程中,執行計劃裡面有些現象總會讓人有些疑惑不解:

    1:為什麼同一條sql語句有時候會走索引查找,有時候sql腳本又不走索引查找,反而走全表掃描?

    2:同一條sql語句,查詢條件的取值不同,它的執行計劃會一緻嗎?

    3: 同一條sql語句,其執行計劃會變化,為什麼

    4: 在查詢條件的某個或幾個字段上建立了索引,執行計劃就一定會走該索引嗎?

    5:同時存在幾個索引,sql語句會走那個索引?

     ............................................................

有時候如果要跟别人解釋清楚這些問題,如果不通過一些案例或例子來解說,很難闡述清楚,一方面是表達能力問題。另外一方面,再華麗的語言也難敵眼見為實,畢竟人接受資訊大部分通過眼睛,小部分通過耳朵。眼見為實耳聽為虛嗎!

下面來看一個簡單的例子,為什麼我在對應的查詢字段上建有索引,但是它不走索引反而走全表掃描。

淺析SQL SERVER執行計劃中的各類怪相

已經在查詢字段object_id上建立了索引,為什麼sql優化器不走索引,

而要走全表掃描呢?為了說明白,那麼我們借助于查詢提示(hints)強制優化器走索引查找來說明上述情況,對比走索引查找、全表掃描兩者的代價開銷,從

下圖,我們可以看到目前情況下,走全表掃描的開銷要小于索引查找。因為目前情況下,走索引需要額外的io開銷,反而不如全表掃描。是以優化器選擇了走全表

掃描而非索引查找。很多開發人員有種根深蒂固的固執觀念“走索引查找一定要優于全表掃描”(我跟他們解釋的時候,很多人不相信,"慷慨激昂"的質疑我,以

至于我的解釋都顯得蒼白無力),大多數情況下,走索引查找要優于全表掃描,但是在特定的場景、特定資料情況下,會出現全表掃描優于索引查找的情況。尤其是

oracle裡面,很多做開發的同僚一看到sql執行計劃走全表掃描,立馬大呼小叫。其實完全是先入為主的觀念作怪。

淺析SQL SERVER執行計劃中的各類怪相

兩者開銷不一緻,其實在io開銷這一塊,可以從下面看出邏輯讀取的差異。

淺析SQL SERVER執行計劃中的各類怪相
淺析SQL SERVER執行計劃中的各類怪相

那麼接下來,我們将該表的資料從20條記錄增長到10000條記錄,你覺得執行計劃會變化嗎?大家不妨先思考一下這個問題,再看下文。

如下所示,當資料變化時,優化器認為走索引查找要優于全表掃描,是以選擇了索引查找,說到底優化器是基于成本的優化器,在衆多的執行計劃中,它會選擇代價開銷最小的一個執行計劃。

淺析SQL SERVER執行計劃中的各類怪相

此時,強制優化器走全表掃描,對比開銷結果,你會發現結果完全跟上面結果相反。

淺析SQL SERVER執行計劃中的各類怪相

我如果更新該表資料,使其分布完全傾斜,那麼你可以看到對于同一個sql,不同的取值,它的執行計劃也會完全不同。

淺析SQL SERVER執行計劃中的各類怪相

可見同一條sql語句,查詢條件的取值不同,它的執行計劃可能會不一樣。

這幾個例子,其實我想說的是執行計劃往往會受資料變化的、資料分布(直

方圖)的影響,在統計資訊正确的情況下,優化器會根據代價來判斷選取最優的執行計劃。前提是統計資訊準确。在調優過程中,有時候遇到統計資訊不正确導緻執

行計劃很差的情況。我沒有想到一個好的例子來讓大家形象觀察統計資訊的不正确性導緻執行計劃的不同。在此不做詳細讨論。

也許細心的朋友已經發現了我上面測試用例使用的是非聚集索引,也就是說該表是一個堆表。如果我建立的索引是聚集索引,情況會怎麼樣?如下所示,聚集索引下的執行計劃跟非聚集索引情況又不一樣。

淺析SQL SERVER執行計劃中的各類怪相

如下所示,這種情況下走聚集索引查找與聚集索引掃描的開銷幾乎接近。

淺析SQL SERVER執行計劃中的各類怪相

若果我将資料增長到10000條記錄後,情況又不同。這是一個顯而易見的結果,僅僅為了說明資料對執行計劃的影響。

淺析SQL SERVER執行計劃中的各類怪相

下面我們删除test表, 建立另外一個test表, 如下所示

下面看看這兩個sql的執行計劃的差異,這兩個sql略有差異,查詢字段不同,一個是查詢所有字段,一個是查詢create_date, type兩個字段

淺析SQL SERVER執行計劃中的各類怪相

對比兩者的執行計劃

淺析SQL SERVER執行計劃中的各類怪相

在這個簡單例子中,我們可以用查詢必須字段代替*,用索引覆寫避免其走

rid查找,但是實際環境中往往比較複雜,有時候同一個表上的查詢sql,可能非常多,索引覆寫也往往不可能全部涉及。是以在寫sql代碼中,我們要養成

查詢必要字段的習慣,不要生成select *的習慣,因為它有下面一些弊端:

1:如果你隻需要表中幾個字段,select * 會産生額外的io,消耗額外的帶寬資源。當資料庫有大量這類sql,就會産生量變到質變。慢慢影響整個資料庫的性能。

2:習慣成必然(很多時候大部分人都是從select * from開始的),養成了這樣寫sql的習慣。

3:造成額外的書簽查找或是由查找變為掃描

4: 産生潛在的bug 例如 insert into t (column1,…… )select * from m . 如果m表字段增加、或修改字段類型等都會導緻錯誤。

上面僅僅是題外話,這裡要說明的是你的sql寫法也有可能影響執行計劃。

下面來看一個例子,突然某天有這麼樣一個需求(當然實際情況遠比這個複雜),

淺析SQL SERVER執行計劃中的各類怪相

某個開發人員在測試、優化過程中,發現執行計劃走聚集索引掃描,于是想如果給create_date和type字段建立一個索引,那麼它會不會快一點?結果他發現他添加了索引,可是優化器根本不走他建立的索引,為什麼呢?

淺析SQL SERVER執行計劃中的各類怪相

我們又要使用查詢提示強制其走索引查找,來對比其開銷代價

淺析SQL SERVER執行計劃中的各類怪相
淺析SQL SERVER執行計劃中的各類怪相

優化器發現走聚集索引的開銷小于走idx_test_n1索引查找,是以即使你在查詢條件上建有索引,執行計劃還是不會走這個索引。如果我建立索引時,覆寫這些字段,那麼它就會走索引查找而不會是聚集索引。

淺析SQL SERVER執行計劃中的各類怪相

另外還附上我測試過程中,查詢條件取值不同,執行計劃不同的案例(不然有些人也會覺得迷惑),還是那句話,資料會影響執行計劃的選擇。

淺析SQL SERVER執行計劃中的各類怪相
淺析SQL SERVER執行計劃中的各類怪相

後記:

   生産環境的案例往往比我上面幾個簡單例子複雜得多,分析優化起來更加麻煩。我們優化時要透過現象看本質,多思考,多對比才能撥開迷霧見真相!