寫有效率的SQL查詢(2)轉

2022-01-13 14:22:15 字數 4124 閱讀 6205

寫有效率的sql查詢(ii)

上回我們說到評估一條語句執行效率主要看邏輯

io(啥是邏輯

io,啥是物理

io見聯機文件),這次我們繼續。

我們先說說,返回多行結果時,為什麼

sqlserver

有時會選擇

index seek

,有時會選擇

index scan。以

nonclustered index

為例說明。

像所有的索引

b樹一樣,非聚集索引樹也包括完全由索引資料組成的根節點和中間級節點;但是和聚集索引樹不同的是,聚集索引樹葉節點包含的是基礎表的資料頁(我們常說,表的物理儲存順序和聚集索引相同,就是這個原因),非聚集索引樹葉節點是索引頁。

sqlserver

通過非聚集索引查詢資料時,會通過這個非聚集索引鍵值去搜尋聚集索引,進而檢索基礎表資料行。

假設有這樣一張表,非聚集索引樹深度為

2,一層根節點(

1個索引頁),一層葉節點(

4個索引頁)。聚集索引樹深度為

3,一層根節點(

1個索引頁),一層中間級節點(

2個索引頁),一層葉節點(

250頁,也就是基礎表物理儲存頁)表的資料假設

1w行。注:所有資料均為假設,只為說明原理。

我們首先,再強調一遍,

sqlserver

獲取資料,總是以頁為單位,就算是只讀取一行也會獲取整張頁

(見《寫有效率的

sql查詢(

i)》)

現在有一條簡單查詢(如:

select * from tb where col2 = 99

,col2是tb

表中的非聚集索引

),假設會返回

100行。

ok,我們來分析如果以

index seek

來查詢這

100行會有多少io。

index seek

每次都從索引樹根節點開始查詢,找到中間級節點(

99對應的索引行),然後從該節點行開始連續遍歷所有

col2為99

的索引行。在遍歷這些行時,每拿到一條,都會通過該條索引行中聚集索引鍵值去聚集索引樹中

index seek

,然後從資料頁中獲取資料。在最壞的情況下,

col2為99

對應的索引行跨越了全部

4個葉級非聚集索引頁(當然,這沒啥可能性,舉例而已,切勿深究);每次通過聚集索引樹進行

index seek,io

開銷最壞情況下是乙個根節點,乙個中間級節點,乙個資料頁

,一共要

seek100

次,開銷

300個邏輯

io。綜上,通過

nonclustered index seek

總共開銷是

305個io。

要知道,我們的基礎表資料頁一共才

250頁,這說明了啥?說明就算是我從頭到尾掃瞄一遍表也比

noncustered index seek

快。這時,

sql2k5

會產生乙個完完全全的

clustered index scan

執行計畫來搞定表掃瞄。

好了,現在我們再來分析

select * from tb1 where col2 = 1

。假設它的結果集為

5行。如果這時還是進行

nonclustered index seek

的話,邏輯

io按照上面相似的分析,應該是19個

io,遠遠要小於整個的

clustered index scan

。這時,

sqlserver

自然會採用

nonclustered index seek

。我們再來看聚集索引。聚集索引和非聚集索引最大的不同在於聚集索引的儲存順序就是基礎表的物理儲存順序。還是上面的表

tb,假設聚集索引建在了

col1上.

如果where

條件是col1 = xx

的話,自然是

index seek

,因為io

最小,撐死了只有

3(乙個聚集索引根節點頁,乙個聚集索引中間級節點頁,乙個資料頁);如果

where

條件是col1 > xx

的話,不管行集是多大,

sqlserver

總是首先通過

index seek

拿到xx

對應的資料頁,然後挨梆往後遍歷基礎表資料頁到尾巴就

ok了。最壞情況

xx恰好比表中最小的

col1

小,那就讀取所有行。如果

where

條件是col1 < xx

,那就倒著檢索聚集索引,無他。

ok,到這裡,我們明白了為啥

sqlserver

會選擇index seek

和index scan

。也順便明白了通過非聚集索引查詢時,結果集相對總行數多寡對查詢計畫選擇的巨大影響。

(結果集

/總行數)被稱為選擇性,比值越大,選擇性就越高。

你得到了它,本文的重點就是選擇性。

統計資訊,說白了,就是表中某個欄位取某個值時有多少行結果集。統計資訊可以說是一種選擇性的度量,

sqlserver

就是根據它來估算不同查詢計畫的優劣。

後面將通過乙個實際的例子來說明統計資訊對查詢計畫的影響。

以下是示例表的表結構:

各位可以注意到,該錶上有乙個

identity

欄位charge_no

,聚集索引就建立在它上面。有兩個非聚集索引

indx_category_no

,indx_provider_no

,我們重點關注

indx_provider_no

。現在來看看

provider_no

欄位的統計資訊(有點長,我前邊粘一部分,後邊粘一部分):

(上述各欄位含義,見聯機文件對

dbcc show_statistics

的描述)

從上面的貼圖可以看到,表中總行數為

1w,取樣行數為1w。

provider_no

值為21

的只有1

行,而值為

500的行則有

4824

行。下面兩張圖是兩條

sql的查詢計畫,我就不多嘴解釋了。

那麼問題來了:

我們知道,

sqlserver

會快取查詢計畫,假如有這麼乙個儲存過程:

create

proc myproc

(@pno int)as

select

*from charge where provider_no = @pno

第一次我們傳進來乙個21,

ok,它會快取該儲存過程的執行計畫為

nonclustered index seek

那個。後來我們又傳進來乙個

500,完蛋了,伺服器發現它有乙個

myproc

的快取,

so,又通過

nonclustered index seek

執行,接著你的同夥看到你的查詢花費了巨量的

io,於是,你被鄙視了。

這說明了啥?說明如果你的查詢選擇性變動劇烈,你應該告訴

sqlserver

不要快取查詢計畫,每次都應該重新評估、編譯。實現方法很簡單,查詢的尾巴上加乙個

option

(recompile

)好了。而且

sql2k5

還有乙個nb的

feature

,可以每次只重新編譯儲存過程的一部分(當然,你也可以選擇重新編譯整個儲存過程,這取決於你的需求。詳見聯機文件。)

*****==

彪悍的分割線

******************************==

後面blog

會提到索引優化。其實百敬同學那本《

sql效能調校》這方面講的不少了。那本書唯一的缺憾就是某些規則在

sql2k5

中不適合。我想我會盡力都寫出來。

寫有效率的SQL查詢(I)

1.1where條件的列上都得有統計資訊。沒統計資訊sqlserver就無法估算不同查詢計畫開銷優劣,而只能採用最穩妥的scan 不管是table scan還是clustered index scan 一般情況下我們不會犯這種錯誤 where條件裡不使用非索引列是個常識。索引上的統計資訊是無法刪除的...

寫有效率的SQL查詢(I)

io 至於為什麼,回頭補一篇 我們常說,要建彪悍的索引 要寫高效的 sql 其實最終目的就是在相同結果集情況下,盡可能減少邏輯io。沒統計資訊 sqlserver 就無法估算不同查詢計畫開銷優劣,而只能採用最穩妥的 scan 不管是 table scan 還是clustered index scan...

寫有效率的SQL查詢(I)

沒統計資訊sqlserver就無法估算不同查詢計畫開銷優劣,而只能採用最穩妥的scan 不管是table scan還是clustered index scan 一般情況下我們不會犯這種錯誤 where條件裡不使用非索引列是個常識。索引上的統計資訊是無法刪除的。這條規則被廣為傳頌,原因據聯機文件和百敬...