這篇文章是我花了不少時間寫出來的,曾經發表在javaeye論壇上,今天竟然不見了,幸虧網上有人轉載這篇文章,沒辦法,只好再一份在博客裏。這個是我之前寫給我部門的一個技術心得,鄙人才疏學淺,知道javaeye高人不少,若是我寫的不對的地方,歡迎指教。 java
我靠這麼多關鍵字過濾啊,「fapiao」也成了關鍵字mysql
1:前言sql
數據庫優化是一個很廣的範圍,涉及到的東西比較多,而且每一個特定的數據庫,其具體的優化過程也是不同的.由於優化的很大一部分最終都要跟具體的數據庫系 統細節打交道,在此不可能針對全部的數據庫都一一詳細闡述,若是那樣,恐怕寫幾本書都寫不完.只能針對一些比較通用的,常常用到的的東西進行一個討論,一 般狀況下,數據庫的優化指的就是查詢性能的優化(雖然嚴格上來講不該該是這樣的),讓數據庫對查詢的響應儘量的快.僅對數據庫系統自己而言,影響到查詢 性能的因素從理論上來說,包括數據庫參數設置(其實就是經過參數控制數據庫系統的內存,i/o,緩存,備份等一些管理性的東西),索引,分區,sql語 句.數據庫參數設置自己是一個很複雜的東西,分區則主要是針對大數據量的狀況下,它分散了數據文件的分佈,減小磁盤競爭,使效率獲得提高。數據庫
每種數據庫或多或少都有一些本身特定的索引,如oracle除了常規索引以外還有反向索引,位圖索引,函數索引,應用程序域索引等等,可以讓用戶對數據 的邏輯組織有着更爲精確的控制,而sqlserver沒有這麼多的索引,大致來講,sqlserver的索引分爲兩種:彙集索引和非彙集索引.在分區方 面,oracle和sqlserver比較類似,不過sqlserver的分區更爲繁瑣一些,但隨着sqlserver的版本愈來愈高,其分區操做也趨向 於簡潔.sql語句優化則基本上比較獨立,目前的一些數據庫系統處理sql的機制都比較相似,由於sql自己就是一個標準。這三種將會在下面做一個詳細的 討論.本討論創建在sqlserver上,由於目前部門的不少系統的數據庫用到的是sqlserver,雖然oracle會給與咱們更多的可探討的範 圍. api
2:測試數據庫的創建
由於要討論索引,分區,sql等,所以有必要創建一個數據庫,否則只是泛泛而談,我在sqlserver2000上創建了一個名爲ipanel的數據庫,該數據庫只有一張表,名爲person,person的定義以下:緩存
CREATE TABLE [dbo].[person] (
[id] [bigint] NOT NULL , --記錄的id
[name] [varchar] (10) COLLATE Chinese_PRC_CI_AS NULL ,--姓名
[age] [int] NULL ,--年齡
[addr] [varchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,--地址
[sex] [char] (10) COLLATE Chinese_PRC_CI_AS NULL ,--性別
[dept] [varchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,--部門
[pos] [varchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,--郵編
[tel] [char] (15) COLLATE Chinese_PRC_CI_AS NULL ,--電話
[fax] [char] (15) COLLATE Chinese_PRC_CI_AS NULL ,--傳真
[emdate] [datetime] NULL --入職日期
) ON [PRIMARY]安全
ON[PRIMARY]表示該表建在系統的默認文件組上,在sqlserver裏,文件組的概念就至關於oracle的表空間,是一種邏輯概念,它包含了 數據文件,所謂數據文件,固然就是存儲數據的文件.默認狀況下,sqlserver會在默認的路徑創建文件組和初始的數據文件,若是用戶在創建數據庫或表 的時候沒有指定文件組,則用默認的。數據文件,日誌文件,參數文件是全部數據庫系統最主要的文件,oracle還有控制文件,在不少的專業書籍裏面,從數 據庫系統的物理結構上來說,數據庫就是指的靜態的數據文件,數據庫系統或者數據庫實例指的是一組進程,如日誌進程,數據緩衝進程,網絡監聽進程等,這些進 程做用在各類文件上面。不說了,扯遠了.建了一個數據插入的存儲過程: 服務器
CREATE PROCEDURE initPerson @start int, @end int , --起始條數,結束條數
@name varchar(10),@age int, --姓名,年齡
@addr varchar(10),@sex char(2), --地址,性別
@dept varchar(20),@emdate varchar(10 --部門,入職日期
AS
declare @id int
set @id=@start
while @id<=@end
begin
insert into person values(@id,@name,@age,@addr,@sex,@dept ,
'438200','82734664','82734665',@emdate)
set @id=@id+1
end
GO 網絡
如下插入記錄
exec initPerson 1,100000, ‘王**’,24,’深圳’,’男’,’應用開發部’,’2007-06-04’
插入10萬條名叫王**的記錄,由於在當前的例子中,姓名不重要,因此相同的姓名不礙事。以下依次執行
exec initPerson 100001,200000, ‘韓**’,25,’深圳’,’男’,’應用工程部’,’2007-06-05’
exec initPerson 200001,300000, ‘徐*’,26,’ 深圳’,’男’,’系統終端部’,’2007-06-06’
exec initPerson 300001,500000, ‘程*’,23’, 深圳’,’男’,’研發中心’,’2007-06-07’
exec initPerson 500001,750000, ‘卓*’,22,’ 深圳’,’男’,’行政部’,’2007-06-08’
exec initPerson 750001,1000000, ‘流*’,20,’ 深圳’,’男’,’業務合做部’,’2007-06-09’
接着依次插入相似的記錄,我就不一一列舉了.
執 行完畢,person表便有了200萬條記錄。爲何我不用更多的數據呢,由於我要頻繁的改變數據庫的設置,若是數據很是多,那當我改變數據庫設置時候, 會耗費很長的時間,好比索引更新維護等,不太方便.值得一提的是,若是沒有指定彙集索引,那麼sqlserver默認在主鍵上創建彙集索引,在當前狀況 下,系統在id列上創建了彙集索引。
數據庫創建完畢,下面將會對索引,分區,sql作比較詳細的討論 架構
3:索引
索引是各類關係數據庫系統最多見的一種邏輯單元,是關係數據庫系統舉足輕重的重要組成部分,對於提升檢索數據速度有着相當重要的做用,索引的原理是根據索引值獲得行指針,而後快速定位到數據庫記錄..
3.1:常見索引介紹
1: B*樹索引
這是最多見的索引,幾乎全部的關係型數據庫系統都支持B*樹結構的索引,也是被最多使用的,其樹結構與二叉樹比較相似,根據行id快速定位到行.大部分數 據庫默認創建的索引就是這種索引.B*樹索引在檢索高基數數據列(高基數列是指該列有不少不一樣的值,該列全部不一樣值的個數之和與該列全部值的個數之和的比 成爲列基數)時提供了比較好的性能,B*樹索引是基於二叉樹的,由分支塊和葉塊組成.在樹結構中,位於最底層的快成爲葉塊,包含每一個被索引列的值和行所對 應的rowid.在葉節點的上面是分支塊,用來導航結構,包含了索引列(關鍵字)範圍和另外一索引快的地址,如圖所示: (圖片插入作的不夠好,插圖進來我以爲很好麻煩)
假設要查找索引中值爲80的行,從索引樹的最上層入口開始,首先定位到大於等於50,而後往左找,找到第二個分支塊,定位到75―100,而後定位 到葉塊,定位到葉塊,找到80所對應的rowid,而後根據rowid到數據塊讀取對應的數據。若是查詢條件是範圍選擇的,好比colume>20 and colume<80,那麼會先定位到20的塊,而後再橫向查找到80的塊爲止,不是每次都從入口進去從新定位的。
要說明的是, 這種索引是用得最多的,基本上全部的數據庫系統都支持這種索引,它是索引裏最主要最廣泛的,它之因此稱爲B*樹索引,更可能是由於它的存儲結構有着廣泛的意 義,不少索引都基於這種結構,固然sqlserver裏沒有名爲B*樹的索引,可是不妨礙咱們以對B*樹索引的認識去理解sqlserver的索引,不是 嗎?這是我爲何把它放在最前面的緣由.
2:彙集索引
沒錯,這是sqlserver裏很重要的一個索引.也叫羣集索引。 彙集索引是相對於常規索引而言的,oracle也有相似的索引,不過叫聚簇索引,注意,雖然聚簇和彙集僅有一字之差,可是oracle的聚簇索引和 sqlserver的彙集索引仍是有不少的不一樣的,oracle的聚簇索引能夠針對多表,根據多個表相同列的不一樣值,將相關數據彙集在周 圍.sqlserver彙集索引也有相似的意思,可是隻能針對單表.在oracle裏,聚簇」是oralce內部的一個對象,就像基本表,視圖,觸發器這 些概念同樣. 聚簇索引就是對聚簇進行的索引,因爲比較複雜,在此不詳細討論,但在sqlserver裏,彙集索引直接做用在表上,所以不能夠將兩者混淆.反正不能等同 來看就是了.
舉個例子說明來講明sqlserver的彙集索引:咱們的漢語字典的正文自己就是一個彙集索引。好比,咱們要查「安」字,就會很天然地翻開字典的前幾頁, 由於「安」的拼音是「an」,而按照拼音排序漢字的字典是以英文字母「a」開頭並以「z」結尾的,那麼「安」字就天然地排在字典的前部。若是您翻完了全部 以「a」開頭的部分仍然找不到這個字,那麼就說明您的字典中沒有這個字;一樣的,若是查「張」字,那您也會將您的字典翻到最後部分,由於「張」的拼音是 「zhang」。也就是說,字典的正文部分自己就是一個目錄,您不須要再去查其餘目錄來找到您須要找的內容。 咱們把這種正文內容自己就是一種按照必定規則排列的目錄稱爲「彙集索引」。 彙集索引都是排好序的.
若是您認識某個字,您能夠快速地從自動中查到這個字。但您也可能會遇到您不認識的字,不知道它的發音,這時候,您就不能按照剛纔的方法找到您要查的字,而 須要去根據「偏旁部首」查到您要找的字,而後根據這個字後的頁碼直接翻到某頁來找到您要找的字。但您結合「部首目錄」和「檢字表」而查到的字的排序並非 真正的正文的排序方法,好比您查「張」字,咱們能夠看到在查部首以後的檢字表中「張」的頁碼是672頁,檢字表中「張」的上面是「馳」字,但頁碼倒是63 頁,「張」的下面是「弩」字,頁面是390頁。很顯然,這些字並非真正的分別位於「張」字的上下方,如今您看到的連續的「馳、張、弩」三字實際上就是他 們在非彙集索引中的排序,是字典正文中的字在非彙集索引中的映射。咱們能夠經過這種方式來找到您所須要的字,但它須要兩個過程,先找到目錄中的結果,而後 再翻到您所須要的頁碼。
總而言之, 彙集索引就是使與被索引的值相關的行數據塊集中在一塊兒,不是物理上的散列分佈.這樣,首先縮小了掃描範圍,並且定位數據的時間短,能夠想象一下查字典的時 候,根據拼音查找漢字,找以」a」發音開頭的字,你只會在a字母裏面找,若是a字母找完了,那麼無論有沒有這個字,查找過程也就結束了。
非彙集 索引也是B*樹結構,只不過每一個索引值對應的不是行id,而是數據行自己,彙集索引會對錶排序,就像字典同樣,它按照英文字母的順序排序的,因此在基於某 個範圍搜索的時候,它的查詢效率是很高的,但同時咱們也能夠看到,它佔據了更多的空間,在插入更新的時候,它會花多一點的時間維持本身的索引順序。每一個表 只能有一個彙集索引,這是固然的,由於每一個表確定只可能有一個全表排序的規則。
3:非彙集索引
非彙集索引是一種典型的B*樹索引,每一個葉塊只包含兩種數據,一種是索引項,一種是該索引項所在行的行指針,當查詢的數據匹配該索引項數據的時候,將會取 出對應的行指針,取得該行的數據.若是要根據鍵值從大型 SQL Server 表提取具備良好選擇性的少數幾行,非彙集索引最有用。B*樹的底部或葉級包含組成該索引的列中的全部數據。當用非彙集索引檢索表中與鍵值匹配的信息時,將 搜索整個索引 B 樹,直到在索引葉級找到一個與鍵值匹配的值。
在非彙集索引中,葉級節點僅包含參與索引的數據以及快速找到相關數據頁上其它行 數據的指針。最糟糕的狀況是,從非彙集索引中得到的每一行都要求一個額外的不連續磁盤 I/O 才能檢索行數據。最好的狀況是,所須要的行有許多都位於相同的數據頁,所以在提取每一個數據頁時可檢索多行。若是是彙集索引,索引的葉級節點是表的實際數據 行。所以,檢索表數據時不須要指針跳動。基於彙集索引的範圍掃描執行狀況很好,由於彙集索引的葉級(即表的全部行)在物理上按照組成彙集索引的列順序排列 在磁盤上.
4:覆蓋索引
覆蓋索引是非彙集索引的一個特例。覆蓋索引的定義是在選擇條件和 WHERE 謂詞上均知足 SQL 查詢的全部列的基礎上創建的非彙集索引。覆蓋索引能夠節省大量的 I/O,所以可極大地改善查詢的性能。可是有必要在新建索引(以及與它相關的 B 樹索引結構維護)所須要的代價和覆蓋索引所帶來的 I/O 性能增益之間進行權衡。若是覆蓋索引對於 SQL Server 上常常運行的查詢或查詢組極其有利,那麼建立覆蓋索引是值得的。
覆蓋索引的示例
Select col1,col3 from table1 where col2 = 'value'.
Create index indexname1 on table1(col2,col1,col3).
本 例中建立出來的索引「indexname1」是一個覆蓋索引,由於它包括 SELECT 語句和 WHERE 謂詞中的全部列。即在執行此查詢期間,SQL Server 不須要訪問與 table1 相關的數據頁。SQL Server 使用索引 indexname1 能夠得到知足查詢所須要的所有信息。在 SQL Server 已遍歷與 indexname1 相關的 B 樹,並找到 col2 等於「value」的索引關鍵字範圍,SQL Server 就知道它能夠從覆蓋索引的葉級(底層)提取全部須要的數據 (col1,col2,col3)。這從兩個方面改進了 I/O 性能:
SQL Server 僅從索引頁而不是數據頁獲取全部須要的數據,所以數據的壓縮率更高,使 SQL Server 能夠節省磁盤 I/O 操做。
覆 蓋索引按照 col2 將全部須要的數據以物理方式組織在磁盤上。使硬盤得以連續返回與 where 謂詞 (col2 = "value") 相關的全部索引行。從而爲咱們提供了更好的 I/O 性能。 總而言之,若是覆蓋索引中的全部列的字節數比該表中單行的字節數少,而且能夠確定將反覆執行使用此覆蓋索引的查詢,那麼使用覆蓋索引是有意義的。
5:位圖索引
這個不是sqlserver的索引,它是oracle的,因此請不要混淆。之因此提出來,是由於它不是B*樹結構的索引。位圖索引相對於B*tree索引 來講,它的存儲結構是不同的,一般在B*tree索引中,在索引條目和行之間有一對一的關係.對於位圖索引,一個索引條目使用一個位圖同時指向許多行. 這對於基本上只讀的低基數(數據只有不多的幾個大相徑庭的值)數據是合適的.好比說,一個person表,有個性別字段sex,Y表明男,N表明女,對於 有幾百萬行數據的表來講, 位圖索引是一個很是好的選擇。它能夠迅速的掃描出來,而不用象對B*樹索引那樣的查找。
3.2 有效的利用索引
索引在數據庫的查詢優化中起着相當重要的做用,一個數據庫索引的好與壞,其查詢性能相差不少倍,下面將談一下各類索引的使用場合和一些觀點。如何選擇索引 可顯著影響所產生的磁盤 I/O,並於是影響查詢性能。在非彙集索引中,選擇性很重要,由於若是在只有少許惟一值的大型表上建立非彙集索引,使用非彙集索引將不會節省數據檢索中的 I/O。由於B*樹結構的索引都注重一種比較性,這樣它能夠快速的肯定範圍,定位位置,例如,person表的性別字段,非男即女,不具備可比性,若是以 它爲非彙集索引,查詢的時候也只能一個個葉節點去比較。在這種狀況下產生的 I/O 可能比對錶進行連續掃描所產生的 I/O 多得多。比較適合非彙集索引的有票據編號、惟一的客戶編號、社會安全號碼和電話號碼,簡單來講,就是基於某種可比較的,有規律的數據。
建立彙集索引以前,應先了解數據是如何被訪問的。
考慮對具備如下特色的查詢使用匯集索引:
使用運算符(如 BETWEEN、>、>=、< 和 <=)返回一系列值。 使用匯集索引找到包含第一個值的行後,即可以確保包含後續索引值的行物理相鄰。例如,若是某個查詢在一系列銷售訂單號間檢索 記錄,銷售單號列的彙集索引可 快速定位包含起始銷售訂單號的行,而後檢索表中全部連續的行,直到檢索到最後的銷售訂單號。
返回大型結果集。
使用 JOIN 子句;通常狀況下,使用該子句的是外鍵列。
使用 ORDER BY 或 GROUP BY 子句。
在 ORDER BY 或 GROUP BY 子句中指定的列的索引,可使數據庫引擎 沒必要對數據進行排序,由於這些行已經排序,這樣能夠提升數據庫性能
通常狀況下,定義彙集索引鍵時使用的列越少越好。考慮具備下列一個或多個屬性的列:
惟一或包含許多不重複的值
例如,僱員 ID 惟一地標識僱員。EmployeeID 列的彙集索引或 PRIMARY KEY 約束將改善基於僱員 ID 號搜索僱員信息的查詢的性能。另外,可對 LastName、FirstName、MiddleName 列建立彙集索引,由於常常以這種方式分組和查詢僱員記錄,並且這些列的組合還可提供高區分度。
按順序被訪問
例如,id惟一地標識person表中的記錄,在其中指定順序搜索的查詢(如 WHERE ID BETWEEN 1000 and 2000)將從id的彙集索引受益。這是由於行將按該鍵列的排序順序存儲。
常常用於對錶中檢索到的數據進行排序。
按該列對錶進行彙集(即物理排序)是一個好方法,它能夠在每次查詢該列時節省排序操做的成本。
彙集索引不適用於具備下列屬性的列:
頻繁更改的列
這將致使整行移動,由於數據庫引擎 必須按物理順序保留行中的數據值。這一點要特別注意,由於在大容量事務處理系統中數據一般是可變的。
寬鍵
寬鍵是若干列或若干大型列的組合。全部非彙集索引將彙集索引中的鍵值用做查找鍵。爲同一表定義的任何非彙集索引都將增大許多,這是由於非彙集索引項包含彙集鍵,同時也包含爲此非彙集索引定義的鍵列。
3.3 談索引使用的誤區
理論的目的是應用。雖然咱們剛纔列出了什麼時候應使用匯集索引或非彙集索引,但在實踐中以上規則卻很容易被忽視或不能根據實際狀況進行綜合分析。下面咱們將根據在實踐中遇到的實際問題來談一下索引使用的誤區。
1:主鍵就是彙集索引
這種想法我認爲不是太合理,大多數狀況下,主鍵上的彙集索引是對彙集索引的一種浪費。雖然SQL SERVER默認是在主鍵上創建彙集索引的。一般,咱們會在每一個表中都創建一個ID列,以區分每條數據,而且這個ID列是自動增大的,步長通常爲1。此 時,若是咱們將這個列設爲主鍵,SQL SERVER會將此列默認爲彙集索引。這樣作有好處,就是可讓您的數據在數據庫中按照ID進行物理排序,但我認爲這樣作意義不大。由於在不少狀況下,由 於主鍵的惟一性,對id或者主鍵進行範圍掃描 是比較少的。顯而易見,彙集索引的優點是很明顯的,而每一個表中只能有一個彙集索引的規則,這使得彙集索引變得更加珍貴。 從咱們前面談到的彙集索引的定義咱們能夠看出,使用匯集索引的最大好處就是可以根據查詢要求,迅速縮小查詢範圍,避免全表掃描。
在實際應用中, 由於ID號是自動生成的,咱們並不知道每條記錄的ID號,因此咱們很難在實踐中用ID號來進行查詢。這就使讓ID號這個主鍵做爲彙集索引成爲一種資源浪 費。其次,讓每一個ID號都不一樣的字段做爲彙集索引也不符合「大數目的不一樣值狀況下不該創建聚合索引」規則;固然,這種狀況只是針對用戶常常修改記錄內容, 特別是索引項的時候會負做用,但對於查詢速度並無影響。 若是您的彙集索引盲目地建在ID這個主鍵上時,查詢速度不必定會提升的,即便你在其餘字段上創建非彙集索引。下面咱們就來看一下在200萬條數據量的狀況 下各類查詢的速度表現:
(1)全表掃描
只在主鍵上創建彙集索引:
Select id,name,dept,emdate from person
用時:20546毫秒(即:21秒)
不在主鍵上創建彙集索引,只建普通索引
Select id,name,dept,emdate from person
用時:17923毫秒(即:18秒)
以上查詢執行的實際上索引不會發揮做用,由於提取的是所有數據。彙集索引在這裏會耗費更多的資源,因此會看到,不創建彙集索引比創建彙集索引還要快
(2):按日期進行過濾(用到索引)
在主鍵上創建彙集索引,在emdate上創建非彙集索引:
select id,name,dept,emdate from person where emdate>dateadd(day,+1,getdate())
用時:12376毫秒(12秒)
在主鍵上創建彙集索引,在emdate上沒有索引:
select id,name,dept,emdate from person where emdate>dateadd(day,+1,getdate())
用時:21296毫秒(21秒)
在主鍵上創建非彙集索引,在emdate上創建非彙集索引:
select id,name,dept,emdate from person where emdate>dateadd(day,+1,getdate())
用時:11590毫秒(12秒)
在主鍵上創建非彙集索引,在emdate上創建彙集索引:
select id,name,dept,emdate from person where emdate>dateadd(day,+1,getdate())
andemdate<dateadd(day,+3,getdate())
用時:5233毫秒(5秒)
雖然每條語句提取出來的都是30萬條數據,各類狀況的差別倒是比較大的,特別是將彙集索引創建在日期列時的差別。事實上,若是您的數據庫真的有幾千萬條記錄的話,差距會更明顯。
2:只要創建索引就能顯著提升查詢速度
這個想法是錯誤的。事實上,咱們能夠發現上面的例子中,上面按日期過濾的語句徹底相同,且創建索引的字段也相同,但查詢速度卻有着很是大的差異。因此,並 非是在任何字段上簡單地創建索引就能提升查詢速度。索引的創建,會帶來更多的系統開銷,由於系統要耗費資源去維護它 ,若是創建了沒有用到的索引,不適當的索引,過多的索引,反而會致使查詢性能降低。總之索引的創建,要看錶的結構,數據的分佈,還有你要用到哪些數據,如 果把索引創建在你根本不須要的數據列上,是根本不會發揮做用的。
3:把全部須要提升查詢速度的字段都加進彙集索引,以提升查詢速度
這個不必定正確。上面已經談到。假設如今查詢要用到用戶名和日期這兩個字段,咱們能夠把他們合併起來,創建一個複合索引(compound index)。 不少人認爲只要把任何字段加進彙集索引,就能提升查詢速度,也有人感到迷惑:若是把複合的彙集索引字段分開查詢,那麼查詢速度會減慢嗎?帶着這個問題,我 們來看一下如下的查詢速度(結果集都是25萬條):(日期列emdate首先排在複合彙集索引的起始列,用戶名name排在後列)
select id,name,dept,emdate from person where emdate>'2007-06-01'
查詢速度:1664毫秒
select id,name,dept,emdate from person
where emdate>'2007-06-01' and name=’王小雪’
查詢速度:1640毫秒
select gid,fariqi,neibuyonghu,title from person
where name='王小雪'
查詢速度:5920毫秒
從 以上試驗中,咱們能夠看到若是僅用匯集索引的起始列做爲查詢條件和同時用到複合彙集索引的所有列的查詢速度是幾乎同樣的,而若是僅用複合彙集索引的非起始 列做爲查詢條件的話,這個索引是不起任何做用的。固然,語句一、2的查詢速度同樣是由於查詢的條目數同樣,若是複合索引的全部列都用上,並且查詢結果少的 話,這樣就會造成「索引覆蓋」,於是性能能夠達到最優。同時,請記住:不管您是否常用聚合索引的其餘列,但其前導列必定要是使用最頻繁的列。
3.4 其餘索引經驗總結
1:用聚合索引比用不是聚合索引的主鍵速度快
下面是實例語句:(都是提取25萬條數據)
select id,name,dept,emdate from person where emdate='2007-06-04'
使用時間:906毫秒
select id,name,dept,emdate from person where id<=100000
使用時間:1153毫秒
這裏,用聚合索引比用不是聚合索引的主鍵速度略快一些。
2:用聚合索引比用通常的主鍵做order by時速度快,特別是小數據量時
select id,name,dept,emdate from person order by emdate
用時:17856 (約18秒)
select id,name,dept,emdate from person order by id
用時:44046 (約45秒)
這裏能夠看到,用匯集索引比用通常的主鍵做order by時,速度幾乎快了2.5倍。事實上,有的資料說小數據量狀況下,用匯集索引排序列比非彙集索引做爲排序列快,10萬以上,則兩者的速度差異不明顯。但 據當前200萬條數據狀況來看,在大數據量的狀況下,這個結論依然成立。
3:使用聚合索引內的時間段,搜索時間會按數據佔整個數據表的百分比
比例減小,而不管聚合索引使用了多少個
select id,name,dept,emdate from person where
emdate='2007-06-04 00:00:00.000'
用時:1123毫秒(提取10萬條)
select id,name,dept,emdate from person where
emdate='2007-06-04 00:00:00.000'
用時:1843毫秒(提取20萬條)
select id,name,dept,emdate from person where
emdate='2007-06-09 00:00:00.000'
用時:4500毫秒(提取45萬條)
從以上統計的數據看來,這個規律基本上是正確的
其餘注意事項
「水可載舟,亦可覆舟」,索引也同樣。索引有助於提升檢索性能,但過多或不當的索引也會致使系統低效。由於用戶在表中每加進一個索引,數據庫就要作更多的 工做。過多的索引甚至會致使索引碎片。因此說,咱們要創建一個「適當」的索引體系,特別是對聚合索引的建立,更應精益求精,以使您的數據庫能獲得高性能的 發揮。在實際的開發中,會遇到不少意想不到的狀況,最好是多測試一些方案,找出哪一種方案效率最高、最爲有效。
4:SQL語句改善
一個sql語句大約要通過三個階段,編譯優化,執行,取值,而編譯階段,而第一階段大部分狀況下都要花掉60%的時間,因此綁定變量是很重要 的,sqlserver和oracle都有緩存區,存放最近使用的sql語句,當有一條sql語句到達數據庫服務器時,數據庫會首先搜索緩存區,看它是否 存在能夠重用的sql語句,若是存在,則無需編譯優化,由於緩存區的sql語句都是編譯優化好了的,能夠直接執行,節省至關多的時間。若是沒有發現該語 句,則必需要徹底經歷語句編譯分析,優化計劃,安全檢查等過程,這不只耗費了大量的cpu功率,並且還在至關長的一段時間內鎖住了一部分數據庫緩存,這樣 執行sql語句的人越多,等待的時間越長,系統的性能會大幅度的降低。
不少人不知道SQL語句在SQL SERVER中是如何執行的,他們擔憂本身所寫的SQL語句會被SQL SERVER誤解。好比:
select id,name,dept,emdate from person
where name='王小雪' and id<100000 用時:1220毫秒
和執行:
select * from table1 where id< 100000 and name='王小雪' 用時:1173毫秒
一 些人不知道以上兩條語句的執行效率是否同樣,由於若是簡單的從語句前後上看,這兩個語句的確是不同,若是id是一個聚合索引,那麼後一句僅僅從表的 100000條之內的記錄中查找就好了;而前一句則要先從全表中查找看有幾個name='王小雪'的,然後再根據限制條件條件id<100000來 提出查詢結果。事實上,這樣的擔憂是沒必要要的。SQL SERVER中有一個「查詢分析優化器」,它能夠計算出where子句中的搜索條件並肯定哪一個索引能縮小表掃描的搜索空間,也就是說,它能實現自動優化。
雖然查詢優化器能夠根據where子句自動的進行查詢優化,但你們仍然有必要了解一下「查詢優化器」的工做原理,如非這樣,有時查詢優化器就會不按照您的 本意進行快速查詢。 在查詢分析階段,查詢優化器查看查詢的每一個階段並決定限制須要掃描的數據量是否有用。若是一個階段能夠被用做一個掃描參數(SARG),那麼就稱之爲可優 化的,而且能夠利用索引快速得到所需數據。 SARG的定義:用於限制搜索的一個操做,由於它一般是指一個特定的匹配,一個值得範圍內的匹配或者兩個以上條件的AND鏈接。形式以下:
列名 操做符 <常數 或 變量> 或 <常數 或 變量> 操做符列名
列名能夠出如今操做符的一邊,而常數或變量出如今操做符的另外一邊。如:
Name=’張三’ ,價格>5000 ,5000<價格 ,Name=’張三’ and 價格>5000
若是一個表達式不能知足SARG的形式,那它就沒法限制搜索的範圍了,也就是SQL SERVER必須對每一行都判斷它是否知足WHERE子句中的全部條件。因此一個索引對於不知足SARG形式的表達式來講是無用的。
介紹完SARG後,咱們來總結一下使用SARG以及在實踐中遇到的和某些資料上結論不一樣的經驗:
1:Like語句是否屬於SARG取決於所使用的通配符的類型
如:name like ‘王%’ ,這就屬於SARG
而:name like ‘%小雪’,就不屬於SARG。
緣由是通配符%在字符串的開通使得索引沒法使用。 如如下查詢
沒有對name進行索引
select id,name,dept,emdate from person where name like '%小雪'
用時 3654毫秒
對name進行非彙集索引
select id,name,dept,emdate from person where name like '%小雪'
用時 3673毫秒
對name進行彙集索引
select id,name,dept,emdate from person where name like '%小雪'
用時 3673毫秒
由以上數據能夠看到,將匹配符號放在被查詢字段的前面,索引根本就不會發生做用,因此這也是要注意的地方,若是不會用到,最好少用
2:or 是否會引發全表掃描
有不少資料上說or會引發全表掃描。
如name=’王小雪’ and emdate>’2007-01-10’不會全表掃描,而
name=’ 王小雪’ or emdate>’2007-01-10’則會,可是據我觀察,狀況不是這樣的.對於這樣的一個sql語句select id,name,dept,emdate from person where name='王小雪' or emdate>'2007-06-08',咱們能夠看sqlserver對於它們的執行計劃
在有彙集索引的狀況下(不管彙集索引建在哪些字段上)
沒有彙集索引可是主鍵索引的狀況下
沒有任何索引的狀況下
由上能夠得出結論,在用到or的時候,若是有彙集索引,就不會引發全表掃描,沒有彙集索引,就會引發全表掃描,因此說,只要用or就會引發全表掃描是片面的,不正確的。
3:非操做符、函數引發的不知足SARG形式的語句
不知足SARG形式的語句最典型的狀況就是包括非操做符的語句,如:NOT、!=、<>、!<、!>、NOT EXISTS、NOT IN、NOT LIKE等,另外還有函數。下面就是幾個不知足SARG形式的例子:
ABS(價 格)<5000 ,Name like ‘%三’ ,有些表達式,如: WHERE 價格*2>5000 ,SQL SERVER也會認爲是SARG,SQL SERVER會將此式轉化爲: WHERE 價格>2500/2 .但不推薦這樣使用,由於有時SQL SERVER不能保證這種轉化與原始表達式是徹底等價的。
4:IN 的做用是否至關與OR
看下面的查詢狀況。
有彙集索引
select id,name,dept,emdate from person where name in('王小雪','聶海')
所花時間:8936ms,
select id,name,dept,emdate from person where name='王小雪' or name='聶海'
所花時間:5390ms,
沒有彙集索引
select id,name,dept,emdate from person where name in('王小雪','聶海')
所花時間:5310ms,
select id,name,dept,emdate from person where name='王小雪' or name='聶海'
所花時間:5326ms,
可見,or 比 in速度快,由於做了彙集索引,因此它們都沒有執行table scan,不過由於彙集索引做用在日期字段emdate上,因此雖然查詢使用了彙集索引,但並不意味着比全表掃描快,其實使用做用在emdate上的彙集 索引查詢,在某種意義上來講,也是一種全表掃描,只不過數據的掃描順序不一樣而已,在這種狀況下,甚至沒有彙集索引反而更快
5:exists 和 in 的執行效率是同樣的
不少資料上都顯示說,exists要比in的執行效率要高,同時應儘量的用not exists來代替not in。但事實上,我試驗了一下,發現兩者不管是前面帶不帶not,兩者之間的執行效率都是同樣的。由於涉及子查詢,我試驗此次用SQL SERVER自帶的pubs數據庫。運行前咱們能夠把SQL SERVER的statistics I/O狀態打開。 語法爲:set statistics io on, 要查看語句的執行過程,打開查詢分析器的消息欄就能夠看到,可是在查詢語句以前要加上set statistics io on
(1)select title,price from titles where title_id in (select title_id from sales where qty>30)
該句的執行結果爲:
表 'sales'。掃描計數 18,邏輯讀 56 次,物理讀 0 次,預讀 0 次。
表 'titles'。掃描計數 1,邏輯讀 2 次,物理讀 0 次,預讀 0 次。
(2)select title,price from titles where exists (select * from sales where sales.title_id=titles.title_id and qty>30)
第二句的執行結果爲:
表 'sales'。掃描計數 18,邏輯讀 56 次,物理讀 0 次,預讀 0 次。
表 'titles'。掃描計數 1,邏輯讀 2 次,物理讀 0 次,預讀 0 次。
咱們今後能夠看到用exists和用in的執行效率是同樣的。
6:用函數charindex()和前面加通配符%的LIKE執行效率同樣
前面,咱們談到,若是在LIKE前面加上通配符%,那麼將會引發全表掃描,因此其執行效率是低下的。但有的資料介紹說,用函數charindex()來代替LIKE速度會有大的提高,但據我測試,發現這種說明也是錯誤的:
select id,name,dept,emdate from person where charindex('小雪',name)>0
用時:4010ms
掃描計數 1,邏輯讀 29905 次,物理讀 0 次,預讀 0 次。
select id,name,dept,emdate from person where name like '%小雪'
用時:4123ms
掃描計數 1,邏輯讀 29905 次,物理讀 0 次,預讀 0 次。
7:union並不絕對比or的執行效率高
不少資料都推薦用union來代替or。事實證實,這種說法對於大部分都是適用的。
(1):select id,name,dept,emdate from person where name='王小雪' or emdate>'2007-06-04'
用時:85626ms。掃描計數 1,邏輯讀 129905 次,物理讀 0 次,預讀 0 次。次。
(2):select id,name,dept,emdate from person where name='王小雪'
union
select id,name,dept,emdate from person where emdate>'2007-06-04'
用時:17373ms。掃描計數 2,邏輯讀 59810 次,物理讀 0 次,預讀 0 次。
看來,用union在一般狀況下比用or的效率要高的多。
5:sqlserver的分區
對於一些超大型的表,分區是很是有用的。分區是一種邏輯概念,和oracle的分區概念是同樣的.在一般狀況下,一個表就是一個總體,當發生數據訪問的時 候,也是對整個表或整個表的索引進行訪問,所謂分區,通俗點講,就是把表按必定的規律劃分紅更小的邏輯單位,當發生訪問的時候,不以表爲單位進行訪問,而 先在表的基礎上,判斷數據在哪一個分區,而後對特定的分區進行訪問.正確的分區有利於提升查詢性能.例如,有一個很是大的表,存儲了一些銷售記錄,如今查詢 老是按銷售季度來執行這個查詢----每一個銷售季度包含幾十萬個記錄,一般你只是要查詢這個數據集的一個至關小的數據,可是給予銷售季度的檢索卻的確是不 太可行的.這個索引可能指向無數個記錄,而以這種方式執行索引範圍掃描是可怕的.爲了處理許多查詢任務,系統須要執行全表掃描,可是結果卻必須掃描幾百萬 個記錄,其中絕大部分不使用咱們的查詢任務.使用智能分區方案,就能夠按季度隔離數據.這樣當咱們爲任意指定的季度去查詢數據時,結果將只是掃描那個季度 的數據.這是全部可能的解決方案種最好的方案.下面將介紹sqlserver的分區使用.
分區是比較複雜的,以分區的對象來分類的話,則分爲兩種,表分區和索引分區。
表分區主要指的是範圍分區,(貌似比較 單一,oracle裏有散列分區等等,不過在sqlserver裏我目前尚未看到).就這麼說可能不清不楚,下面將以咱們已經創建好的ipanel數據 庫爲例,對person表進行按日期分區,假設ipanel每月都要進出幾十萬人,而後HR每個月還要做不少的統計吧。下面一步一步來,common
建立文件組
各類數據最終是存儲在數據文件裏,在實際應用中,表的分區都會分佈在多個數據文件中,這樣以便得到更好的 I/O 平衡,對於文件,是以文件組爲單位進行管理,文件組至關於目錄,數據文件就至關於目錄裏的文件。爲數據庫添加文件組,這個文件組分佈存儲person表的 數據:
ALTER DATABASE ipanel ADD FILEGROUP [person_fg]
如今爲ipanel數據庫建立了一個名爲person_fg的文件組。下面爲該文件組添加數據文件。
添加數據文件
ALTER DATABASE ipanel
ADD FILE
(NAME = N'person001',
FILENAME = N'C:\ipanel\person001.ndf',
SIZE = 5MB,
MAXSIZE = 100MB,
FILEGROWTH = 5MB)
TO FILEGROUP [person_fg]
如上,爲文件組添加了一個數據文件
建立分區函數
既然分區,那麼就應該有一個分區的標準,就是說數據將以什麼標準來分區,分區函數就是作這件事情的,它定義數據劃分的標準,對錶進行邏輯上的劃分。
CREATE PARTITION FUNCTION personRangePFN(datetime)
AS
RANGE LEFT FOR VALUES ('20030930',
'20050930',
'20070930',
'20090930')
上 面的分區函數建立了5個分區,而且定義了分區列的數據類型爲datetime,由於分區的標準要建在表的某一列上,在此定義,分區列必須是日期時間型。 RANGE LEFT表示範圍分區,LEFT所在的選項有兩個:LEFT,RIGHT.分區標識着數據的上界和下界。如當前選項是LEFT,則表示:
分區1:<=20030930
分區2:>20030930,<=20050930
分區3:>20050930,<=20070930
分區4:>20070930,<=20090930
分區5:>20030930
若是當前選項是RIGHT,則表示:
分區1:<20030930
分區2:>=20030930,<20050930
分區3:>=20050930,<20070930
分區4:>=20070930,<20090930
分區5:>=20030930
建立分區架構
建立分區函數後,必須將其與分區架構相關聯,以便將分區定向至特定的文件組。定義分區架構時,即便多個分區位於同一 個文件組中,也必須爲每一個分區指定一個文件組。對於前面建立的範圍分區 (personRangePFN),存在五個分區;最後一個空分區將在 PRIMARY 文件組中建立。由於此分區永遠不包含數據,因此不須要指定特殊的位置
CREATE PARTITION SCHEME PersonEmdateScheme
AS
PARTITION personRangePFN
TO ([person001], [person002], [person003], [person004], [PRIMARY])
建立分區表
定義分區函數(邏輯結構)和分區架構(物理結構)後,便可建立表來利用它們。表定義應使用的架構,而架構又定義函數。要將這三者結合起來,必須指定應該應 用分區函數的列。範圍分區始終只映射到表中的一列,此列應與分區函數中定義的邊界條件的數據類型相匹配。另外,若是表應明確限制數據集(而不是從負無窮大 到正無窮大),則還應添加 CHECK 約束。
CREATE TABLE [dbo].[person] (
[id] [bigint] NOT NULL , --記錄的id
[name] [varchar] (10) COLLATE Chinese_PRC_CI_AS NULL ,--姓名
[age] [int] NULL ,--年齡
[addr] [varchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,--地址
[sex] [char] (10) COLLATE Chinese_PRC_CI_AS NULL ,--性別
[dept] [varchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,--部門
[pos] [varchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,--郵編
[tel] [char] (15) COLLATE Chinese_PRC_CI_AS NULL ,--電話
[fax] [char] (15) COLLATE Chinese_PRC_CI_AS NULL ,--傳真
[emdate] [datetime] NULL --入職日期
)
On PersonEmdateScheme (emdate)
若是要限制的emdate的值的範圍,則必須給它加上約束,如只容許emdate的值從2002年9月1日到2010年9月1日,
則將[emdate] [datetime] NULL 改成
[emdate] [datetime] NULL
CONSTRAINT personRangeYear
CHECK ([emdate] >= '20020901'
AND [emdate] <= '20100901 11:59:59.997')
分區總結
到此,對於分區表person已經設置完畢,person的數據會根據emdate的值分佈到幾個不一樣的數據文件裏,在查詢的時候,系統會首先判斷 emdate的值,看它在哪一個分區,而後只進入該分區查找數據,這對於超大規模的系統來講,是頗有用的,若是一個表有幾千萬上億的數據,即便是索引掃描也 是一個很費時的過程,不要忘記,索引也就像至關於簡化了的表。對於索引,sqlserver裏有索引分區,若是索引分區和表分區對齊的話,就是說和表同樣 使用了相同的分區函數和相同的分區架構,那麼對於索引的查找,就不是對整個索引的查找了,而是先判斷在哪一個索引分區,而後再取查找該索引值,而後找到數 據,這樣就會節省不少時間。分區還有一個好處就是,對於一些數據能夠更好的進行管理,好比說,定義了2006年度的銷售數據存儲在對應的分區area6, 而area6對應的數據文件是sale006.ndf,到2007年的時候,通常狀況下,可能不用06年的數據,按照分區的理論,它也不會訪問06年的數 據所在的區域。
分區的應用是比較複雜的,上面只是介紹了其中一部分,其餘還有索引分區,分區合併,分區移出等比較多的的東西。在oracle 裏,分區的概念是比較多的,包括對索引的分區都會有不少介紹,如散列分區,混合分區,局部索引,全局索引,原理上是差很少的。在此談分區只是一個拋磚引玉 的過程,若是對sqlserver分區想更深刻了解的話,能夠看看msdn,有中文的,不過翻譯得很爛。
6:後記
我之前看到過不少項目,數據庫系統只是被純粹的看成了一個存儲數據的地方,建完表能增刪改查就萬事大吉了,有的連索引都沒有,對於數據庫的創建也很不嚴 謹,更談不上管理,雖然不少人認爲數據庫的管理是DBA的事情,可是我想做爲一個技術人員,加深對數據庫的瞭解是絕對沒有壞處的,開發大型的系統,數據庫 確定是很是重要的。若是想深刻學習一門數據庫的話,我建議你們從oracle開始學,由於sqlserver做了不少封裝,而oracle更爲複雜,是 的,雖然它概念不少,比較複雜,可是卻有助你瞭解更多的數據庫細節,在不少方面,大部分的數據庫系統都是相同的,oracle學好再來學其餘的數據庫,上 手就很是容易,若是你會寫PL/SQL程序,那有什麼理由不會寫TRANACT-SQL的數據庫程序呢,語法只是一些細微的差異,而不少的概念倒是相同 的。
其餘方法: