3、索引優化(1)堆上的非彙集索引

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 個字節對應於 數據行定位符(即堆 RID),1個字節對應於索引行的行標題開銷,3個字節用於NULL位圖(UserID列能夠爲空) 。詳細的計算方法,見《估計非彙集索引的大小》 http://technet.microsoft.com/zh-cn/library/ms190620.aspx

  根頁的行長度爲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)才能釋放堆中的空閒頁面。

相關文章
相關標籤/搜索