1、索引概述html
一、概念jquery
能夠把索引理解爲一種特殊的目錄。就比如《新華字典》爲了加快查找的速度,提供了幾套目錄,分別按拼音、偏旁部首、難檢字等排序,這樣咱們就能夠方便地找到須要的字。數據庫
與書中的索引同樣,數據庫中的索引使您能夠快速找到表或索引視圖中的特定信息。索引包含從表或視圖中一個或多個列生成的鍵,以及映射到指定數據的存儲位置的指針。經過建立設計良好的索引以支持查詢,能夠顯著提升數據庫查詢和應用程序的性能。索引能夠減小爲返回查詢結果集而必須讀取的數據量。索引還能夠強制表中的行具備惟一性,從而確保表數據的數據完整性。ide
2. 分類函數
SQL SERVER提供了兩種索引:彙集索引(Clustered Index)和非彙集索引(Nonclustered Index)。 性能
3. 索引B樹ui
索引是按照B樹結構組織的。以下圖。spa
在上圖中,底部是葉級(leaf level),用於保存指向數據行的指針;非葉級用於導航到下一個非葉級或者葉級,非葉級包括2部分,即頂部的根(root)和中間部分的中間級(intermediate level)。設計
假設數據頁每頁能夠保存2條記錄,索引的平均寬度爲20字節,則索引的每一個頁(8KB)能夠保存約400個行指針,理論上(排除碎片等因素)上圖所示的4級樹可以搜索的記錄行能夠達到:2*400*400*400=1.28億。這表示查詢時若是使用此索引,只須要4次I/O操做就能夠導航至對應的數據行。指針
2、堆的物理結構
SQL Server 的數據組織結構爲HOBT(堆或平衡樹),詳見《SQL Server 數據文件存儲結構》 http://jimshu.blog.51cto.com/3171847/987275。
若是數據以堆的方式組織,那麼數據行不按任何特殊的順序存儲,數據頁也沒有任何特殊的順序。 能夠經過掃描 IAM (索引分配映射)頁實現對堆的表掃描或串行讀操做來找到容納該堆的頁的區。
從上圖可見,數據頁之間沒有任何關係,徹底依賴IAM頁進行組織。對於一個查詢,須要首先查詢IAM頁,而後根據IAM頁提供的指針去遍歷對應的每一個區,而後返回這些區內符合查詢條件的頁。若是IAM頁損壞,則整張表的結構被破壞,數據基本上不能被修復。
3、堆上的索引
索引中的每一個索引行都包含非彙集鍵值和行定位符。此定位符指向堆中包含該鍵值的數據行。
索引行中的行定位器是指向行的指針。該指針由文件標識符 (ID)、頁碼和頁上的行數生成。整個指針稱爲行 ID (RID)。
從上表可見,這是一個完整的樹狀結構。索引的最頂層是根,根頁存放的指針指向中間級(下一級的非葉級索引)或者指向葉級。索引的最底層是葉級,只能有一個葉級,葉級頁存放的指針指向真實的數據行。
4、實驗[三A]:堆上的非彙集索引
1. 構建一個堆結構的表
建立一張沒有彙集索引的表,併爲這張表添加80000條記錄。
create table person1 (UserID int,pwd char(20),OtherInfo char(360),modifydate datetime) declare @i int set @i=0 while @i<80000 begin insert into person1 select cast(floor(rand()*100000) as int), cast(floor(rand()*100000) as varchar(20)), cast(floor(rand()*100000) as char(360)), GETDATE() set @i=@i+1 end |
2. 添加一個非彙集索引
爲這個堆結構的表建立一個非彙集索引。
CREATE NONCLUSTERED INDEX IX_person1_UserID ON person1 (UserID) |
3. 表和索引的頁面分配統計
使用DBCC命令查看錶和索引的頁面分配狀況。
DBCC SHOWCONTIG ('person1') WITH ALL_INDEXES |
顯示結果以下:
DBCC SHOWCONTIG 正在掃描 'person1' 表... 表: 'person1' (245575913);索引 ID: 0,數據庫 ID: 8 已執行 TABLE 級別的掃描。 - 掃描頁數................................: 4000 - 掃描區數..............................: 502 - 區切換次數..............................: 501 - 每一個區的平均頁數........................: 8.0 - 掃描密度 [最佳計數:實際計數].......: 99.60% [500:502] - 區掃描碎片 ..................: 1.79% - 每頁的平都可用字節數.....................: 76.0 - 平均頁密度(滿).....................: 99.06% DBCC SHOWCONTIG 正在掃描 'person1' 表... 表: 'person1' (245575913);索引 ID: 2,數據庫 ID: 8 已執行 LEAF 級別的掃描。 - 掃描頁數................................: 179 - 掃描區數..............................: 23 - 區切換次數..............................: 22 - 每一個區的平均頁數........................: 7.8 - 掃描密度 [最佳計數:實際計數].......: 100.00% [23:23] - 邏輯掃描碎片 ..................: 0.00% - 區掃描碎片 ..................: 4.35% - 每頁的平都可用字節數.....................: 51.3 - 平均頁密度(滿).....................: 99.37% DBCC 執行完畢。若是 DBCC 輸出了錯誤信息,請與系統管理員聯繫。 |
以上結果顯示,數據頁共有4000頁(即4,000*8=32,000KB),佔用502個區(即502*64=32,128KB);索引的葉級共有179頁(即179*8=1,432KB),佔用23個區(即23*64=1,72KB)。
4. 查看索引的級數
根據DBCC SHOWCONTIG的結果,咱們如今能夠結合DMV來查看該索引的每一級是如何分佈。
SELECT index_depth, index_level, record_count, page_count, min_record_size_in_bytes as 'MinLen', max_record_size_in_bytes as 'MaxLen', avg_record_size_in_bytes as 'AvgLen', convert(decimal(6,2),avg_page_space_used_in_percent) as 'PageDensity' FROM sys.dm_db_index_physical_stats (8, OBJECT_ID('person1'),2,NULL,'DETAILED') |
注意,sys.dm_db_index_physical_stats 函數的5個參數分別表示以下意義:
第1個參數爲該數據庫的 ID。在本例中,DBCC SHOWCONTIG 已經顯示了該數據庫的 ID=8。也能夠經過 DB_ID('DatabaseName') 相似方式取得。
第2個參數爲該表的 ID。能夠經過 OBJECT_ID('TableName') 相似方式取得。或者經過 select * from sys.objects where name='person' 查找到具體的OBJECT_ID 。
第3個參數爲該索引的 ID。在本例中,DBCC SHOWCONTIG 已經顯示了該索引的 ID=2。若是 ID=0,則指向數據頁(堆或彙集索引)。NULL表示須要得到全部的索引。
第4個參數表示分區號。NULL表示須要得到全部分區的信息。
第5個參數表示但願返回的信息級別。NULL表示不返回全部的信息。
結果以下表所示:
index _depth |
Index _level |
Record _count |
Page _count |
MinLen |
MaxLen |
AvgLen |
PageDensity |
2 |
0 |
80000 |
179 |
16 |
16 |
16 |
99.37 |
2 |
1 |
179 |
1 |
22 |
22 |
22 |
53.05 |
根據上表的數據,能夠看到該索引共有2層。level=0 是葉級,它有179個頁面,指向包含80000行數據的數據頁;level=1 是根頁,它只有1個頁面,指向葉級的179個索引頁。
葉級索引的行長度爲16字節,它包括:4 個字節對應於 int 列(UserID列),8 個字節對應於
根頁的行長度爲22字節,即在葉級的行長度加6字節,這6個字節對應下一級索引頁的指針。
5. 查看堆的分佈
查看索引 ID=0 的分佈,實際上就是查看堆的頁面分佈狀況。
SELECT index_depth, index_level, record_count, page_count, min_record_size_in_bytes as 'MinLen',max_record_size_in_bytes as 'MaxLen', avg_record_size_in_bytes as 'AvgLen', convert(decimal(6,2),avg_page_space_used_in_percent) as 'PageDensity' FROM sys.dm_db_index_physical_stats (8, OBJECT_ID('person1'),0,NULL,'DETAILED') |
結果以下:
index _depth |
Index _level |
Record _count |
Page _count |
MinLen |
MaxLen |
AvgLen |
PageDensity |
1 |
0 |
80000 |
4000 |
399 |
399 |
399 |
99.06 |
6. 總結
5、實驗[三B]:堆上的(惟1、非空值)非彙集索引
1. 構建一個堆結構的表
建立一張沒有彙集索引的表,併爲這張表添加80000條記錄。注意,與前一個實驗不一樣的是,UserID列是惟一且非空。
create table person2 (UserID int not null,pwd char(20),OtherInfo char(360),modifydate datetime) declare @i int set @i=0 while @i<80000 begin insert into person2 select @i, cast(floor(rand()*100000) as varchar(20)), cast(floor(rand()*100000) as char(360)), GETDATE() set @i=@i+1 end |
2. 添加一個非彙集索引
爲這個堆結構的表建立一個惟1、非彙集索引。
CREATE UNIQUE NONCLUSTERED INDEX IX_person2_UserID ON person2 (UserID) |
3. 查看索引的級數
使用DMV來查看全部的索引分佈。
SELECT index_depth, index_level, record_count, page_count, min_record_size_in_bytes as 'MinLen', max_record_size_in_bytes as 'MaxLen', avg_record_size_in_bytes as 'AvgLen', convert(decimal(6,2),avg_page_space_used_in_percent) as 'PageDensity' FROM sys.dm_db_index_physical_stats (8, OBJECT_ID('person2'),NULL,NULL,'DETAILED') |
結果以下表所示:
index _depth |
Index _level |
Record _count |
Page _count |
MinLen |
MaxLen |
AvgLen |
PageDensity |
1 |
0 |
80000 |
4000 |
399 |
399 |
399 |
99.06 |
2 |
0 |
80000 |
179 |
16 |
16 |
16 |
99.37 |
2 |
1 |
179 |
1 |
11 |
11 |
11 |
53.05 |
根據上表的數據,能夠看到該彙集索引共有2層(僅指index_depth=2的索引)。葉級索引的行長度爲16字節不變。但根頁的行長度從22字節降爲11字節。
6、刪除堆中的數據
1. 使用delete刪除全部數據
delete person2 |
2. 查看錶的空間
使用DBCC SHOWCONTIG查看該表
DBCC SHOWCONTIG ('person2') WITH ALL_INDEXES |
發現仍然佔用着數據頁(「掃描頁數」>0)。
DBCC SHOWCONTIG 正在掃描 'person2' 表... 表: 'person2' (261575970);索引 ID: 0,數據庫 ID: 8 已執行 TABLE 級別的掃描。 - 掃描頁數................................: 296 - 掃描區數..............................: 39 - 區切換次數..............................: 38 - 每一個區的平均頁數........................: 7.6 - 掃描密度 [最佳計數:實際計數].......: 94.87% [37:39] - 區掃描碎片 ..................: 7.69% - 每頁的平都可用字節數.....................: 8056.0 - 平均頁密度(滿).....................: 0.47% DBCC SHOWCONTIG 正在掃描 'person2' 表... 表: 'person2' (261575970);索引 ID: 2,數據庫 ID: 8 已執行 LEAF 級別的掃描。 - 掃描頁數................................: 1 - 掃描區數..............................: 1 - 區切換次數..............................: 0 - 每一個區的平均頁數........................: 1.0 - 掃描密度 [最佳計數:實際計數].......: 100.00% [1:1] - 邏輯掃描碎片 ..................: 0.00% - 區掃描碎片 ..................: 0.00% - 每頁的平都可用字節數.....................: 8078.0 - 平均頁密度(滿).....................: 0.20% DBCC 執行完畢。若是 DBCC 輸出了錯誤信息,請與系統管理員聯繫。 |
3. 使用表鎖刪除數據
往該表中添加 1 條記錄,而後再使用如下命令刪除。
insert person2 values (1,'abc','abc',GETDATE()) delete person2 with (TABLOCK) |
再次使用DBCC SHOWCONTIG查看該表,能夠看到佔用的頁數(「掃描頁數」)減小了1頁,即已經釋放了1個頁面。
4. 收縮表的空間
使用如下命令將該數據庫的數據文件進行收縮。
DBCC SHRINKFILE (N'db01' , 0, TRUNCATEONLY) |
再次使用DBCC SHOWCONTIG查看該表,發現仍然佔用的頁數爲零(「掃描頁數」=0),即已經釋放了全部的空間。
5. 總結
SQL Server 默認在 delete 時不會加上表鎖(即TABLOCK),此時堆結構的表不會釋放空間給其它數據複用。
在刪除堆中的行數據時加上TABLOCK,或者手動執行SHRINKFILE (或SHRINKDB)才能釋放堆中的空閒頁面。