2013-7-26html
常常在面試中發現不少人工做了好多年了,項目經驗也很多,用過各類數據庫,但大都不知道這些SQL語句背後的基本原理,更別說數據庫優化了。平時作項目只知道實現功能,懶得學習,懶得思考,懶得看書(其實本人也是,不要找藉口說這是China國情,項目是給boss作的,但技術和成長是你本身的)。web
本篇文章主要講述數據庫索引的基本原理,及基本的數據庫優化的知識。全部知識均爲本人本身學習的總結以及網絡。此篇文章主要是爲公司內部人員培訓所用的,整理出來只是但願和你們分享、交流,因本人技術有限,如有遺漏、錯誤,但願多多指正、交流。面試
數據庫文件存儲是已頁爲存儲單元的,一個頁是8K(8192Byte),一個頁就能夠存放N行數據。咱們經常使用的頁類型就是數據頁和索引頁。一個頁中除了存放基本數據以外還須要存放一些其餘的數據,如頁的信息、偏移量等,以下圖所示。sql
雖然SQLServer是以頁爲單位存儲數據,可是其分配空間是以一個盤區爲單位的(8個頁=64K),這樣作的目的主要是爲提升I/O的性能。數據庫
B樹即二叉搜索樹,全部非葉子節點最低擁有兩個子節點,基本信息以下圖所示。都是小的元素放左邊,大的元素放右邊。好比說要查找某個元素,其時間複雜度就對應該元素的深度,如要查詢9,從根節點開始,只要比較三次就找到他了,其查詢效率是很是高的。性能優化
子節點:最多兩個子節點(指針分別指向Left和Right)服務器
階數(節點子節點個數):2網絡
深度:就是層數,各個葉子節點不必定同樣,如節點21的深度爲4,40的深度爲3函數
B-樹是一中多路搜索樹,其階數能夠自定義(>2),是不少數據及文件系統應用的一種索引結構,基本特徵如:佈局
1) 階數(M)>2,即孩子數量大於2個
2) 每一個結點存放至少M/2-1(取上整)和至多M-1個關鍵字;(至少2個關鍵字)
3) 非葉子結點上的多個關鍵字是按照順序排列的:K[1], K[2], …, K[M-1];且K[i] < K[i+1];
4) 全部葉子節點都位於同一層,所以葉子節點的深度都是同樣的
5) 非葉子結點的關鍵字個數=指向兒子的指針個數-1;
6) 非葉子結點的指針:P[1], P[2], …, P[M];其中P[1]指向關鍵字小於K[1]的子樹,P[M]指向關鍵字大於K[M-1]的子樹,其它P[i]指向關鍵字屬於(K[i-1], K[i])的子樹;
以下圖是一個三階的B-樹,節點[18]有兩個指針分別指向其2個子節點。
這時若是要插入一個值17,其處理步驟:
1) 從根節點進入,17小於22,進入左邊的節點[18];
2) [18]不是葉子節點,繼續向下搜索,17小於18,進入其左邊的子節點[12,16];
3) [12,16]爲葉子節點,插入到該節點;
4) 節點[12,16,17]元素大於2了(3階樹的節點關鍵字數量應>3/2-1,<3-1),所以該節點須要分裂,分裂中間的元素16到父節點18中去;
5) 12,17分裂成了兩個子節點了;
分裂後的效果以下圖
以上圖片效果來自一個外國大學裏面的的在線版B-樹的測試,網站:http://www.cs.usfca.edu/~galles/visualization/BTree.html ,你們能夠去這個網站測試,效果很直觀,外國人就是牛。本人之前用C#+GDI實現過相似的效果,結果仍是能夠的,就是當樹太大的時候,佈局很差處理了。
B+樹是B-樹的變體,也是一種多路搜索樹,一棵m 階的B+樹和m 階的B-樹的差別在於:
l 非葉子節點的子節點和其關鍵字相同,即節點有三個元素(關鍵字),他就確定有三個子節點;
l 非葉子節點的子節點P[i],指向關鍵字值屬於[K[i], K[i+1])的子樹(B-樹是開區間);
l 全部葉子節點增長一個鏈指針;
l 全部關鍵字的數據都在葉子節點中;
以下圖所示,圖片來自網絡(http://www.cnblogs.com/chjw8016/archive/2011/03/08/1976891.html)。
B+樹和B-樹是數據庫廣發應用的索引存儲結構,它能夠極大的提升數據查找的效率。關於B-樹、B+樹的原理與應用的詳細能夠參考文檔:http://blog.csdn.net/hguisu/article/details/7786014
前面2.1中咱們講了SQLServer中使用頁爲存儲單元的,那麼在創建索引時,其索引節點就是頁了,而後樹的鍵值就是存放到這些頁(節點)中的。就是說表中的數據行就是存放到頁上的,一個表有多個頁構成,這些頁以樹的結構存放。
以下圖爲彙集索引的存儲結構(圖片來自網絡)。其中能夠看出頁有兩種:Index Rows(索引頁)、data rows(數據頁),全部非葉子節點都存放着索引項,數據行是存放在葉子節點中的,只有葉子節點才真實存放着表中的每一行數據,而其餘非葉子節點的頁都存放着彙集索引的鍵值。所以查詢數據的時間複雜度都是同樣的,就是該樹的深度。
在4.2中有說明,彙集索引決定了表數據的存儲順序,具體能夠參考4.2。若表沒有建立彙集索引,則表數據時一個無序的堆結構。
與非彙集索引的存儲結構惟一不同的,就是非彙集索引中不存儲真正的數據行,由於在彙集索引中已經存放了數據,非彙集索引只包含一個指向數據行的指針便可。以下圖所示(圖片來源:http://www.cnblogs.com/ashou706/archive/2010/06/08/1754164.html):
數據庫優化的一個重要參數指標就是「邏輯讀」(Logical Reads),可使用命令SET STATISTICS IO ON來打開消息提示,以下圖所示。
邏輯讀(Logical Reads):咱們在查詢數據時,數據時從緩衝區(內存)中讀取的,而不是直接從磁盤讀取數據的。數據庫會預先把數據讀取到數據緩衝區中,存放到8K字節的頁中。邏輯讀就是從緩衝區中讀取頁的頁數,這個纔是真正反映查詢效率的指標,通常狀況下,一個查詢的邏輯讀越小,其效率越高、速度就越快。同時,一樣的SQL查詢一樣數據集,每次的邏輯讀是同樣的。
物理讀取(Physical Reads):真正的從磁盤讀取數據到期緩衝區,在SQLServer執行查詢前,會先檢查其須要的數據是否在緩衝區中,若不在,就會從磁盤讀取數據到緩衝區。這一塊是數據庫自己的職責,咱們在作查詢優化的時候不用太關注的,只要給數據庫服務器提供足夠的內存就OK了。
預讀(Read-Ahead Reads):數據庫爲優化查詢,預先讀取一部分數據,這個值在優化中能夠不用關注。因爲存儲介質的特性,磁盤自己存取就比主存慢不少,再加上機械運動耗費,磁盤的存取速度每每是主存的幾百分分之一,所以爲了提升效率,要儘可能減小磁盤I/O。爲了達到這個目的,磁盤每每不是嚴格按需讀取,而是每次都會預讀,即便只須要一個字節,磁盤也會從這個位置開始,順序向後讀取必定長度的數據放入內存。因爲磁盤順序讀取的效率很高(不須要尋道時間,只需不多的旋轉時間),所以對於具備局部性的程序來講,預讀能夠提升I/O效率。
數據優化的一個主要手段就是查看SQL的執行計劃,經過查看具體SQL執行過程,能夠看出索引的使用是否正確,瞭解查詢中性能問題在哪,從而解決問題。
1.在Where條件中儘可能不要在=號左邊進行函數、運算符、或表達式計算,如Where DATEDIFF(DD,StartTime,GetDate())=6 ;或Where Num/2=100;
2.在Where中儘可能避免出現!=或<>操做符;
3.在Where中儘可能避免對字段進行null值斷定;
4.使用Like關鍵字進行模糊查找時,不要使用前置百分號,如Like ‘%123%’;
5.數據庫字段的長度儘可能的小(保證應用的前提下);
6.不要使用Selecte*,不要使用*號來查詢數據;
7.儘可能避免使用遊標,遊標的效率是不好的,可使用While循環來代替;
8.儘可能避免返回大量數據(查詢數據(Select)優化,分頁處理等);
9.使用Exists代替in和not in;
彙集索引決定了表數據的物理存儲順序,也就是說表的物理存儲時根據彙集索引結構進行順序存儲的,所以一個表只能有一個彙集索引,SQLServer的彙集索引屬性以下圖。
該索引的的建立腳本:
/****** Object: Index [Index_KeyId] Script Date: 08/12/2013 15:25:59 ******/
CREATE UNIQUE CLUSTERED INDEX [Index_KeyId] ON [dbo].[User] ( [KeyId] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
所以咱們能夠得出一個個結論:根據彙集索引的查找效率是比較高的。若表沒有創建彙集索引,則表的數據存儲是亂的,數據就是一個堆,沒有任何順序可言,對錶的查詢常常會掃描全表,形成性能較低。通常咱們在實際使用中,大多會對主鍵創建彙集索引,通常這麼作就足夠了,但實際應用應該尊從一個原則就是「頻繁使用的、排序的字段上」,若是要深究的話,能夠參見文章結尾的參考目錄其餘同窗的文章。
對應與彙集索引,全部其餘的索引能夠統稱爲非彙集索引,非彙集索引的建立會單首創建索引文件來存儲索引結構,所以在建立其餘索引的時候也要注意硬盤空間。好比一個表的容量是2000w行,大概有800Mb,建立的一個非彙集索引可能數據立馬增長好幾個G。具體以下4.三、4.4所述。
覆蓋索引就是在本來索引的基礎上,把Select中須要的字段放到索引包含列中,這樣就不須要再到數據表中讀取數據了,這個就叫作覆蓋索引了。 好比,咱們查詢User表中的字段UserName、Age,其中UserName上建立了非彙集索引,查詢語句及索引腳本以下:
Select UserName,Age from [User] where UserName ='Ryan'
--UserName的索引
CREATE UNIQUE NONCLUSTERED INDEX [Index_UserName] ON [dbo].[User] ( [UserName] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
經過UserName條件查詢到數據後,還須要Age字段的值,該非彙集索引沒有她的數據,還要到數據頁中取其Age數據(就是圖中的鍵查找),這樣會形成額外的開銷,查詢計劃以下圖。
若把Age放到該索引的包含列中,該索引就會包含Age的值,查詢的時候就能夠直接返回UserName、Age的值了,UserName、Age的覆蓋索引腳本及SQLServer的管理視圖以下:
CREATE UNIQUE NONCLUSTERED INDEX [Index_UserName] ON [dbo].[User] ( [UserName] ASC ) INCLUDE ( [Age]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
建立了覆蓋索引後的查詢效率明顯高了,執行計劃以下圖,其中就沒有了循環鍵查找了:
關於覆蓋索引更詳細的文章,能夠參考文章:SQL Server 查詢性能優化——覆蓋索引
在上面的索引例子中都只是在一個鍵上創建索引,但實際狀況中每每一個查詢會有多個查詢條件,以下的sql語句中,多了條件Password:
Select UserName,Age from [User] where UserName ='Ryan' and Password='123456'
該索引中是沒有關於Password字段的任何信息,所以查詢也會引起鍵查找,查詢計劃以下圖
對於這種狀況,複合索引的用途就來了,簡單來所,複合索引就是在多個列上創建索引。Sql腳本及SqlServer的索引屬性視圖以下:
CREATE UNIQUE NONCLUSTERED INDEX [Index_UserName] ON [dbo].[User] ( [UserName] ASC, [Password] ASC ) INCLUDE ( [Age]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
建立複合索引後再次執行剛的查詢,觀看查詢計劃,查詢有明顯的改善:
在使用複合索引時,應注意多個索引鍵的順序問題,這個是會影響查詢效率的,通常的原則是惟一性高的放前面,還有就是SQl語句中Where條件的順序應該和索引順序一致。
經過前面的瞭解咱們知道數據時存放到樹上的頁中,當插入數據時,若是該頁已經存儲滿了,就要進行頁的拆分,頻繁的拆分,會產生較多的索引碎片,影響修改和查詢數據的效率。
填充因子就是用來描述這種頁中填充數據的一個比例,通常默認是100%填充的。若是咱們修改填充因子爲80%,那麼頁在存儲數據時,就會剩餘20%的剩餘空間,這樣在下次插入的時候就不會拆分頁了。 那麼是否是咱們能夠把填充因子設置第一點,留更多的剩餘空間,不是很好嘛?固然也很差,填充因子設置的低,會須要分配更多的存儲空間,葉子節點的深度會增長,這樣是會影響查詢效率的,所以,這是要根據實際狀況而定的。那麼通常咱們是怎麼設置填充因子的呢,主要根據表的讀寫比例而定的。若是讀的多,填充因子能夠設置高一點,如100%,讀寫各一半,能夠80~90%;更改多能夠設置50~70%。SQlServer的的索引屬性中有一個設置填充因子的項,以下圖。
更詳細的信息能夠參考:http://www.cnblogs.com/cxd4321/archive/2010/08/16/1800677.html
咱們在前面瞭解到索引的結構就是B樹,當樹在增長、刪除的時候,會觸發頁的拆分或合併,這種頻繁的操做會產生索引碎片,形成索引不連續,當索引碎片曾多時,是會影響查詢效率的。所以,訪問使用的是隨機的i/o,而不是有順序的i/o,這樣訪問索引頁會變得更慢。所以要按期的清理索引碎片,通常的方法就是重建索引。關於索引碎片的整理,可參考:http://www.cnblogs.com/mywebname/archive/2007/11/13/958463.html
Ø 創建索引的字段儘可能的小,最好是數值;
Ø 儘可能在惟一性高的字段上建立索引,不要在性別這種惟一性很低的字段上建立索引;
1.一個b-樹的在線演示例子,很直觀很不錯的額:
http://www.cs.usfca.edu/~galles/visualization/BTree.html
2. SQLSERVER彙集索引與非彙集索引的再次研究
http://www.cnblogs.com/lyhabc/p/3196479.html
3. SQL Server 查詢性能優化——覆蓋索引:
http://www.cnblogs.com/chillsrc/archive/2012/09/04/2671092.html
出處:http://www.cnblogs.com/anding/p/3254674.html
Ps:這文章格式太難弄了,圖片上傳也很繁瑣(原本是用word寫好了的)