1、基本概念html
1.數據的讀取數據庫
頁(page)是SQL SERVER能夠讀寫的最小I/O單位。即便只需訪問一行,也要把整個頁加載到緩存之中,再從緩存中讀取數據。物理讀取是從磁盤上讀取,邏輯讀取是從緩存中讀取。物理讀取一頁的開銷要比邏輯讀取一頁的要大得多。緩存
SET STATISTICS IO ON數據結構
--do something...oop
SET STATISTICS IO OFF性能
能夠用以上代碼來查看IO訪問狀況spa
2.表的組織方式指針
表有兩種組織方式,B樹(Balance Tree)或者堆(Heap)。當在表上建立了一個彙集索引的時候,整個表數據就以B樹的結構排列。不然就是按照堆的結構排列。不管表是怎麼組織的,均可以在表上面建立多個非彙集索引。非彙集索引都是以B樹的結構排列。htm
2.1 堆(Heap)blog
之因此這個結構稱爲堆,是由於它不以任何人爲指定的邏輯順序進行排列。而是按照分區組隊數據進行組織。也就是說,是按照磁盤的物理順序。只要須要讀取的數據文件沒有文件系統碎片(注意和下面提到的索引的碎片區分),這個讀取過程在磁盤中就能夠連續的進行,沒有多餘的磁盤臂移動。而磁盤臂移動是I/O操做中開銷最大的操做。
堆使用一個bitmap結構來管理數據的分配。也就是它會告訴你兩個結果,這個區是分配了,仍是沒有分配。每個區中的物理順序以下圖。
對於新插入的數據,堆只管在最後一條數據的後面的一個空閒位置保存新插入的數據,不保持任何的邏輯順序。好比拿order表舉例,若是先插入orderid 4,5,6, 假設在位置1:17六、 1:17七、1:178這三個位置。這時再插入1,這時保存的數據就變爲4,5,6,1, 1保存在 1:179的位置。
2.2彙集索引(Clustered Index)
該索引中鍵值的邏輯順序決定了表中相應行的物理順序。
彙集索引肯定表中數據的物理順序。彙集索引相似於電話簿,後者按姓氏排列數據。因爲彙集索引規定數據在表中的物理存儲順序,所以一個表只能包含一個彙集索引。但該索引能夠包含多個列(組合索引),就像電話簿按姓氏和名字進行組織同樣。
彙集索引對於那些常常要搜索範圍值的列特別有效。使用匯集索引找到包含第一個值的行後,即可以確保包含後續索引值的行在物理相鄰。例如,若是應用程序執行 的一個查詢常常檢索某一日期範圍內的記錄,則使用匯集索引能夠迅速找到包含開始日期的行,而後檢索表中全部相鄰的行,直到到達結束日期。這樣有助於提升此 類查詢的性能。一樣,若是對從表中檢索的數據進行排序時常常要用到某一列,則能夠將該表在該列上彙集(物理排序),避免每次查詢該列時都進行排序,從而節 省成本。
當索引值惟一時,使用匯集索引查找特定的行也頗有效率。例如,使用惟一僱員 ID 列 emp_id 查找特定僱員的最快速的方法,是在 emp_id 列上建立彙集索引或 PRIMARY KEY 約束。
繼續拿Order表舉例,Order表中的所有數據都保存在B樹中的葉層(leaf level)中,其餘層只是起到一個索引的做用,並不包含任何數據。葉層是一個雙向鏈表結構,並按照彙集索引的主鍵的邏輯順序排列。所以邏輯順序是用指針來維護。
2.2.1 索引碎片
數據庫中之因此會出現碎片,是由於B樹的頁拆分形成的。具體頁拆分請參考數據結構,這裏要說的是因爲拆分所產生的新頁不保證必定就會在被拆分的頁的後面,而是可能出於文件的任何位置。這就是「無序頁」。換句話說,也就是在列表中處於後面位置的元素,在物理文件中卻排在前面。若是你明白指針的定義的話,這句話並不難理解。由於葉層的雙向列表就是以指針來維護邏輯順序。
所以在按邏輯順序讀取的時候,因爲無序頁的存在,可能形成磁臂頻繁的擺動。別忘記,磁盤擺動是I/O中開銷最大的操做。而I/O每每是一個系統的瓶頸所在。
若是按照物理順序來讀取,也就是unordered讀取,就會避免上面所產生的問題。再次強調,unordered是指不按邏輯順序讀取,因此叫unordered。
2.2.2 索引的層數
索引的層數,也就是B樹的高度,直接代表了一次查找操做在頁面讀取方面的開銷。一些執行計劃如Nested loop聯接會屢次調用查找操做。所以理解這個概念很重要。
樹的高度主要和如下幾個因素相關
1.表的總行數。
2.平均一行保存數據的大小。
3.頁的平均密度。由於不是每一頁都應該填充滿數據,這樣能夠減小頁拆分的次數。
4.一頁所能容納的行數。
具體公式也很簡單,3級索引大概能容納4百萬行,4級索引大概能容納4億行數據。所以一般一張表的索引層數一般爲3到4級。
2.3非彙集索引(NonClustered Index)
該索引中索引的邏輯順序與磁盤上行的物理存儲順序不一樣。
索引是經過二叉樹的數據結構來描述的,咱們能夠這麼理解聚簇索引:索引的葉節點就是數據節點。而非聚簇索引的葉節點仍然是索引節點,只不過有一個指針指向對應的數據塊。
和彙集索引的區別就在於它的葉層並不包含全部的數據。在默認狀況下它只包含了鍵列的數據,幷包含了一個行定位符(row locator)。這個行定位符的具體內容取決於它創建在以堆形式的表仍是以B樹組織的表,換句話說也就是這張表是否創建了彙集索引會影響到非彙集索引的行定位符。若是是創建了彙集索引,那麼這個行定位符就是一個彙集鍵,咱們經過這個彙集鍵再次查找彙集索引上的數據。
2.3.1 若是非彙集索引包含了咱們須要查找的全部數據
這種狀況咱們一般叫作索引覆蓋。
正由於非彙集索引有着和索引同樣的結構,而且因爲非彙集索引所包含的列少,所以數據量就小,使得葉層的一頁能包含更多的行,所以進行一次I/O頁讀取的動做的時候,就能讀取進更多的行。所以查找效率是最高的。
舉個不恰當的例子,美女徵婚,應徵人員的我的信息表有 「姓名、 德、 智、 體 、美、 勞、 高、 富、 帥」這幾列,按姓名排序。美女只關注「高、 富、 帥」這三列的內容,爲了更快的篩選,咱們幫美女按照我的信息表的內容從新制做了一張表,這張表忽略了其餘信息,只保留了高、富、帥和姓名,篩選效率固然就比原來關注更多內容時要高。
2.3.2 若是非彙集索引不包含咱們須要查找的全部數據
通俗的說這時咱們就須要從非彙集索引中所包含的線索去包含全部數據的表中去找。
按照咱們以前的定義換句話來講,就是經過非彙集索引中的行定位符去彙集索引或者堆中去查找所需的數據。
2、深刻淺出理解索引結構
實際上,您能夠把索引理解爲一種特殊的目錄。微軟的SQL SERVER提供了兩種索引:彙集索引(clustered index,也稱聚類索引、簇集索引)和非彙集索引(nonclustered index,也稱非聚類索引、非簇集索引)。下面,咱們舉例來講明一下彙集索引和非彙集索引的區別:
其實,咱們的漢語字典的正文自己就是一個彙集索引。好比,咱們要查「安」字,就會很天然地翻開字典的前幾頁,由於「安」的拼音是「an」,而按照拼音排序漢字的字典是以英文字母「a」開頭並以「z」結尾的,那麼「安」字就天然地排在字典的前部。若是您翻完了全部以「a」開頭的部分仍然找不到這個字,那麼就說明您的字典中沒有這個字;一樣的,若是查「張」字,那您也會將您的字典翻到最後部分,由於「張」的拼音是「zhang」。也就是說,字典的正文部分自己就是一個目錄,您不須要再去查其餘目錄來找到您須要找的內容。咱們把這種正文內容自己就是一種按照必定規則排列的目錄稱爲「彙集索引」。
若是您認識某個字,您能夠快速地從自動中查到這個字。但您也可能會遇到您不認識的字,不知道它的發音,這時候,您就不能按照剛纔的方法找到您要查的字,而須要去根據「偏旁部首」查到您要找的字,而後根據這個字後的頁碼直接翻到某頁來找到您要找的字。但您結合「部首目錄」和「檢字表」而查到的字的排序並非真正的正文的排序方法,好比您查「張」字,咱們能夠看到在查部首以後的檢字表中「張」的頁碼是672頁,檢字表中「張」的上面是「馳」字,但頁碼倒是63頁,「張」的下面是「弩」字,頁面是390頁。很顯然,這些字並非真正的分別位於「張」字的上下方,如今您看到的連續的「馳、張、弩」三字實際上就是他們在非彙集索引中的排序,是字典正文中的字在非彙集索引中的映射。咱們能夠經過這種方式來找到您所須要的字,但它須要兩個過程,先找到目錄中的結果,而後再翻到您所須要的頁碼。咱們把這種目錄純粹是目錄,正文純粹是正文的排序方式稱爲「非彙集索引」。
經過以上例子,咱們能夠理解到什麼是「彙集索引」和「非彙集索引」。進一步引伸一下,咱們能夠很容易的理解:每一個表只能有一個彙集索引,由於目錄只能按照一種方法進行排序。
一、什麼時候使用匯集索引或非彙集索引
下面的表總結了什麼時候使用匯集索引或非彙集索引(很重要):
動做描述 |
使用匯集索引 |
使用非彙集索引 |
列常常被分組排序 |
應 |
應 |
返回某範圍內的數據 |
應 |
不該 |
一個或極少不一樣值 |
不該 |
不該 |
小數目的不一樣值 |
應 |
不該 |
大數目的不一樣值 |
不該 |
應 |
頻繁更新的列 |
不該 |
應 |
外鍵列 |
應 |
應 |
主鍵列 |
應 |
應 |
頻繁修改索引列 |
不該 |
應 |
事實上,咱們能夠經過前面彙集索引和非彙集索引的定義的例子來理解上表。如:返回某範圍內的數據一項。好比您的某個表有一個時間列,剛好您把聚合索引創建在了該列,這時您查詢2004年1月1日至2004年10月1日之間的所有數據時,這個速度就將是很快的,由於您的這本字典正文是按日期進行排序的,聚類索引只須要找到要檢索的全部數據中的開頭和結尾數據便可;而不像非彙集索引,必須先查到目錄中查到每一項數據對應的頁碼,而後再根據頁碼查到具體內容。
二、結合實際,談索引使用的誤區
理論的目的是應用。雖然咱們剛纔列出了什麼時候應使用匯集索引或非彙集索引,但在實踐中以上規則卻很容易被忽視或不能根據實際狀況進行綜合分析。下面咱們將根據在實踐中遇到的實際問題來談一下索引使用的誤區,以便於你們掌握索引創建的方法。
2.一、主鍵就是彙集索引
這種想法筆者認爲是極端錯誤的,是對彙集索引的一種浪費。雖然SQL SERVER默認是在主鍵上創建彙集索引的。
一般,咱們會在每一個表中都創建一個ID列,以區分每條數據,而且這個ID列是自動增大的,步長通常爲1。咱們的這個辦公自動化的實例中的列Gid就是如此。此時,若是咱們將這個列設爲主鍵,SQL SERVER會將此列默認爲彙集索引。這樣作有好處,就是可讓您的數據在數據庫中按照ID進行物理排序,但筆者認爲這樣作意義不大。
顯而易見,彙集索引的優點是很明顯的,而每一個表中只能有一個彙集索引的規則,這使得彙集索引變得更加珍貴。
從咱們前面談到的彙集索引的定義咱們能夠看出,使用匯集索引的最大好處就是可以根據查詢要求,迅速縮小查詢範圍,避免全表掃描。在實際應用中,由於 ID號是自動生成的,咱們並不知道每條記錄的ID號,因此咱們很難在實踐中用ID號來進行查詢。這就使讓ID號這個主鍵做爲彙集索引成爲一種資源浪費。其次,讓每一個ID號都不一樣的字段做爲彙集索引也不符合「大數目的不一樣值狀況下不該創建聚合索引」規則;固然,這種狀況只是針對用戶常常修改記錄內容,特別是索引項的時候會負做用,但對於查詢速度並無影響。
在辦公自動化系統中,不管是系統首頁顯示的須要用戶簽收的文件、會議仍是用戶進行文件查詢等任何狀況下進行數據查詢都離不開字段的是「日期」還有用戶自己的「用戶名」。
一般,辦公自動化的首頁會顯示每一個用戶還沒有簽收的文件或會議。雖然咱們的where語句能夠僅僅限制當前用戶還沒有簽收的狀況,但若是您的系統已創建了很長時間,而且數據量很大,那麼,每次每一個用戶打開首頁的時候都進行一次全表掃描,這樣作意義是不大的,絕大多數的用戶1個月前的文件都已經瀏覽過了,這樣作只能徒增數據庫的開銷而已。事實上,咱們徹底可讓用戶打開系統首頁時,數據庫僅僅查詢這個用戶近3個月來未閱覽的文件,經過「日期」這個字段來限制表掃描,提升查詢速度。若是您的辦公自動化系統已經創建的2年,那麼您的首頁顯示速度理論上將是原來速度8倍,甚至更快。
在這裏之因此提到「理論上」三字,是由於若是您的彙集索引仍是盲目地建在ID這個主鍵上時,您的查詢速度是沒有這麼高的,即便您在「日期」這個字段上創建的索引(非聚合索引)。下面咱們就來看一下在1000萬條數據量的狀況下各類查詢的速度表現(3個月內的數據爲25萬條):
(1)僅在主鍵上創建彙集索引,而且不劃分時間段:
Select gid,fariqi,neibuyonghu,title from tgongwen
用時:128470毫秒(即:128秒)
(2)在主鍵上創建彙集索引,在fariq上創建非彙集索引:
select gid,fariqi,neibuyonghu,title from Tgongwen
where fariqi> dateadd(day,-90,getdate())
用時:53763毫秒(54秒)
(3)將聚合索引創建在日期列(fariqi)上:
select gid,fariqi,neibuyonghu,title from Tgongwen
where fariqi> dateadd(day,-90,getdate())
用時:2423毫秒(2秒)
雖然每條語句提取出來的都是25萬條數據,各類狀況的差別倒是巨大的,特別是將彙集索引創建在日期列時的差別。事實上,若是您的數據庫真的有1000 萬容量的話,把主鍵創建在ID列上,就像以上的第一、2種狀況,在網頁上的表現就是超時,根本就沒法顯示。這也是我摒棄ID列做爲彙集索引的一個最重要的因素。得出以上速度的方法是:在各個select語句前加:
declare @d datetime
set @d=getdate()
並在select語句後加:
select [語句執行花費時間(毫秒)]=datediff(ms,@d,getdate())
2.二、只要創建索引就能顯著提升查詢速度
事實上,咱們能夠發現上面的例子中,第二、3條語句徹底相同,且創建索引的字段也相同;不一樣的僅是前者在fariqi字段上創建的是非聚合索引,後者在此字段上創建的是聚合索引,但查詢速度卻有着天壤之別。因此,並不是是在任何字段上簡單地創建索引就能提升查詢速度。
從建表的語句中,咱們能夠看到這個有着1000萬數據的表中fariqi字段有5003個不一樣記錄。在此字段上創建聚合索引是再合適不過了。在現實中,咱們天天都會發幾個文件,這幾個文件的發文日期就相同,這徹底符合創建彙集索引要求的:「既不能絕大多數都相同,又不能只有極少數相同」的規則。由此看來,咱們創建「適當」的聚合索引對於咱們提升查詢速度是很是重要的。
2.三、把全部須要提升查詢速度的字段都加進彙集索引,以提升查詢速度
上面已經談到:在進行數據查詢時都離不開字段的是「日期」還有用戶自己的「用戶名」。既然這兩個字段都是如此的重要,咱們能夠把他們合併起來,創建一個複合索引(compound index)。
不少人認爲只要把任何字段加進彙集索引,就能提升查詢速度,也有人感到迷惑:若是把複合的彙集索引字段分開查詢,那麼查詢速度會減慢嗎?帶着這個問題,咱們來看一下如下的查詢速度(結果集都是25萬條數據):(日期列fariqi首先排在複合彙集索引的起始列,用戶名neibuyonghu排在後列):
(1)select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi>''2004-5-5''
查詢速度:2513毫秒
(2)select gid,fariqi,neibuyonghu,title from Tgongwen
where fariqi>''2004-5-5'' and neibuyonghu=''辦公室''
查詢速度:2516毫秒
(3)select gid,fariqi,neibuyonghu,title from Tgongwen where neibuyonghu=''辦公室''
查詢速度:60280毫秒
從以上試驗中,咱們能夠看到若是僅用匯集索引的起始列做爲查詢條件和同時用到複合彙集索引的所有列的查詢速度是幾乎同樣的,甚至比用上所有的複合索引列還要略快(在查詢結果集數目同樣的狀況下);而若是僅用複合彙集索引的非起始列做爲查詢條件的話,這個索引是不起任何做用的。固然,語句一、2的查詢速度同樣是由於查詢的條目數同樣,若是複合索引的全部列都用上,並且查詢結果少的話,這樣就會造成「索引覆蓋」,於是性能能夠達到最優。同時,請記住:不管您是否常用聚合索引的其餘列,但其前導列必定要是使用最頻繁的列。
三、其餘書上沒有的索引使用經驗總結
3.一、用聚合索引比用不是聚合索引的主鍵速度快
下面是實例語句:(都是提取25萬條數據)
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi=''2004-9-16''
使用時間:3326毫秒
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid<=250000
使用時間:4470毫秒
這裏,用聚合索引比用不是聚合索引的主鍵速度快了近1/4。
3.二、用聚合索引比用通常的主鍵做order by時速度快,特別是在小數據量狀況下
select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by fariqi
用時:12936
select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by gid
用時:18843
這裏,用聚合索引比用通常的主鍵做order by時,速度快了3/10。事實上,若是數據量很小的話,用匯集索引做爲排序列要比使用非彙集索引速度快得明顯的多;而數據量若是很大的話,如10萬以上,則兩者的速度差異不明顯。
3.三、使用聚合索引內的時間段,搜索時間會按數據佔整個數據表的百分比成比例減小,而不管聚合索引使用了多少個:
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>''2004-1-1''
用時:6343毫秒(提取100萬條)
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>''2004-6-6''
用時:3170毫秒(提取50萬條)
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi=''2004-9-16''
用時:3326毫秒(和上句的結果如出一轍。若是採集的數量同樣,那麼用大於號和等於號是同樣的)
select gid,fariqi,neibuyonghu,reader,title from Tgongwen
where fariqi>''2004-1-1'' and fariqi<''2004-6-6''
用時:3280毫秒
3.四、日期列不會由於有分秒的輸入而減慢查詢速度
下面的例子中,共有100萬條數據,2004年1月1日之後的數據有50萬條,但只有兩個不一樣的日期,日期精確到日;以前有數據50萬條,有5000個不一樣的日期,日期精確到秒。
select gid,fariqi,neibuyonghu,reader,title from Tgongwen
where fariqi>''2004-1-1'' order by fariqi
用時:6390毫秒
select gid,fariqi,neibuyonghu,reader,title from Tgongwen
where fariqi<''2004-1-1'' order by fariqi
用時:6453毫秒
四、其餘注意事項
「水可載舟,亦可覆舟」,索引也同樣。索引有助於提升檢索性能,但過多或不當的索引也會致使系統低效。由於用戶在表中每加進一個索引,數據庫就要作更多的工做。過多的索引甚至會致使索引碎片。
因此說,咱們要創建一個「適當」的索引體系,特別是對聚合索引的建立,更應精益求精,以使您的數據庫能獲得高性能的發揮。
總結轉載與如下博客中
http://www.cnblogs.com/lwzz/archive/2012/08/05/2620824.html
http://www.cnblogs.com/aspnethot/articles/1504082.html
轉載自:http://www.cnblogs.com/taiyonghai/p/5780907.html