本文選自《Pro SQL Server Internals》html
做者: Dmitri Korotkevitch數據庫
出版社: Apress數組
出版年: 2016-12-29性能優化
做者簡介:Dmitri Korotkevitchis是微軟SQL Server MVP和微軟認證大師。做爲應用程序和數據庫開發人員、數據庫管理員和數據庫架構師,他具備多年使用SQL Server的經驗。他專門從事OLTP系統在高負載下的設計、開發和性能調優。Dmitri常常在各類Microsoft和SQL PASS活動上發言,他爲世界各地的客戶提供SQL Server培訓。架構
原文連接:http://www.doc88.com/p-4042504089228.html併發
聚簇索引聚簇索框架
引指示表中數據的物理順序,該表根據聚簇索引鍵進行排序。 該表只能定義一個聚簇索引。 假設您要在堆表上使用數據建立聚簇索引。 做爲第一步,如圖2-5所示,SQL Server會建立另外一個數據副本,而後根據羣集密鑰的值對其進行排序。 數據頁連接在雙鏈表中,其中每一個頁面都包含指向鏈中下一頁和上一頁的指針。 此列表稱爲索引的葉級,它包含實際的表數據。函數
第2章 表格和索引:內部結構和訪問方法性能
圖2-5 彙集的索引結構:葉級 HeapTable = 堆表 clustered index(leaf level actual data)=聚簇索引(精算數據)測試
■注意頁面上的排序順序由插槽陣列控制。 頁面上的實際數據未排序。
當葉級別由多個頁面組成時,SQL Server開始構建索引的中間級別,如圖2-6所示。
intermediate level 中間級; leaf level 葉級
圖2-6。 彙集的索引結構:中級和葉級
中間級別爲每一個葉級頁面存儲一行。 它存儲兩條信息:它引用的頁面中的索引鍵的物理地址和最小值。 惟一的例外是第一頁上的第一行,其中SQL Server存儲NULL而不是最小索引鍵值。 經過這種優化,當您在表中插入具備最低鍵值的行時,SQL Server不須要更新非葉級行。 中間級別的頁面也連接到雙鏈表。 SQL Server添加了愈來愈多的中間級別,直到只包含單個頁面的級別。 此級別稱爲根級別,它將成爲索引的入口點,如圖2-7所示。
第2章■表格和索引:內部結構和訪問方法
圖2-7。 聚簇索引結構:根級別
如您所見,索引始終具備一個葉級別,一個根級別和零個或多箇中間級別。惟一的例外是索引數據適合單個頁面。在這種狀況下,SQL Server不會建立單獨的根級頁面,索引只包含單個葉級頁面。索引中的級別數很大程度上取決於行和索引鍵的大小。例如,4字節整數列上的索引在中間和根級別上每行須要13個字節。這13個字節由一個2字節的插槽數組條目,一個4字節的索引鍵值,一個6字節的頁面指針和一個1字節的行開銷組成,這是足夠的,由於索引鍵不包含變量 - length和NULL列。所以,每行可容納8,060字節/ 13字節=每頁620行。這意味着,使用一箇中間級別,您能夠存儲最多620 * 620 = 384,400個葉級頁面的信息。若是數據行大小爲200字節,則每一個葉級頁面可存儲40行,索引中最多可存儲15,376,000行,只有三個級別。向索引添加另外一箇中間級別將基本上涵蓋全部可能的整數值。
注意 在實際生活中,索引碎片會減小這些數字。咱們將在第6章中討論索引碎片。
SQL Server能夠以三種不一樣的方式從索引讀取數據。第一個是經過有序掃描。假設咱們要運行SELECTNamefromdbo.Customers ORDER BY CustomerId查詢。索引的葉級數據已經基於CustomerId列值進行了排序。所以,SQL Server能夠掃描索引從第一頁到最後一頁的葉子級別,並按照存儲行的順序返回這些行。SQL Server從索引的根頁開始,而後從那裏讀取第一行。該行引用具備最小鍵值的中間頁從表中引用。SQLServer讀取該頁並重復該過程,直到找到葉級上的第一頁爲止。而後,SQLServer開始逐行讀取行,遍歷頁的鏈表,直到讀取全部行爲止。圖2-8說明了這一過程。
第2章表和索引:內部結構和訪問方法
圖2-8。有序索引掃描
前面查詢的執行計劃顯示了具備Ordered屬性設置爲true的集羣索引掃描操做符,如圖2-9所示。
圖2-9。有序索引掃描執行計劃
值得一提的是,觸發有序掃描不須要order by子句。有序掃描僅僅意味着SQL Server根據索引鍵的順序讀取數據。SQL Server能夠在兩個方向上(向前和向後)瀏覽索引。可是,您必須記住一個重要方面:SQL Server在反向索引掃描期間不使用並行性。
第2章 ■ 表和索引:內部結構和訪問方法
■提示 您能夠經過檢查執行計劃中的INDEX SCAN或INDEX SEEK操做符屬性來檢查掃描方向。可是,請記住,Management Studio不會在執行計劃的圖形表示中顯示這些屬性。您須要打開「屬性」窗口以經過在執行計劃中選擇運算符並選擇「查看/屬性」窗口菜單項或按F4鍵來查看該屬性窗口。
SQL Server的企業版有一個名爲merry-go-.scan的優化特性,容許多個任務共享相同的索引掃描。假設您有會話S1,它正在掃描索引。在掃描中間的某個時刻,另外一個會話S2運行須要掃描相同索引的查詢。經過旋轉木馬掃描,S2在其當前掃描位置鏈接S1。SQLServer只讀取每一個頁面一次,將行傳遞給兩個會話。當S1掃描到達索引的結束時,S2從索引的開始開始掃描數據,直到S2掃描開始的點。旋轉木馬掃描是另外一個示例,它說明了爲何您不能依賴於索引鍵的順序,以及爲何在須要時應該始終指定ORDER BY子句。排序掃描以後的下一個訪問方法稱爲分配順序掃描。S QL Server經過IAM頁面訪問表數據,相似於使用堆表的方式。SELECTNamefromdbo.Customers WITH(NOLOCK)查詢和圖2-10說明了這種方法。圖2-11顯示了查詢執行計劃。
圖2-10。分配順序掃描
第2章 ■ 表和索引:內部結構和訪問方法
圖2-11。分配順序掃描執行計劃
不幸的是,很難檢測SQL Server什麼時候使用分配順序掃描。儘管執行計劃中的Ordered屬性顯示爲false,但它代表SQL Server並不關心行是否按索引鍵的順序讀取,而不關心使用分配順序掃描。分配順序掃描能夠更快地掃描大表,儘管它有更高的啓動成本。
當表小時,SQLServer不使用此訪問方法。另外一個重要的考慮因素是數據一致性。SQL Server在具備集羣索引的表中不使用轉發指針,而且分配順序掃描可能產生不一致的結果。因爲分頁致使的數據移動,能夠屢次跳過或讀取行。所以,SQLServer一般避免使用分配順序掃描。除非它以READ UNCOMMITTED或SERIALIZABLE事務隔離級別讀取數據。
■注意 咱們將在第6章「索引分段」中討論分頁和分段,在第三部分「鎖定、阻塞和併發」中討論鎖定和數據一致性。
最後一個索引訪問方法稱爲索引查找。從dbo.Customers WHERE CustomerId BETWEEN 4和7查詢的SELECT Name以及圖2-12說明了該操做。
圖2-12。索引查找
第2章 ■ 表和索引:內部結構和訪問方法
爲了從表中讀取行的範圍,SQL Server須要從範圍中找到具備最小鍵值的行,即4。SQL Server從根頁面開始,其中第二行引用具備最小鍵值350的頁面。它大於咱們正在尋找的鍵值(4),而且SQL Server讀取由根頁面上的第一行引用的中間級數據頁(1:170)。
相似地,中間頁面將SQL Server引導到第一個葉級頁面(1:176)。SQLServer讀取而後,它讀取CustomerIds等於4和5的行,最後,它從第二頁讀取剩餘的兩行。
執行計劃如圖2-13所示。
圖2-13。索引查找執行計劃
能夠猜到,索引查找比索引掃描更有效,由於SQL Server只處理行和數據頁的子集,而不掃描整個表。
從技術上講,有兩種索引查找操做。第一個稱爲單例查找,有時稱爲點查找,其中SQL Server查找並返回一行。您能夠想到Cuffer-Id= 2謂詞在哪裏。另外一種類型的索引查找操做稱爲範圍掃描,它要求SQL Server查找鍵的最低值或最高值,並掃描(向前或向後)行集合,直到到達掃描範圍的末尾。客戶機ID介於4和7之間的謂詞致使範圍掃描。兩種狀況都顯示爲執行計劃中的索引查找操做。
正如您所猜到的,範圍掃描徹底可能迫使SQL Server處理索引中的大量數據頁甚至全部數據頁。例如,若是將查詢更改成使用WHERE CustomerId>0謂詞,SQL Server將讀取全部行/頁,儘管在執行計劃中將顯示Index Seek操做符。您必須牢記這種行爲,並在查詢性能優化期間始終分析範圍掃描的效率。
在關係數據庫中有一個稱爲SARGable謂詞的概念,它表明Search Argument able。若是SQL Server能夠利用索引查找操做(若是存在索引),則謂詞是SARGable。簡言之,當SQL Server能夠隔離要處理的索引鍵值的單個值或範圍時,謂詞就是SARGable,從而限制了謂詞評估期間的搜索。顯然,使用SARGable謂詞編寫查詢並儘量利用索引查找是有益的。
SARGable謂詞包括下列操做符:=、>、>=、<、<=、IN、BETWEEN和LIKE(在前綴匹配的狀況下)。非SARGable操做符包括NOT、<>、LIKE(在非前綴匹配的狀況下)和NOT IN。
使謂詞不可SARGable的另外一種狀況是對錶列使用函數或數學計算。SQL Server必須調用該函數或對其處理的每行執行計算。幸運的是,在某些狀況下,您能夠重構查詢以使得這樣的謂詞SARGable。表2-1顯示了一些例子。
第2章 ■ 表和索引:內部結構和訪問方法
你必須記住的另外一個重要因素是類型轉換。在某些狀況下,可使用不正確的數據類型使謂詞不可SARGable。讓咱們建立一個包含varchar列的表,並用一些數據填充它,如清單2-6所示。
清單2-6。SARG謂詞和數據類型:測試表建立
create table dbo.Data
(
VarcharKey varchar(10) not null,
Placeholder char(200)
);
create unique clustered index IDX_Data_VarcharKey
on dbo.Data(VarcharKey);
;with N1(C) as (select 0 union all select 0) -- 2 rows
,N2(C) as (select 0 from N1 as T1 cross join N1 as T2) -- 4 rows
,N3(C) as (select 0 from N2 as T1 cross join N2 as T2) -- 16 rows
,N4(C) as (select 0 from N3 as T1 cross join N3 as T2) -- 256 rows
,N5(C) as (select 0 from N4 as T1 cross join N4 as T2) -- 65,536 rows
,IDs(ID) as (select row_number() over (order by (select null)) from N5)
insert into dbo.Data(VarcharKey)
select convert(varchar(10),ID) from IDs;
集羣索引鍵列被定義爲varchar,即便它存儲整數值。如今,讓咱們運行兩個選擇,如清單2-7所示,並查看執行計劃。
第2章 ■ 表和索引:內部結構和訪問方法
清單2-7。SARG謂詞和數據類型:用整數參數選擇
declare
@IntParam int = '200'
select * from dbo.Data where VarcharKey = @IntParam;
Select * from dbo.Data where VarcharKey = convert(varchar(10),@IntParam);
如圖2-14所示,在整數參數的狀況下,SQL Server掃描集羣索引,將每行的varchar轉換爲整數。在第二種狀況下,SQL Server在開始時將整數參數轉換爲varchar,並利用更有效的集羣索引查找操做。
圖2-14。SARG謂詞和數據類型:整數參數的執行方案
■ 提示 注意鏈接謂詞中的列數據類型。隱式或顯式數據類型轉換能夠顯著下降查詢的性能。
在unicode字符串參數的狀況下,您將觀察到很是相似的行爲。讓咱們運行清單2-8所示的查詢。圖2-15顯示了語句的執行計劃。
清單2-8。SARG謂詞和數據類型:用字符串參數選擇
select * from dbo.Data where VarcharKey = '200';
select * from dbo.Data where VarcharKey = N'200'; -- unicode parameter
第2章■ 表和索引:內部結構和訪問方法
圖2-15。SARG謂詞和數據類型:具備字符串參數的執行計劃
正如您所看到的,Unicode字符串參數對於VARCHAR列是非SGARABLE。這是一個比看上去更大的問題。雖然不多以這種方式編寫查詢,如清單2-8所示,可是如今大多數應用程序開發環境將字符串視爲unicode。所以,SQL Server客戶端庫爲字符串對象生成unicode(nvarchar)參數,除非參數數據類型明確指定爲varchar。這就使得謂語不可理解,它能夠致使主語。即便對varchar列進行索引,因爲沒必要要的掃描也會致使性能降低。
Parameters.Add("@ParamName").Value = stringVariable overload. 在ORM框架中使用映射來顯式地指定類中的非unicode屬性。
還值得一提的是,對於nvarchar unicode數據列,varchar參數是SARGable。