SQL Server執行計畫教會我如何建立索引?

2021-07-10 18:06:49 字數 3595 閱讀 5490

因為對索引不是很熟悉,所以測試得到結果沒有任何價值,甚至有些誤導人,這邊說聲抱歉,在哪跌倒在哪爬起來。

應用場景

還是用商品表(product)作為示例,表結構如下:

存在這樣一種業務場景:獲取某個**商(providerid),狀態為已售(state 為 1)的商品列表,排序方式為生產日期(producetime)降序,有可能我們應用程式在顯示資料的時候用到分頁,這邊我們查詢前 100 行。翻譯為 sql **:

select

top 100  

[id], 

[name], 

[remarks], 

[providerid], 

[producetime], 

[state] 

from [testdb].[dbo].[product] 

where [providerid]=1 and [state]=1 

order

by [producetime] desc

上面這個業務場景,在我們一般的應用程式中基本上都會遇到,有時候資料量不是很大的時候,我們一般不會做任何資料庫優化,但是你看了下面的實踐,你是否應該考慮下,為你現在的資料庫加個索引呢?

sql server 執行計畫

sql server 執行計畫,是我們分析 sql 執**況的一大利器,通過它,我們也可以很方面的檢視索引的執行,在實踐之前,需要了解一些必備技能,以下知識點摘自-看懂 sqlserver 查詢計畫。

sql server 有二種索引:聚集索引和非聚集索引。二者的差別在於:【聚集索引】直接決定了記錄的存放位置, 或者說:根據聚集索引可以直接獲取到記錄。【非聚集索引】儲存了二個資訊:1.相應索引欄位的值,2.記錄對應聚集索引的位置(如果表沒有聚集索引則儲存記錄指標)。 因此,如果能通過【聚集索引】來查詢記錄,顯然也是最快的。

sql server 會有以下方法來查詢您需要的資料記錄:

【table scan】:遍歷整個表,查詢所有匹配的記錄行。這個操作將會一行一行的檢查,當然,效率也是最差的。

【index scan】:根據索引,從表中過濾出來一部分記錄,再查詢所有匹配的記錄行,顯然比第一種方式的查詢範圍要小,因此比【table scan】要快。

【index seek】:根據索引,定位(獲取)記錄的存放位置,然後取得記錄,因此,比起前二種方式會更快。

【clustered index scan】:和【table scan】一樣。注意:不要以為這裡有個index,就認為不一樣了。 其實它的意思是說:按聚集索引來逐行掃瞄每一行記錄,因為記錄就是按聚集索引來順序存放的。 而【table scan】只是說:要掃瞄的表沒有聚集索引而已,因此這二個操作本質上也是一樣的。

【clustered index seek】:直接根據聚集索引獲取記錄,最快!

所以,當發現某個查詢比較慢時,可以首先檢查哪些操作的成本比較高,再看看那些操作在查詢記錄時, 是不是【table scan】或者【clustered index scan】,如果確實和這二種操作型別有關,則要考慮增加索引來解決了。 不過,增加索引後,也會影響資料表的修改動作,因為修改資料表時,要更新相應欄位的索引。所以索引過多,也會影響效能。 還有一種情況是不適合增加索引的:某個欄位用0或1表示的狀態。例如可能有絕大多數是1,那麼此時加索引根本就沒有意義。 這時只能考慮為0或者1這二種情況分開來儲存了,分表或者分割槽都是不錯的選擇。

應用分析

我們先不建任何索引(除了主鍵 id 的聚集索引),來看一下上面 sql **,在 sql server 執行計畫中的執**況:

可以看到,查詢開銷基本上被 sort 霸佔了,看到這種情況,按照正常的思維,我們首先考慮的是為 producetime 建立乙個非聚集索引,然後按照 desc 排序,但有時候我們要沉下心思考一下,是不是用 id 排序會更好呢?因為在 product 表中,id 為自增欄位,producetime 在新增的時候獲取的是當前時間,在 sql 排序中,其實 id 和 producetime 的排序效果是一樣的,但是執行效能方面確實天壤之別,我們看一下執行計畫就知道了:

從上面的執行計畫中,我們可以很直觀的看出差別,所以在寫 sql 的時候,一定要慎重啊,這邊為了方便展示,我們還是以 producetime 字段進行排序,按照 id 排序,雖然沒有了 sort 效能開銷,但是發現查詢記錄為「clustered index scan」,這是全表查詢的意思,我們理想的應該是「index seek」或者「clustered index seek」,因為這種是按照索引查詢,速度最快。按照我們程式設計師的理解,應該建立乙個非聚集索引,比如下面 ix_product_provider_state 索引:

建立好之後,我們再來執行一下 sql **:

「key lookup(clustered)」記錄,其實還是全表進行查詢,預設通過聚集索引(pk_product),我們可能會有疑問,索引就是按照查詢及排序方式建立的啊,為什麼還是這種情況?這時候我們看一下 select 後面的字段就知道了,我們查詢顯示的是 product 表中所有字段,但是 ix_product_provider_state 非聚集索引,只是針對的查詢條件字段,並沒有吧查詢顯示字段包含進來,在建立索引視窗中,「索引鍵 列」 tab 的旁邊有個「包含性 列」,我們把其他顯示字段加進來,看下執行效果:

「index seek」,這就是我們想要的效果,其實關於索引的建立有很多的現實問題,比如組合字段索引和單個字段索引有何不同?就像上面示例中的查詢用例,如果 producetime 排序在其他查詢條件中也存在,是不是應該拉出來建立乙個索引?還是像上面一樣,和查詢條件一起建立乙個組合字段索引?還有一種情況就是,在乙個應用程式查詢中,存在單個欄位的查詢,也存在組合欄位的查詢,那這時候我們是建立單個字段索引?還是建立組合字段索引呢?這幾個問題,你建立一下索引,然後用「 sql 執行計畫」試試就知道了。

總結

針對上面的查詢用例,我個人覺得,最好的方案是:排序字段使用 id,按照實際應用場景,提取出需要查詢的字段,避免 select *,這樣會減少在新增「包含性 列」的字段,建立 ix_product_provider_state 非聚集索引,索引欄位為:providerid 和 state,如果 state 的值不是多變的(比如值為 1 和 0),盡量不要建立 state 欄位的非聚集索引。

做完這些,你會發現,你的應用程式像飛的一樣。

SQL Server 執行計畫

預讀 邏輯讀 物理讀的解釋 預讀 sql server查詢的時候會在記憶體中生成查詢計畫,但在同時會去硬碟上取估計的資料放入快取 邏輯讀 從快取中讀取資料 物理讀 當快取中也沒有的時候,就回去硬碟讀 檢視語句執 況 set statistics profile on set statistics i...

SQL Server 執行計畫

預讀 邏輯讀 物理讀的解釋 預讀 sql server查詢的時候會在記憶體中生成查詢計畫,但在同時會去硬碟上取估計的資料放入快取 邏輯讀 從快取中讀取資料 物理讀 當快取中也沒有的時候,就回去硬碟讀 檢視語句執 況 set statistics profile on set statistics i...

Sql Server 執行計畫

1.每次執行sql語句都會生成執行計畫並快取起來,因為生成執行計畫也需要時間開銷,因此重用執行計畫將能提高效能,並節省緩衝區空間。我們可以使用sys.dm exec cached plans sys.dm exec sql text sys.dm exec query plan來查詢快取的執行計畫。...