1. 程式人生 > >SQL Server 查詢優化 索引的結構與分類

SQL Server 查詢優化 索引的結構與分類

一、索引的結構

關係型資料庫中以二維表來表達關係模型,表中的資料以頁的形式儲存在磁碟上,在SQL SERVER中,資料頁是磁碟上8k的連續空間,那麼,一個表的所有資料頁在磁碟上是如何組織的呢?分兩種情況:一是資料頁間無序、隨機地儲存在磁碟上,這樣的表叫做堆表;二是資料頁間按某個表字段的值有序地儲存在磁碟上,這樣的表做索引組織表。

索引是什麼?從物理結構上可分為兩種:聚集索引和非聚集索引。將表中的資料有序地組織起來的索引稱為聚集索引,一個表只有一個聚集索引,表上其他的索引都是非聚集索引。

1.1、聚集索引結構

(1)聚集索引將表內的資料進行有序的組織,並不是指磁碟上資料頁內資料的物理順序,也不是指資料頁在磁碟上的物理順序,而是資料頁間邏輯上以樹型結構連結起來;

create table t

(

ID       int,

NAME     varchar(100)

AGE      int,

)

GO

INSERT INTO t VALUES(1,'張一',20)

INSERT INTO t VALUES(2,'張二',25)

INSERT INTO t VALUES(4,'張三',21)

INSERT INTO t VALUES(5,'李二',23)

INSERT INTO t VALUES(7,'李三',24)

INSERT INTO t VALUES(8,'李四',22)

GO

create CLUSTERED index IX_t_ID on t(ID)

2)聚集索引的非葉子節點(即索引節點)行中,只包含下一節點的第一個鍵值及指向下一節點的指標,指標的格式為:檔案編號+頁編號,長度為2Byte+4Byte=6Byte;

(3)聚集索引的葉子節點行就是表中的資料行;

(4)沒有聚集索引的表,結構如下:

堆表中的資料頁沒有經過組織,隨機的存放在磁碟上,通過IAM頁進行管理,可以知道哪些資料頁屬於某個表以及資料頁的分配情況,對於資料頁的結構及資料庫引擎對其管理,這裡不多作介紹,詳見《SQL Server 儲存引擎》系列。

(5)資料庫引擎根據系統目錄判斷當前表是否為索引組織表,以選擇索引組織表的root_page或堆表的first_IAM_page及first_page,即可對錶進行掃描;

(6)索引組織表和堆表包含的資料行是一樣的,只是組織形式不同而已;

1.2、非聚集索引結構

(1)非聚集索引是對聚集索引的索引;

(2)非聚集索引的索引節點行和聚集索引一樣,只包含下一節點的第一個鍵值及指向下一節點的指標,指標的格式為:檔案編號+頁編號,長度為2Byte+4Byte=6Byte;

(3)非聚集索引的葉子節點行儲存的是索引列和書籤。如果是索引組織表,書籤為聚集索引鍵;如果是堆表,書籤為ROWID,長度為8Byte,即資料頁號(4Byte)+檔案號(2Byte)+槽號(2Byte)的行定位串;

create NONCLUSTERED index IX_t_AGE on t(AGE)

GO

SELECT * FROM t WHERE AGE=20

此時想要根據年齡20來查詢資料。

如果是索引組織表,先根據AGE上的非聚集索引找到ID,此時ID值為1,然後再將ID=1帶入聚集索引進行等值查詢,最終在聚集索引的葉子節點得到該行的所有資料;

如果是堆表,先根據AGE找的應該是ID=1這一行資料的8位元組的ROWID,然後再根據這個ROWID去找到該行的所有資料,即(1,'張一',20);

(4)無論是聚集索引還是非聚集索引的葉子節點上都有一個指向上下頁的指標。

二、索引分類

1、SQL SERVER中索引分類

(1)B+樹索引

目前關係型資料庫中一種常見的索引組織結構。B+樹,它是一多叉平衡排序樹,直到葉子節點才會命中資料,以下簡稱B樹,可參見相關《資料結構》的書籍;

(2)、全文索引

目前關係型資料庫一種基於標記的索引組織結構,它不是B樹結構,而是基於要索引的文字中的各個標記來建立倒排、堆積且壓縮的索引結構。

(3)、XML索引

隨著XML文字的應用,在各個關係型資料庫中也相繼提供了對這種資料結構的支援。XML 例項作為二進位制大型物件 (BLOB) 儲存在 xml 型別列中。對於列中的每個 XML物件,索引將建立幾個資料行。該索引中的行數大約等於 XML物件中的節點數。

1.2、B樹索引的分類

1.2.1、物理結構分類

(1)聚集索引

根據索引列值,按B樹結構對錶內資料進行組織;

(2)非聚集索引

對聚集索引的索引;

1.2.2、列值唯一性分類

(1)唯一索引

表中索引列的值唯一。

SQL SERVER在唯一性上認為NULL是相等的(ORACLE中唯一鍵是可以插入多個NULL值的),即唯一鍵中只允許出現一個NULL值,但在比較運算中認為NULL是不相等的,這點要注意;

(2)非唯一索引

表中索引列的值不唯一;

1.2.3、列個數分類

(1)複合索引

包含多個列的索引;

(2)單列索引

只包含一個列的索引。

1.2.4、特殊索引

(1)計算列索引

計算列上的索引;

(2)檢視索引

對檢視建立索引。

一、遍歷

索引樹的每個節點都是一個頁面。

索引樹有三種類型的節點:根節點、中間節點、葉子節點。

根節點與中間節點一樣,只包含下一層節點的入口值與入口指標,它們稱為索引節點;

葉子節點包含要遍歷的資料,對聚集索引而言資料就是表中資料行,對非聚集索引資料是指索引列值和行書籤。

索引的遍歷總是從根節點開始,即先根遍歷,分為兩種:索引掃描和索引查詢。

索引掃描是指從索引樹的根節點開始,對葉子節點逐個掃描,直至命中所有滿足查詢條件的資料;

索引查詢是指從索引樹的根節點開始,按查詢值在索引節點中根據路由資訊跳轉,直至葉子節點以命中資料。

B+樹的深度通常小於等於3,計算如下:

以聚集索引為例,簡單計算如下:10個INT列寬度總和為40B,假設聚集索引樹每一層為二叉,共三層,即2^0+2^1+2^2=1*(1-2^3)/(1-2)=7個頁面,4個葉子節點,每個頁面8060K可儲存8060000/40=201500行,乘以4=806000行,如果是三叉、四叉,那麼三層可儲存上千萬至億行的資料,當然在資料量達到這個等級時,通常我們會選擇表分割槽,那麼B樹深度就更不會突破三層了。

所以索引查詢的效率是很高的,在查詢中應該努力構造索引查詢,避免索引掃描。

二、插入

2.1、頁空間充足

在已存在資料的表上,建立或重建索引時,可指定填充因子,即在索引樹的每個節點上預留一定的空間,供表中後續增加的資料使用。但如果在建立表的時候就建立了索引,並指定了填充因子,這時的填充因子是無用的,資料庫系統不會刻意去保留頁面的空間。

索引頁面有剩餘空間的情況如下圖:


 圖
1

參考圖1,此時向索引樹中插入一條索引鍵值為31的記錄,步驟如下:

(1)執行索引鍵值=31的查詢操作,確定該新記錄應該插入到葉子節點L2中。

(2)檢查L2上是否有足夠的空間來存放當前記錄,這裡假設有足夠的空間;

(3)將記錄45向後移動,插入索引鍵值為31的新記錄。插入之後,10、30、31、45還是順序的,如下圖:

圖2

 2.2、頁空間不足

 參加圖2,此時再插入一條索引鍵值為32的記錄,步驟如下:

(1)執行索引鍵值=32的查詢操作,確定該新記錄應該插入到葉子節點L2中;

(2)檢查L2上是否有足夠的空間來存放當前記錄,這時發現沒有足夠的頁空間,此時需要進行頁面分裂;

(3)向資料庫系統申請一個新的頁面L4,將L2的一半資料移到L4中,並重新連結葉子的左右節點,如下圖:

圖3

(4)此時,上層節點也需要生成一個新的葉子節點的指標。這裡的上層節點即根節點,如果上層節點沒有剩餘空間的話,同樣也需要進行分裂,這裡有剩餘空間,如下圖:

圖4

(5)因為當前記錄的鍵值範圍位於頁分裂的後一半中,將索引鍵值為32的新記錄插入到L4中,如果鍵值範圍位於前一半,則插入到L2中。如果L4的空間不夠存放鍵值為32的新記錄,則L4會繼續進行頁分裂,這裡假設空間足夠,插入結束,如下圖:

圖5

三、刪除

3.1、刪除葉子節點中的記錄

參考圖5,刪除索引鍵值為32的記錄,步驟如下:

(1)執行索引鍵值=32的查詢操作,確定該記錄在L4中;

(2)將索引鍵值=32的記錄標記為虛影,但並不立即釋放空間,虛影記錄可用於事務回滾、多版本等;

(3)如果此時L4上的虛影記錄空間被申請使用,虛影記錄就會被擦除;

(4)如果資料頁面最後一條記錄也被刪除,資料頁面會被回收;

3.2、刪除非葉子節點中的記錄

(1)索引節點中的指標被刪除時並不是虛影記錄,但同樣也不釋放空間,直到有新的指標插入時,才會進行空間壓縮;

(2)堆表中資料行被刪除後,頁空間不會被回收,即使是空閒分頁也還是標識為分配狀態,無法被其他物件使用;

注:從理論上講,在兄弟節點頁面空閒空間都小於50%時,應該將兄弟節點合併,即分裂的逆操作,但這樣可能帶來的後果是更頻繁的頁面合併、分裂,成本更大,所以在資料庫系統中通常不進行頁面合併操作。

四.更新

4.1、覆蓋更新

如果更新操作能夠在頁內進行原位鍵值替換,那麼就進行覆蓋更新。

4.2、非覆蓋更新

無法進行覆蓋更新時,更新操作被分解為刪除和插入操作。

如果非覆蓋更新過程中,新的記錄比較長,則會在頁面分裂的過程中會帶來資料行的移動:

(1)聚集索引的移動對非聚集索引沒有影響,因為非聚集索引中儲存的是聚集索引的鍵值,分裂並不會改變鍵值;

(2)堆表中的資料頁分裂,會在原記錄處留下一個前轉指標,以告訴非聚集索引去哪裡找新的記錄;

所以資料行的移動對非聚集索引都不會帶來維護的成本,非聚集索引的維護成本來自書籤的變化:

(1)聚集索引的鍵值發生變化或被刪除;

(2)堆表中的資料行被刪除。

一、索引的作用

1、幫助檢索資料;

2、提高聯接效率;

3、節省ORDER BY、GROUP BY的時間;

4、保證資料唯一性(僅限於唯一索引)。

二、索引的設計

在確定要建立一個索引時,首先我們要確定它是聚集還是非聚集、單列還是多列、唯一還是非唯一、列是升序還是降序、它的儲存是如何的,比如:分割槽、填充因子等。下面逐條來看:

1、聚集索引

(1)首先指出一個誤區,主鍵並不一定是聚集索引,只是在SQL SERVER中,未明確指出的情況下,預設將主鍵定義為聚集,而ORACLE中則預設是非聚集,因為SQL SERVER中的ROWID未開放使用。

(2)聚集索引適合用於需要進行範圍查詢的列,因為聚集索引的葉子節點存放的是有序的資料行,查詢引擎可根據WHERE中給出的範圍,直接定位到兩端的葉子節點,將這部分節點頁的資料根據連結串列順序取出即可;

(3)聚集索引儘量建立在值不會發生變更的列上,否則會帶來非聚集索引的維護;

(4)儘量在建立非聚集索引之前建立聚集索引,否則會導致表上所有非聚集索引的重建;

(5)聚集索引應該避免建立在數值單調的列上,否則可能會造成IO的競爭,以及B樹的不平衡,從而導致資料庫系統頻繁的維護B樹的平衡性。聚集索引的列值最好能夠在表中均勻分佈。

2、非聚焦索引

(1)非聚集索引適合用於需要進行等值查詢的列,因為非聚集索引的葉子節點存放的是有序的索引列與書籤的對映行,查詢引擎可根據WHERE中給出的值,得到書籤,繼而定位到資料行;

(2)覆蓋索引(Covering Index),是非聚集索引的一種特殊且高效的應用,就是將需要返回的資料列設計成組合索引,在SELECT時只查詢索引中存在的資料列,這樣就能形成索引覆蓋,因為索引行中已經包含了想到的資料,不需要再進行書籤查詢;

在SQL SERVER 2005及以上版本中,提供了INCLUDED關鍵字,可以在非聚集索引中包含更多列,也是覆蓋索引的一個有效引申;

(3)非聚集索引建立在值具有單調性的列上,比如:自增列(單調遞增),可以減少索引的外部碎片及索引結構的維護;

3、複合索引

(1)複合索引建立在多個列上。上面已經講過,在非聚集索引中,可以利用覆蓋索引來提高檢索的效率,但如果組合索引的列太多的話,那麼對於這個索引的維護成本也會加大,DML的效率將會下降,而且索引的查詢路徑會變長;

(2)在建立複合索引時,應該將高選擇性的列放在前面,即作為引導列;

4、唯一索引

(1)再指出一個誤區,聚集索引並不一定是唯一索引,由於SQL SERVER將主鍵預設定義為聚集索引,事實上,索引是否唯一與是否聚集是不相關的,聚集索引可以是唯一索引,也可以是非唯一索引;

(2)將索引設定為唯一,對於等值查詢是很有利的,當查到第一條符合條件的紀錄時即可停止查詢,返回資料,而非唯一索引則要繼續查詢,同樣,由於需要保證唯一性,每一行資料的插入都會去檢查重複性;

5、分割槽索引

(1)SQL SERVER從2005引入的分割槽表的概念,對於在分割槽表上建立的索引,無論是否包含分割槽列,在未指定分割槽方案或檔案組的情況下,均會使用分割槽表的分割槽方案來建立該索引。這一點與ORACLE正好相反;

6、其他索引

(1)篩選索引,在ORACLE中叫域索引,這是SQL SERVER 2008新增的功能,適用於表中資料密度不高的列來建立索引,比如:表中某列大多是NULL或某相同值,那麼此時可通過WHERE關鍵字來篩選這些相同值來建立索引,這樣在對非相同的值進行檢索時,即可以用到這個索引;

(2)計算列索引,在ORACLE中叫函式索引,如果表中存在計算列,但想要對計算列進行檢索,可在計算列上建立索引,但前提是必須要先持久化該計算列;

(3)索引檢視,在ORACLE中叫物化檢視,我們知道檢視的資料都是來源於基礎表,在SQL SERVER中對檢視建立索引,事實上就對檢視進行了物化,然後才建立索引,索引檢視多用於靜態資料的查詢,畢竟物化檢視的更新是比較麻煩的;

(4)點陣圖索引,對BOOL型欄位建立索引,在SQL SERVER中暫未提供這樣的功能;

(5)XML索引,對XML列只能建立XML索引;

(6)全文索引,LOB型別列上無法建立普通索引,比如VARCHAR(MAX)、TEXT、IMAGE,如果需要檢索可以考慮建立全文索引,當然全文索引也可以用在非LOB型別的列上;

7、索引引數

(1)填充因子,索引的葉子節點的填充程式,預留一定的頁空間,以避免過多的頁拆分,但如果預留空間太大,會放大查詢的成本。對於索引的中間節點如果也使用填充因子可以開啟PAD_INDEX選項;

(2)忽略重複鍵,這個選項通常用於去重處理,對於將要插入到表中的行,如果存在相同索引鍵,則拋棄;

(3)根據業務查詢需要,決定列升序還是降序,通常對於單列索引存在正向或反向掃描,但在複合索引的非引導列中檢索時,如果存在需要倒排的查詢,則在建立索引時應選擇降序;

(4)在索引的儲存上,將聚集和非聚集索引定義在不同磁碟分割槽的檔案組上,減少IO競爭,亦可利用多CPU併發,但目前通常都是RAID5+1(1備份5中的一塊硬碟)的儲存,資料存放本就已分散,這樣做的意義並不是太大。

8、索引列選擇

(1)參考[索引的作用]選擇索引列,比如:如果表中存在外來鍵,考慮在該列上建立索引,可增加參照完整性檢查的速率,同樣,如果存在連線,也可提高連線的速率;

(2)索引選擇性公式:selectivity=unique keys/rows,一般當選擇性低於0.1即10%時,查詢優化器拒絕使用該索引,比如:BOOL型別的列。此處有一個矛盾,有可能我們無法知道當前列的selectivity,那麼只能採用估算的方式,比如採用SQL SERVER 2005的TABLESAMPLE進行資料取樣等;

(3)在面對一個多條件的查詢時,應該選擇過濾性較強的欄位作為單列索引,或作為複合索引的引導列;

(4)在長度較小的資料型別上建立索引,這樣每條索引儲存所佔用的空間就會變小,每個索引頁上就能夠儲存更多的索引行,縮短索引查詢的路徑;

(5)不要在一個表上建立太多的索引,索引的維護會影響DML的效率;

三、索引的使用

1、避免無效搜尋表示式

(1)WHERE條件中將索引列放在表示式中:如fun(c1) = 1,c1+c2 = 2等;

(2)索引列在未知數值起點的LIKE運算子中:如Like ‘%abc’或like ‘%abc%’;

(3)包含否定運算子的表示式,如:<>、not in/exists/like;

(4)NULL表示式,如:is null、is not null;

總之,無法讓檢索值與索引值,進行比較運算的表示式,都為無效搜尋表示式。

2、複合索引的引導列

比如複合索引列為(c1,c2,c3),則在WHERE條件中必須指定c1,才會用到該複合索引;

以上兩點總的原則就是:構造索引查詢,避免索引掃描,我們通常所說的:能不能用到索引,也應該是指索引查詢,而不是索引掃描。

3、儘量使用等值運算子

比如在使用WHERE c1>1時,SQL SERVER會盡量去將它轉會為WHERE c1>=2(如果整形的話),以給索引查詢一個起點,如果無法隱式轉換為>=,則會以c1>=1為起點,查詢完成後再踢除=1的資料;

4、表示式的順序

在WHERE條件中包含多個表示式時,應該將過濾性最強的表示式放在靠近WHERE的位置,通常這樣的表示式中的列,也會選擇在上面建立索引,因為在一個WHERE子句中只有一個表示式是可以使用索引的,其他的表示式都是基於索引查詢結果集的過濾,所以應儘量保證有效索引的使用。儘管查詢優化器會去識別,然後自動去重排表達式的順序,但我們應該養成一個好的習慣。

一、索引碎片

無論是索引組織表(IOT)還是堆表(HEAP),隨著資料的增刪改,都會或多或多的產生碎片。碎片的存在,主要對於資料掃描效率有著較大的影響,對於資料查詢效率幾乎沒有影響或者說影響很小,如果想要改善資料查詢的效率,進行索引碎片整理並沒有什麼效果。以下的碎片分類也主要是從資料掃描著眼。

1、內部碎片

內部指的是頁內,即頁面的空閒空間。其實填充因子就是一種碎片,為了減少頁拆分,寧願適當地去製造這種碎片。但在大量內部碎片一直處於無法被資料填充的情況下,是沒有益處的,它會導致掃描過程中讀取額外的頁面。

對於LOB和ROW_OVERFLOW_DATA頁面,這是唯一的碎片形式,因為在這兩種列上無法建立B樹索引。

2、外部碎片

(1)邏輯碎片

索引葉子節點頁的邏輯順序與物理順序不一致,比如:有頁號1,2的兩個頁面,此時1頁面發生頁拆分,這時新申請的頁面頁號為3,此時邏輯順序為1-3-2,但物理順序是1-2-3,1頁面沒有直接指向磁碟的下一個物理頁,這就造成了不一致,即邏輯碎片;

(2)擴充套件碎片

SQL SERVER通常給表或索引分配新的空間是以EXTENT(區或擴充套件)的形式,一個區是8個頁面,所以區的第一個頁號應該是8的倍數,比如:一個包含有序區的表,第一個頁面的頁號應該是8-16-24,這樣下去,如果是8-24,那麼說明第一個頁面頁號為16的區被分配給了另一個表,那麼8-24的表在物理上就存在一個間隙,即擴充套件碎片;

外部碎片是對資料連續性的度量,擴充套件碎片是堆表資料連續性的度量,資料的連續性越差,掃描的成本也會越大。

二、檢視與管理

對於索引樹的管理主要考慮兩方面:一是B樹的平衡性,這一點資料庫系統會自動維護;二是索引碎片,這需要手動去維護。

1、碎片的檢視

SQL SERVER支援兩種碎片檢視方式,至於這兩種碎片檢視方式的使用方法,幫助文件裡有很詳細的說明。

(1)dbcc showcontig是SQL SERVER 2000中的唯一碎片檢視方式,在SQL SERVER 2005中無法支援LOB型別、ROW_OVERFLOW_DATA及整個分割槽表的碎片檢視;

(2)sys.dm_db_index_physical_stats是SQL SERVER 2005新的碎片檢視方式;

對於碎片的檢測以及是否需要進行碎片處理,主要從以下幾個引數來看:

(1)內部碎片檢測

avg_page_space_used_in_percent:頁面空間平均使用比例;

對於資料掃描而言,該引數越大越好,這意味著讀取較少的頁面即可返回想要的資料。

fragment_count:IN_ROW_DATA碎片的數量;

avg_fragment_size_in_pages:IN_ROW_DATA碎片的平均頁大小;

以上引數反應了行內資料頁碎片的數量及碎片的大小,即便碎片數量很多,但如果碎片很大即很整塊的話,通常要大於64KB即一個EXTENT,對於資料掃描而言,效率也是很高的,因為SQL SERVER會跳過這些整塊的碎片。

ghost_record_count和version_ghost_record_count:虛影紀錄數;

對於資料掃描而言,該引數越小越好。

forwarded_record_count:前轉紀錄數;

前轉紀錄只會在堆中存在,對於資料掃描而言,是非常有幫助的。

(2)外部碎片檢測

avg_fragmentation_in_percent:頁面平均外部碎片的比例;

對於資料掃描而言,該引數越小越好。

2、碎片的整理

(1)、索引重建

顧名思義,重新建立索引,對索引的資料進行重新排列。在SQL SERVER 2000中這是一個離線操作,即索引重建完成前,索引無法訪問或使用,在SQL SERVER 2005/2008中支援聯機操作。

聯機過程的實現,就是先維護另一份新索引,然後同步舊索引中變化的部分,同步完成後使用架構鎖鎖定舊索引,切換表到新的索引上,再釋放架構鎖,切換的過程是資料庫系統目錄的維護過程,速度很快,所以可以理解為是聯機操作。

索引重建的方式有四種:

A:drop index再create index

這種方法是最差的,它會導致表上非聚集索引的兩次重建,一次在DROP時指向ROWID,一次在CREATE時重新指向聚集鍵。以下三種方法只需重新非聚集索引一次。

而且對於主鍵約束或唯一鍵約束產生的索引無法直接刪除,必須要先刪除約束,才可以刪除索引;

B:create index的drop_existing選項

這種方法完全重建索引,並可以重新指定索引引數;

C:alter index的rebuild選項

這是SQL SERVER 2005/2008新的重建索引的方式,它不會重建非聚集索引,除非指定ALL關鍵字,同時它也更靈活,可以針對表上某個分割槽重建索引;

D:dbcc dbreindex;

這是SQL SERVER 2000時重建索引的方法,它預設使用表上原來create index的引數重建索引。填充因子可重新指定。

(2)、索引重組

將索引樹的葉節點頁重新排序,以消除外部碎片。這是一個聯機操作。但不同於索引重建的是,索引重組後統計資訊不會得到更新,而且索引重組使用的是氣泡排序法,效率比較低。同時,索引重組只能在單個檔案內進行,無法跨檔案重組索引。

索引重組的方式有兩種:

A:alter index的reorgnize選項

這是SQL SERVER 2005/2008新的重組索引的方式,和dbcc indexdefrag一樣,它預設對錶上所有分割槽進行索引重組,不可以重新指定填充因子,因為只是索引葉子節點的重新排序。但它比dbcc indexdefrag的選項更豐富些;

B:dbcc indexdefrag

這是SQL SERVER 2000時重組索引的方法。

注意:

(1)碎片是不可避免,但並不是說一旦檢測到碎片就進行碎片整理,有時少量碎片的整理工作反而會帶來更大的成本消耗,所以在不能確定索引整理能否帶來良好效能時,不要輕易進行索引整理。

(2)不管是內部碎片還是外部碎片,即便進行了碎片整理,也並不一定能夠完全消除,對於外部碎片,可以將表獨立存放在一個檔案組上,並在檔案初始化時分配足夠的磁碟空間,這樣的表經過碎片整理後外部碎片可以消除,但太過浪費磁碟空間;

(3)無論是索引重建、索引重組都會對資料頁進行壓縮,頁面的填充程度由填充因子決定。它們都是事務操作,會產生日誌,為了減少事務日誌的大小,可在索引整理後進行日誌截斷或日誌備份。

(4)如有必要,對於DML非常頻繁的表,為了保證資料掃描的效率,可以考慮利用作業來在系統相對空閒的時候定期進行索引整理

物理結構的設計,也即是設計資料在磁碟上的儲存,需要考慮:如何做到安全、IO競爭少、伺服器資源利用率高。

通常當前資料庫只使用一份ONLINE的資料來源,至於分散式儲存已經上升到伺服器架構的設計,這裡不作討論。以下僅簡單描述非分散式儲存的情況,主要從磁碟、檔案、表資料三方面來說。

一、磁碟

RAID0提供了最好的讀寫效能,但RAID0沒有提供冗餘恢復策略,所以很少單獨全用。通常,使用RAID10或RAID5來實現磁碟資料的儲存與容災。

隨著磁碟上資料量的提升,RAID5的讀寫效能會明顯低於RAID10,RAID5至少需要3塊磁碟,RAID10至少需要4塊磁碟,RAID5在同樣提供資料容災的情況下,更能節省成本,但如果成本不在考慮範圍內,當然是效能優先,使用RAID10。

二、檔案

2.1、檔案組(表空間)

在SQL SERVER中叫檔案組,在ORACLE中叫表空間,它們都是對資料檔案的邏輯分組。使用檔案組的目的如下:

(1)效能

檔案組中的檔案放在不同磁碟上,是否可以提高讀寫的效率?在使用RAID1或RAID5的情況下,這種效率的提升已經幾乎可以忽略,因為資料本身已經被分散寫到RAID上的各塊磁碟上;

(2)管理

目前使用檔案組的目的,大都出於管理。

a)在磁碟空間不足的情況下,可以使用多個小空間的磁碟,同時,也可為資料庫擴充套件新的磁碟空間;

b)備份/還原,只讀/讀寫,離線/線上等資料庫管理與維護;

SQL SERVER中檔案組與檔案的關係如下圖:

2.2、檔案

(1)如果檔案組中的檔案被限制大小,檔案填滿了會不會自動新建一個檔案?

不會,那麼此時資料庫會報出空間不足的錯誤,所以在定義檔案時,要麼指定其大小無限增長(直到填滿磁碟空間),要麼定期檢查檔案空閒空間的大小,以手動擴充套件。

(2)檔案組中檔案是一個個寫入還是各個檔案同時都在增大?

SQL SERVER中是檔案級別中各個檔案同時寫入,ORACLE中是寫滿一個再寫下一個。

三、表資料

3.1、堆表和索引組織表

使用堆表還是索引組織表,取決於對DML效率的要求。

(1)堆表在DML時沒有聚集索引的維護成本,在非明確指定的情況下,ORACLE中預設使用堆表;

(2)對於主鍵的單值或範圍查詢,尤其是範圍查詢,索引組織表效率更高,因為省去了bookmark lookup的成本,在非明確指定的情況下,SQL SERVER中預設使用索引組織表。

關於堆表和索引組織表的設計,詳見《SQL Server 查詢優化》系列。

3.2、表資料分割

資料分割的方式,通常可分為以下兩種:

3.2.1、水平分割

出於讀寫效能的考慮,將一個大表,拆分為若干個小的單元,應用程式根據每個單元的入口規則,對映到相應的單元,只處理所需訪問的單元中的資料,以提高資料讀寫的效率。

水平分割針對表中資料行進行分割,可採用以下方式:

(1)分表

在還沒出現分割槽的概念時,對於大表,通常採用分表的方式來實現資料的水平分割。

按照一定的規則將資料分別存入不同名稱的表中:

a)最常見的就是按日期、時間來分表,比如:將每月的訂單放到當前月的訂單表中,orders_201001、orders_201002、orders_201003……依次類推;

b)也可以根據編號規則來分表,比如將尾數:將訂單尾數為1的訂單放到一個表中,orders_1、orders_2、orders_3……依次類推;

(2)表分割槽

a)SQL SERVER 2005以前的版本中,出現過分割槽檢視,算是它在分割槽表出現前的一個替代品,有本地分割槽檢視和分散式分割槽檢視兩種。分散式分割槽檢視的概念,倒是有現在分散式儲存的味道,但由於受網路因素影響較大,未被廣泛使用。

b)對於本地化的表分割槽,目前多采用分割槽表,結構如下圖:

3.2.2、垂直分割

垂直分割針對表中列進行分割。

如果表中列非常多,從而導致一行資料所佔用的儲存空間非常大時,可以考慮進行垂直分割,比如:將一個表拆成主表和從表,主表中存放訪問最頻繁的列,從表中存放訪問較少的列,從而提高資料讀取的效率。

通常不建議使用垂直分割槽,如果主從表都變得很大,在需要進行主從表關聯時,成本也是相當高的,可能會影響效能。

3.3、簇

簇(CLUSTER),這是ORACLE裡的一個概念,將多個表中相同的欄位存放到一起,稱為簇,即聚集的意思,這裡需要和聚集索引的概念區分開來。放到一起的好處如下:

(1)節省儲存空間,相同資料只需要存一份;

(2)提高多表關聯的速度;