北京北大青鳥(niǎo)學(xué)術(shù)部提供:SQL SERVER 建立索引

北京北大青鳥(niǎo)學(xué)術(shù)部提供:
假設(shè)你想找到本書(shū)中的某一個(gè)句子。你可以一頁(yè)一頁(yè)地逐頁(yè)搜索,但這會(huì)花很時(shí)間。而通過(guò)使用本書(shū)的索引,你可以很快地找到你要搜索的主題。表的索引與附在一本書(shū)后面的索引非常相似。它可以極大地提高查詢的速度。對(duì)一個(gè)較大的表來(lái)說(shuō),通過(guò)加索引,一個(gè)通常要花費(fèi)幾個(gè)小時(shí)來(lái)完成的查詢只要幾分鐘就可以完成。

因此沒(méi)有理由對(duì)需要頻繁查詢的表增加索引。
注意:
當(dāng)你的內(nèi)存容量或硬盤(pán)空間不足時(shí),也許你不想給一個(gè)表增加索引。對(duì)于包含索引的數(shù)據(jù)庫(kù),SQL Sever 需要一個(gè)可觀的額外空間。例如,要建立一個(gè)聚簇索引,需要大約1.2倍于數(shù)據(jù)大小的空間。要看一看一個(gè)表的索引在數(shù)據(jù)庫(kù)中所占的空間大小,你可以使用系統(tǒng)存儲(chǔ)過(guò)程sp_spaceused,對(duì)象名指定為被索引的表名。

聚簇索引和非聚簇索引(北京北大青鳥(niǎo))
假設(shè)你已經(jīng)通過(guò)本書(shū)的索引找到了一個(gè)句子所在的頁(yè)碼。一旦已經(jīng)知道了頁(yè)碼后,你很可能漫無(wú)目的翻尋這本書(shū),直至找到正確的頁(yè)碼。通過(guò)隨機(jī)的翻尋,你最終可以到達(dá)正確的頁(yè)碼。但是,有一種找到頁(yè)碼的更有效的方法。

首先,把書(shū)翻到大概一半的地方,如果要找的頁(yè)碼比半本書(shū)處的頁(yè)碼小,就書(shū)翻到四分之一處,否則,就把書(shū)翻到四分之三的地方。通過(guò)這種方法,你可以繼續(xù)把書(shū)分成更小的部分,直至找到正確的頁(yè)碼附近。這是找到書(shū)頁(yè)的非常有效的一種方法。
SQL Sever 的表索引以類似的方式工作。一個(gè)表索引由一組頁(yè)組成,這些頁(yè)構(gòu)成了一個(gè)樹(shù)形結(jié)構(gòu)。根頁(yè)通過(guò)指向另外兩個(gè)頁(yè),把一個(gè)表的記錄從邏輯上分成和兩個(gè)部分。而根頁(yè)所指向的兩個(gè)頁(yè)又分別把記錄分割成更小的部分。每個(gè)頁(yè)都把記錄分成更小的分割,直至到達(dá)葉級(jí)頁(yè)。

索引有兩種類型:聚簇索引和非聚簇索引。在聚簇索引中,索引樹(shù)的葉級(jí)頁(yè)包含實(shí)際的數(shù)據(jù):記錄的索引順序與物理順序相同。在非聚簇索引中,葉級(jí)頁(yè)指向表中的記錄:記錄的物理順序與邏輯順序沒(méi)有必然的聯(lián)系。

聚簇索引非常象目錄表,目錄表的順序與實(shí)際的頁(yè)碼順序是一致的。非聚簇索引則更象書(shū)的標(biāo)準(zhǔn)索引表,索引表中的順序通常與實(shí)際的頁(yè)碼順序是不一致的。一本書(shū)也許有多個(gè)索引。例如,它也許同時(shí)有主題索引和作者索引。同樣,一個(gè)表可以有多個(gè)非聚簇索引。通常情況下,你使用的是聚簇索引,但是你應(yīng)該對(duì)兩種類型索引的優(yōu)缺點(diǎn)都有所理解。每個(gè)表只能有一個(gè)聚簇索引,因?yàn)橐粋(gè)表中的記錄只能以一種物理順序存放。通常你要對(duì)一個(gè)表按照標(biāo)識(shí)字段建立聚簇索引。但是,你也可以對(duì)其它類型的字段建立聚簇索引,如字符型,數(shù)值型和日期時(shí)間型字段。從建立了聚簇索引的表中取出數(shù)據(jù)要比建立了非聚簇索引的表快。當(dāng)你需要取出一定范圍內(nèi)的數(shù)據(jù)時(shí),用聚簇索引也比用非聚簇索引好。例如,假設(shè)你用一個(gè)表來(lái)記錄訪問(wèn)者在你網(wǎng)點(diǎn)上的活動(dòng)。如果你想取出在一定時(shí)間段內(nèi)的登錄信息,你應(yīng)該對(duì)這個(gè)表的DATETIME 型字段建立聚簇索引。

對(duì)聚簇索引的主要限制是每個(gè)表只能建立一個(gè)聚簇索引。但是,一個(gè)表可以有不止一個(gè)非聚簇索引。實(shí)際上,對(duì)每個(gè)表你最多可以建立249個(gè)非聚簇索引。你也可以對(duì)一個(gè)表同時(shí)建立聚簇索引和非聚簇索引。(北京北大青鳥(niǎo))

假如你不僅想根據(jù)日期,而且想根據(jù)用戶名從你的網(wǎng)點(diǎn)活動(dòng)日志中取數(shù)據(jù)。在這種情況下,同時(shí)建立一個(gè)聚簇索引和非聚簇索引是有效的。你可以對(duì)日期時(shí)間字段建立聚簇索引,對(duì)用戶名字段建立非聚簇索引。如果你發(fā)現(xiàn)你需要更多的索引方式,你可以增加更多的非聚簇索引。

非聚簇索引需要大量的硬盤(pán)空間和內(nèi)存。另外,雖然非聚簇索引可以提高從表中 取數(shù)據(jù)的速度,它也會(huì)降低向表中插入和更新數(shù)據(jù)的速度。每當(dāng)你改變了一個(gè)建立了非聚簇索引的表中的數(shù)據(jù)時(shí),必須同時(shí)更新索引。因此你對(duì)一個(gè)表建立非聚簇索引時(shí)要慎重考慮。如果你預(yù)計(jì)一個(gè)表需要頻繁地更新數(shù)據(jù),那么不要對(duì)它建立太多非聚簇索引。另外,如果硬盤(pán)和內(nèi)存空間有限,也應(yīng)該限制使用非聚簇索引的數(shù)量。

索引屬性
這兩種類型的索引都有兩個(gè)重要屬性:你可以用兩者中任一種類型同時(shí)對(duì)多個(gè)字段建立索引(復(fù)合索引);兩種類型的索引都可以指定為唯一索引。
你可以對(duì)多個(gè)字段建立一個(gè)復(fù)合索引,甚至是復(fù)合的聚簇索引。假如有一個(gè)表記錄了你的網(wǎng)點(diǎn)訪問(wèn)者的姓和名字。如果你希望根據(jù)完整姓名從表中取數(shù)據(jù),你需要建立一個(gè)同時(shí)對(duì)姓字段和名字字段進(jìn)行的索引。這和分別對(duì)兩個(gè)字段建立單獨(dú)的索引是不同的。當(dāng)你希望同時(shí)對(duì)不止一個(gè)字段進(jìn)行查詢時(shí),你應(yīng)該建立一個(gè)對(duì)多個(gè)字段的索引。如果你希望對(duì)各個(gè)字段進(jìn)行分別查詢,你應(yīng)該對(duì)各字段建立獨(dú)立的索引。(北京北大青鳥(niǎo))

兩種類型的索引都可以被指定為唯一索引。如果對(duì)一個(gè)字段建立了唯一索引,你將不能向這個(gè)字段輸入重復(fù)的值。一個(gè)標(biāo)識(shí)字段會(huì)自動(dòng)成為唯一值字段,但你也可以對(duì)其它類型的
字段建立唯一索引。假設(shè)你用一個(gè)表來(lái)保存你的網(wǎng)點(diǎn)的用戶密碼,你當(dāng)然不希望兩個(gè)用戶有相同的密碼。通過(guò)強(qiáng)制一個(gè)字段成為唯一值字段,你可以防止這種情況的發(fā)生。

用SQL建立索引
為了給一個(gè)表建立索引,啟動(dòng)任務(wù)欄SQL Sever 程序組中的ISQL/w 程序。進(jìn)入查詢窗
口后,輸入下面的語(yǔ)句:
CREATE INDEX mycolumn_index ON mytable (myclumn)
這個(gè)語(yǔ)句建立了一個(gè)名為mycolumn_index 的索引。你可以給一個(gè)索引起任何名字,但你應(yīng)該在索引名中包含所索引的字段名,這對(duì)你將來(lái)弄清楚建立該索引的意圖是有幫助的。
注意:
在本書(shū)中你執(zhí)行任何SQL語(yǔ)句,都會(huì)收到如下的信息:
This command did not return data,and it did not return any rows
這說(shuō)明該語(yǔ)句執(zhí)行成功了。
索引mycolumn_index對(duì)表mytable 的mycolumn字段進(jìn)行。這是個(gè)非聚簇索引,也是個(gè)非唯一索引。(這是一個(gè)索引的缺省屬性)如果你需要改變一個(gè)索引的類型,你必須刪除原來(lái)的索引并重建 一個(gè)。建立了一個(gè)索
引后,你可以用下面的SQL 語(yǔ)句刪除它:
DROP INDEX mytable.mycolumn_index
注意在DROP INDEX 語(yǔ)句中你要包含表的名字。在這個(gè)例子中,你刪除的索引是
mycolumn_index,它是表mytable 的索引。
要建立一個(gè)聚簇索引,可以使用關(guān)鍵字CLUSTERED。)記住一個(gè)表只能有一個(gè)聚簇索引。
(這里有一個(gè)如何對(duì)一個(gè)表建立聚簇索引的例子:(北京北大青鳥(niǎo))

CREATE CLUSTERED INDEX mycolumn_clust_index ON mytable(mycolumn)
如果表中有重復(fù)的記錄,當(dāng)你試圖用這個(gè)語(yǔ)句建立索引時(shí),會(huì)出現(xiàn)錯(cuò)誤。但是有重復(fù)記錄的表也可以建立索引;你只要使用關(guān)鍵字ALLOW_DUP_ROW把這一點(diǎn)告訴SQL Sever 即可:
CREATE CLUSTERED INDEX mycolumn_cindex ON mytable(mycolumn)
WITH ALLOW_DUP_ROW
這個(gè)語(yǔ)句建立了一個(gè)允許重復(fù)記錄的聚簇索引。你應(yīng)該盡量避免在一個(gè)表中出現(xiàn)重復(fù)記錄,但是,如果已經(jīng)出現(xiàn)了,你可以使用這種方法。
要對(duì)一個(gè)表建立唯一索引,可以使用關(guān)鍵字UNIQUE。對(duì)聚簇索引和非聚簇索引都可以使用這個(gè)關(guān)鍵字。這里有一個(gè)例子:
CREATE UNIQUE COUSTERED INDEX myclumn_cindex ON mytable(mycolumn)
這是你將經(jīng)常使用的索引建立語(yǔ)句。無(wú)論何時(shí),只要可以,你應(yīng)該盡量對(duì)一個(gè)對(duì)一個(gè)表建立唯一聚簇索引來(lái)增強(qiáng)查詢操作。最后,要建立一個(gè)對(duì)多個(gè)字段的索引──復(fù)合索引──在索引建立語(yǔ)句中同時(shí)包含多個(gè)字段名。下面的例子對(duì)firstname 和lastname兩個(gè)字段建立索引:
CREATE INDEX name_index ON username(firstname,lastname)
這個(gè)例子對(duì)兩個(gè)字段建立了單個(gè)索引。在一個(gè)復(fù)合索引中,你最多可以對(duì)16 個(gè)字段進(jìn)
行索引。
一. 聚集索引B樹(shù)分析(北京北大青鳥(niǎo))

  1.聚集索引按B樹(shù)結(jié)構(gòu)進(jìn)行組織的,索引B樹(shù)種的每一頁(yè)稱為一個(gè)索引節(jié)點(diǎn)。B樹(shù)的頂端節(jié)點(diǎn)稱為根節(jié)點(diǎn)。
  索引中的低層節(jié)點(diǎn)稱為葉節(jié)點(diǎn)。根節(jié)點(diǎn)與葉節(jié)點(diǎn)之間的任何索引級(jí)別統(tǒng)稱為中間級(jí)。在聚集索引中,葉節(jié)點(diǎn)包含基礎(chǔ)表的數(shù)據(jù)頁(yè)。
  根節(jié)點(diǎn)和中間級(jí)節(jié)點(diǎn)包含存有索引行的索引頁(yè)。每個(gè)索引行包含一個(gè)鍵值和一個(gè)指針,該指針指向 B 樹(shù)上的某一中間級(jí)頁(yè)或葉級(jí)索引中的某個(gè)數(shù)據(jù)行.每級(jí)索引中的頁(yè)均被連接在雙向鏈接列表中。
  2.索引使用的每一個(gè)分區(qū)的index_id = 1 ,默認(rèn)情況下聚集索引單個(gè)分區(qū),當(dāng)使用分區(qū)表的時(shí)候,每個(gè)分區(qū)都有一個(gè)包含該特定分區(qū)相關(guān)數(shù)據(jù)的B樹(shù)結(jié)構(gòu),我是這么理解的不知道對(duì)不對(duì)?
  3.SQL Server 寫(xiě)入的數(shù)據(jù),數(shù)據(jù)鏈內(nèi)的頁(yè)和行將按聚集索引鍵值進(jìn)行排序。
  4.SQL Server 將在索引中查找該范圍的起始鍵值,然后用向前或向后在數(shù)據(jù)頁(yè)中進(jìn)行掃描。為了查找數(shù)據(jù)頁(yè)鏈的首頁(yè),SQL Server 將從索引的根節(jié)點(diǎn)沿最左邊的指針進(jìn)行掃描。
  聚集索引B樹(shù)圖 :

二 .優(yōu)化 Transact-SQL 語(yǔ)句經(jīng)常使用的語(yǔ)句 (北京北大青鳥(niǎo))

  1.SET STATISTICS IO {ON| OFF} /*Transact-SQL 語(yǔ)句生成的磁盤(pán)活動(dòng)量的信息*/
  2.SET SHOWPLAN_ALL ON {ON| OFF} /*返回有關(guān)語(yǔ)句執(zhí)行情況的詳細(xì)信息,并估計(jì)語(yǔ)句對(duì)資源的需求*/
  3.SET STATISTICS TIME {ON| OFF} /*顯示分析、編譯和執(zhí)行各語(yǔ)句所需的毫秒數(shù)*/
  4.使用T-SQL語(yǔ)句創(chuàng)建索引的語(yǔ)法:
CREATE [UNIQUE] [CLUSTERED|NONCLUSTERED]

  INDEX index_name

  ON table_name (column_name)

  [WITH FILLFACTOR=x]


  一. 聚集索引B樹(shù)分析
  1.聚集索引按B樹(shù)結(jié)構(gòu)進(jìn)行組織的,索引B樹(shù)種的每一頁(yè)稱為一個(gè)索引節(jié)點(diǎn)。B樹(shù)的頂端節(jié)點(diǎn)稱為根節(jié)點(diǎn)。
  索引中的低層節(jié)點(diǎn)稱為葉節(jié)點(diǎn)。根節(jié)點(diǎn)與葉節(jié)點(diǎn)之間的任何索引級(jí)別統(tǒng)稱為中間級(jí)。在聚集索引中,葉節(jié)點(diǎn)包含基礎(chǔ)表的數(shù)據(jù)頁(yè)。
  根節(jié)點(diǎn)和中間級(jí)節(jié)點(diǎn)包含存有索引行的索引頁(yè)。每個(gè)索引行包含一個(gè)鍵值和一個(gè)指針,該指針指向 B 樹(shù)上的某一中間級(jí)頁(yè)或葉級(jí)索引中的某個(gè)數(shù)據(jù)行.每級(jí)索引中的頁(yè)均被連接在雙向鏈接列表中。
  2.索引使用的每一個(gè)分區(qū)的index_id = 1 ,默認(rèn)情況下聚集索引單個(gè)分區(qū),當(dāng)使用分區(qū)表的時(shí)候,每個(gè)分區(qū)都有一個(gè)包含該特定分區(qū)相關(guān)數(shù)據(jù)的B樹(shù)結(jié)構(gòu),我是這么理解的不知道對(duì)不對(duì)?
  3.SQL Server 寫(xiě)入的數(shù)據(jù),數(shù)據(jù)鏈內(nèi)的頁(yè)和行將按聚集索引鍵值進(jìn)行排序。
  4.SQL Server 將在索引中查找該范圍的起始鍵值,然后用向前或向后在數(shù)據(jù)頁(yè)中進(jìn)行掃描。為了查找數(shù)據(jù)頁(yè)鏈的首頁(yè),SQL Server 將從索引的根節(jié)點(diǎn)沿最左邊的指針進(jìn)行掃描。(北京北大青鳥(niǎo))

  聚集索引B樹(shù)圖 :

  二 .優(yōu)化 Transact-SQL 語(yǔ)句經(jīng)常使用的語(yǔ)句
  1.SET STATISTICS IO {ON| OFF} /*Transact-SQL 語(yǔ)句生成的磁盤(pán)活動(dòng)量的信息*/
  2.SET SHOWPLAN_ALL ON {ON| OFF} /*返回有關(guān)語(yǔ)句執(zhí)行情況的詳細(xì)信息,并估計(jì)語(yǔ)句對(duì)資源的需求*/
  3.SET STATISTICS TIME {ON| OFF} /*顯示分析、編譯和執(zhí)行各語(yǔ)句所需的毫秒數(shù)*/
  4.使用T-SQL語(yǔ)句創(chuàng)建索引的語(yǔ)法:
CREATE [UNIQUE] [CLUSTERED|NONCLUSTERED]

  INDEX index_name

  ON table_name (column_name)

  [WITH FILLFACTOR=x]
 
三 創(chuàng)建數(shù)據(jù)測(cè)試下上面學(xué)到的理論知識(shí)(北京北大青鳥(niǎo))

--創(chuàng)建表

CREATE TABLE employee

(

emp_username varchar (20),

emp_register DATETIME

)

 

--插入測(cè)試數(shù)據(jù)

DECLARE @startid INT

DECLARE @endid INT

SELECT @startid= 1,@endid = 100

WHILE @startid <=@endid

BEGIN

INSERT INTO employee (

    emp_username,

    emp_register

) VALUES (

    /* emp_username - varchar (20) */ '劉'+CAST(@startid AS NVARCHAR(20)),

    /* emp_register - DATETIME */ GETDATE() )

SELECT @startid =@startid +1;

END

 

-- 查詢employee的執(zhí)行計(jì)劃 和 io  信息

SET STATISTICS IO ON

SELECT * FROM employee WHERE emp_username = '劉'

查看消息輸出的 IO 信息
  表'employee'。(1)1掃描計(jì)數(shù)1,(2)邏輯讀取1 次,(3)物理讀取0 次,(4)預(yù)讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預(yù)讀0 次。
  輸出的信息和上面的圖片講解的是對(duì)應(yīng)的
  1. 執(zhí)行的掃描次數(shù) 。
  2. 從磁盤(pán)讀取的頁(yè)數(shù)。
  3. 為進(jìn)行查詢而放入緩存的頁(yè)數(shù)。
  4. 預(yù)讀
  T_SQL transaction 語(yǔ)句有很多種的寫(xiě)法,但是決定那條語(yǔ)句是最優(yōu)的是根據(jù)(logical reads) 邏輯讀取來(lái)判斷。(北京北大青鳥(niǎo))

  添加聚集索引 查詢邏輯讀取是否會(huì)變少
CREATE CLUSTERED INDEX Idx_emp_username ON employee (emp_username);

--然后再執(zhí)行查詢

SET STATISTICS IO ON

SELECT * FROM employee WHERE emp_username = '劉'

查看消息輸出的 IO 信息
  表'employee'。掃描計(jì)數(shù)1,邏輯讀取2 次,物理讀取0 次,預(yù)讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預(yù)讀0 次。
  Q 這次邏輯讀取是2次為什么呢 ?
  A.難道查詢比表掃描還要慢,答案是對(duì)的,數(shù)據(jù)量小的時(shí)候,聚集索引的優(yōu)勢(shì)體現(xiàn)不出來(lái)。
  Q 為什么是2次邏輯讀取
  A 現(xiàn)在查詢的時(shí)候如聚集索引圖,先查詢索引頁(yè) ,查找到對(duì)應(yīng)的鍵值后,掃描數(shù)據(jù)頁(yè),如果有包含索引,直接在索引頁(yè)就可以提取到需要的數(shù)據(jù)。
  上面說(shuō)了小數(shù)據(jù)量的時(shí)候聚集索引體現(xiàn)不出效果,下面我們繼續(xù)填充數(shù)據(jù)測(cè)試 。
  填充測(cè)試數(shù)據(jù)到1000
  表掃描
  消息:
  表'employee'。掃描計(jì)數(shù)1,邏輯讀取36 次,物理讀取0 次,預(yù)讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預(yù)讀0 次。
  聚集索引掃描
  消息:
  表'employee'。掃描計(jì)數(shù)1,邏輯讀取2 次,物理讀取0 次,預(yù)讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預(yù)讀0 次。
  這個(gè)時(shí)候聚集索引的優(yōu)勢(shì)就先顯示出來(lái)了 O(∩_∩)O
  下面在來(lái)講講transaction sql 語(yǔ)句 ,大家在網(wǎng)上看到的一些人說(shuō) In like left 不使用索引 ,我們動(dòng)手來(lái)測(cè)試下看他們說(shuō)的對(duì)不對(duì) ?
  刪除employee表的索引
DROP INDEX employee.Idx_emp_username
 
  打開(kāi)IO信息(北京北大青鳥(niǎo))

SET STATISTICS IO ON

SELECT * FROM employee WHERE employee.emp_username in ('劉10000')

消息:
  表 'employee'。掃描計(jì)數(shù) 1,邏輯讀取 371 次,物理讀取 0 次,預(yù)讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預(yù)讀 0 次。
--添加Idx_emp_username聚集索引

CREATE CLUSTERED INDEX Idx_emp_username ON employee (emp_username);

SELECT * FROM employee WHERE employee.emp_username in ('劉10000');

消息:
  表 'employee'。掃描計(jì)數(shù) 1,邏輯讀取 3 次,物理讀取 0 次,預(yù)讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預(yù)讀 0 次。
  使用索引后邏輯讀取3次,沒(méi)有使用索引是371次,IN 很好的使用了索引!
  下面我們來(lái)測(cè)試下 LIKE 是否很好的使用索引
  刪除索引
DROP INDEX employee.Idx_emp_username
   打開(kāi)IO 信息
SET STATISTICS IO ON
   執(zhí)行查詢
SELECT * FROM employee WHERE  employee.emp_username like   ('劉1000%')
 
消息:
  表 'employee'。掃描計(jì)數(shù) 1,邏輯讀取 371 次,物理讀取 0 次,預(yù)讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預(yù)讀 0 次。
  添加索引
CREATE CLUSTERED INDEX Idx_emp_username ON employee (emp_username);
SET STATISTICS IO ON
SELECT * FROM employee WHERE employee.emp_username  like  ( '劉1000%');

   表 'employee'。掃描計(jì)數(shù) 1,邏輯讀取 3 次,物理讀取 0 次,預(yù)讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預(yù)讀 0 次。
  網(wǎng)上很多優(yōu)化的文章寫(xiě)到查詢不要使用 in like left ,其實(shí)自己動(dòng)手測(cè)試下看看查詢計(jì)劃就一幕了然了(北京北大青鳥(niǎo))

北大青鳥(niǎo)網(wǎng)上報(bào)名
北大青鳥(niǎo)招生簡(jiǎn)章