數據庫調優策略

1.1  名詞解釋

OLTP是傳統的關係型數據庫的主要應用,主要是基本的、日常的事務處理,例如銀行交易。OLAP數據倉庫系統的主要應用,支持複雜的分析操作,側重決策支持,並且提供直觀易懂的查詢結果.

1.2  硬件優化

1.2.1  數據庫分區

利用數據庫分區技術,均勻地把數據分佈在系統的磁盤中,平衡I/O訪問,避免I/O瓶頸:

 (1)訪問分散到不同的磁盤,即使用戶數據儘可能跨越多個設備,多個I/O運轉,避免I/O競爭,克服訪問瓶頸;分別放置隨機訪問和連續訪問數據。

(2)分離系統數據庫I/O和應用數據庫I/O,把系統審計表和臨時庫表放在不忙的磁盤

上。

(3)把事務日誌放在單獨的磁盤上,減少磁盤I/O開銷,這還有利於在障礙後恢復,提高了系統的安全性。

(4)把頻繁訪問的“活性”表放在不同的磁盤上;把頻繁用的表、頻繁做Join的表分別放在單獨的磁盤上,甚至把頻繁訪問的表的字段放在不同的磁盤上,把訪問分散到不同的磁盤上,避免I/O爭奪。

1.2.2  RAID

RAID0:稱爲條帶化(Striping)存儲,將數據分段存儲於各個磁盤中,讀寫均可以並行處理。因此其讀寫速率爲單個磁盤的N倍(N爲組成RAID0的磁盤個數),但是卻沒有數 據冗餘,單個磁盤的損壞會導致數據的不可修復


RAID 1:鏡像存儲(mirroring),沒有數據校驗。數據被同等地寫入兩個或多個磁盤中,可想而知,寫入速度會比較慢,但讀取速度會比較快。讀取速度可以接近所有磁盤吞吐量的總和,寫入速度受限於最慢的磁盤。 RAID1也是磁盤利用率最低的一個。如果用兩個不同大小的磁盤建立RAID1,可以用空間較小的那一個,較大的磁盤多出來的部分可以作他用,不會浪費。


RAID 5:奇偶校驗(XOR),數據以塊分段條帶化存儲。校驗信息交叉地存儲在所有的數據盤上。


RAID5把數據和相對應的奇偶校驗信息存儲到組成RAID5的各個磁盤上,並且奇偶校驗信息和相對應的數據分別存儲於不同的磁盤上,其中任意N-1塊磁盤上都存儲完整的數據,也就是說有相當於一塊磁盤容量的空間用於存儲奇偶校驗信息。因此當RAID5的一個磁盤發生損壞後,不會影響數據的完整性,從而保證了數據安全。當損壞的磁盤被替換後,RAID還會自動利用剩下奇偶校驗信息去重建此磁盤上的數據,來保持RAID5的高可靠性。

RAID 5可以理解爲是RAID 0和RAID 1的折衷方案。RAID 5可以爲系統提供數據安全保障,但保障程度要比鏡像低而磁盤空間利用率要比鏡像高。RAID 5具有和RAID 0相近似的數據讀取速度,只是因爲多了一個奇偶校驗信息,寫入數據的速度相對單獨寫入一塊硬盤的速度略慢

 

1.3  設計優化

1.3.1  程序設計

從數據庫設計者的角度來看,應用程序無非是實現對數據的增加、修改、刪除、查詢和體現數據的結構和關係。設計者在性能方面的考慮因素,總的出發點是:把數據庫當作奢侈的資源看待,在確保功能的同時,儘可能少地動用數據庫資源。包括如下原則:  (1)不訪問或少訪問數據庫;

(2)簡化對數據庫的訪問;

(3)使訪問最優; 

(4)對前期及後續的開發、部署、調整提出要求,以協助實現性能目標;

(5)不要直接執行完整的SQL 語法,儘量通過存儲過程來調用數據庫

(6)客戶與服務器連接時,建立連接池;

(7)非到不得已,不要使用遊標結構。確實使用時,注意各種遊標的特性。

(8)歸納一些業務邏輯放在數據庫編程實現。數據庫編程包括數據庫存儲過程、觸發器和函數,用數據庫編程實現業務邏輯的好處是減少網絡流量並可更充分利用數據庫的預編譯和緩存功能。

1.3.2  數據庫表設計

1.3.2.1  範式

第一範式:當關系模式R的所有屬性都不能在分解爲更基本的數據單位時,稱R是滿足第一範式的,簡記爲1NF;

第二範式:如果關係模式R滿足第一範式,並且R得所有非主屬性都完全依賴於R的每一個候選關鍵屬性,稱R滿足第二範式,簡記爲2NF。

R是一個滿足第一範式條件的關係模式,XR的任意屬性集,如果X非傳遞依賴(直接依賴)於R的任意一個候選關鍵字,稱R滿足第三範式,簡記爲3NF。也就是說, 屬性之間不能存在傳遞關係,即每個屬性都跟主鍵有直接關係而不是間接關係

設計時,儘量滿足3NF,但也要考慮實際情況,如增加冗餘字段以提高查詢效率等。如果全部達到第二範式,大部分達到第三範式,系統會產生較少的列和較多的表,因而減少了數據冗餘,也利於性能的提高

1.3.2.2  合理冗餘

完全按照規範化設計的系統幾乎是不可能的,除非系統特別的小,在規範化設計後,有計劃地加入冗餘是必要的。冗餘可以是冗餘數據庫、冗餘表或者冗餘字段,不同粒度的冗餘可以起到不同的作用。冗餘可以是爲了編程方便而增加,也可以是爲了性能的提高而增加。從性能角度來說,冗餘數據庫可以分散數據庫壓力,冗餘表可以分散數據量大的表的併發壓力,也可以加快特殊查詢的速度,冗餘字段可以有效減少數據庫表的連接,提高效率。

1.3.2.3  主鍵設計

主鍵是必要的,而且在實際應用中,我們往往選擇最小的鍵組合作爲主鍵,所以主鍵往往適合作爲表的聚集索引,聚集索引對查詢的影響是比較大的。

在有多個鍵的表,主鍵的選擇也比較重要,一般選擇總的長度小的鍵,小的鍵的比較速度快,同時小的鍵可以使主鍵的B樹結構的層次更少。主鍵的選擇還要注意組合主鍵的字段次序,對於組合主鍵來說,不同的字段次序的主鍵的性能差別可能會很大,一般應該選擇重複率低、單獨或者組合查詢可能性大的字段放在前面。

 

1.3.2.4  外鍵設計

外鍵作爲數據庫對象,很多人認爲麻煩而不用,實際上,外鍵在大部分情況下是很有用的,理由是:外鍵是最高效的一致性維護方法,數據庫的一致性要求,依次可以用外鍵、CHECK約束、規則約束、觸發器、客戶端程序,一般認爲,離數據越近的方法效率越高。

謹慎使用級聯刪除和級聯更新。因爲級聯刪除和級聯更新有些突破了傳統的關於外鍵的定義,功能有點太過強大,使用前必須確定自己已經把握好其功能範圍,否則,級聯刪除和級聯更新可能讓你的數據莫名其妙的被修改或者丟失。從性能看級聯刪除和級聯更新是比其他方法更高效的方法。

1.3.2.5  字段設計

字段是數據庫最基本的單位,其設計對性能的影響是很大的,對此,馬海祥提醒大家要注意以下幾點:

l  數據類型儘量用數字型數字型的比較比字符型的快很多

l  數據類型儘量小,這裏的儘量小是指在滿足可以預見的未來需求的前提下的。

儘量不要允許NULL,除非必要,可以用NOT NULL+DEFAULT代替。

少用TEXTIMAGE,二進制字段的讀寫是比較慢的,而且,讀取的方法也不多,大部分情況下最好不用。

自增字段要慎用,不利於數據遷移

 

1.3.2.6  分割表

1)       水平分割

水平分割是按照行將一個表分割爲多個表,這可以提高每個表的查詢速度,但是由於造成了多表連接,所以應該在同時查詢或更新不同分割表中的列的情況比較少的情況下使用。

2)       垂直分割

是對於一個列很多的表,若某些列的訪問頻率遠遠高於其它列,在不破壞第三範式的前提下將主鍵和這些列作爲一個表,將主鍵和其它列作爲另外一個表。

 

1.3.3  索引設計

1.3.3.1  基本知識

索引是一種特殊的文件(innoDB(事務性數據庫的首選引擎)。數據表上的索引是表空間的一個組成部分),它們包含着對數據表裏所有記錄的引用指針。爲表設置索引要付出代價的:一是增加了數據庫的存儲空間(索引也是文件,需要存儲),二是在插入和修改數據時要花費較多的時間(因爲索引也要隨之變動)。


上圖展示了一種可能的索引方式。最左邊的是數據記錄的物理地址(注意邏輯上相鄰的記錄在磁盤上也並不是一定物理相鄰的)。中間是數據表,一共有兩列七條記錄。爲了加快Col2的查找,可以在維護數據表的同時維護一個如右邊所示的二叉查找樹(即索引),每個節點分別包含索引鍵值和一個指向對應數據記錄物理地址的指針(如[34,0x07]),這樣就可以運用二叉查找在O(log2n)的複雜度內獲取到相應數據。

索引分爲:聚集索引和非聚集索引。

聚集索引:如同字典中按照字母查詢,我們把這種正文內容本身就是一種按照一定規則排列目錄(正文本身就是目錄)稱爲聚集索引(即不需要單獨建立目錄文件,因爲內容本身就是目錄)。我們的漢語字典的正文本身就是一個聚集索引。比如,我們要查“安”字,就會很自然地翻開字典的前幾頁,因爲“安”的拼音是“an”,而按照拼音排序漢字的字典是以英文字母“a”開頭並以“z”結尾的,那麼“安”字就自然地排在字典的前部。由於聚集索引規定了數據的物理存儲順序,因此一個表至多有一個聚集索引,但該索引可以包含多個列(組合索引)。

非聚集索引(先從目錄中找到目標的位置信息,再根據位置信息找到正真的數據):非聚集索引與字典中按部首查字的內容相似。比如,當遇到不認識的字,不知道它的發音,這時候,就不能按照聚集索引的方式找到要查的字,而需要去根據“偏旁部首”查到您要找的字,然後根據這個字後的頁碼直接翻到某頁來找到您要找的字。但您結合“部首目錄”“檢字表”而查到的字的排序並不是真正的正文的排序方法,比如查“張”字,我們可以看到在查部首之後的檢字表中“張”的頁碼是672頁,檢字表中“張”的上面是“馳”字,但頁碼卻是63頁,“張”的下面是“弩”字,頁面是390頁。很顯然,這些字並不是真正的分別位於“張”字的上下方,現在看到的連續的“馳、張、弩”三字實際上就是他們在非聚集索引中的排序,是字典正文中的字在非聚集索引中的映射。我們可以通過這種方式來找到所需要的字,但它需要兩個過程,先找到目錄中的結果,然後再翻到您所需要的頁碼

下圖總結了聚集索引與非聚集索引的適用環境:


1.3.3.2  索引實現

索引的實現通常使用B樹、B+樹,B-樹。

1.3.3.3  索引選擇

l   根據數據量決定哪些表需要增加索引,數據量小的可以只有主鍵,不建立索引。

l   根據使用頻率決定哪些字段需要建立索引,選擇經常作爲連接條件、篩選條件、聚合查詢、排序的字段作爲索引的候選字段。

l   把經常一起出現的字段組合在一起,組成組合索引,組合索引的字段順序與主鍵一樣,也需要把最常用的字段放在前面,把重複率低的字段放在前面。

l   一個表不要加太多索引,因爲索引影響插入和更新的速度。

1.3.4  SQL語句優化

儘量必要全表掃描,條件越精確越好。

l  對查詢進行優化,應儘量避免全表掃描,首先應考慮在 where order by 涉及的列上建立索引

l   選擇最有效率的表名順序(只在基於規則的優化器中有效):ORACLE的解析器按照從右到左的順序處理FROM子句中的表名,FROM子句中寫在最後的表(基礎表 driving table)將被最先處理,在FROM子句中包含多個表的情況下,你必須選擇記錄條數最少的表作爲基礎表(放在最右邊)。如果有3個以上的表連接查詢, 那就需要選擇交叉表(intersection table)作爲基礎表, 交叉表是指那個被其他表所引用的表.

l    WHERE子句中的連接順序:ORACLE採用自下而上的順序解析WHERE子句,根據這個原理,表之間的連接必須寫在其他WHERE條件之前。那些可以過濾掉最大數量記錄的條件必須寫在WHERE子句的末尾(也就是說,將最嚴格的條件放在where條件的最後)。

l    使用表的別名(Alias):當在SQL語句中連接多個表時, 請使用表的別名並把別名前綴於每個Column上.這樣一來,就可以減少解析的時間並減少那些由Column歧義引起的語法錯誤

l   避免使用having,使用where替代

l   應儘量避免在 where 子句中對字段進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如:select id from t where num is null。可以在num上設置默認值0,確保表中num列沒有null值,然後這樣查詢:select idfrom t where num=0

l   應儘量避免在 where 子句中使用!=<>操作符,否則將引擎放棄使用索引而進行全表掃描。

l   應儘量避免在 where 子句中使用 or 來連接條件,否則將導致引擎放棄使用索引而進行全表掃描,如:
select id from t where num=10 or num=20
可以這樣查詢:
select id from t where num=10
union all
select id from t where num=20

UNION 操作符用於合併兩個或多個 SELECT語句的結果集,默認返回不同值,使用UNION ALL 則可返回重複值。

l   in/not in 與exist/not exist的選擇

in是把外表和內表作hash連接,而exists是對外表作loop循環,每次loop循環再對內表進行查詢,一直以來認爲exists比in效率高的說法是不準確的。如果查詢的兩個表大小相當,那麼用in和exists差別不大;如果兩個表中一個較小一個較大,則子查詢表大的用exists(外層表小,循環較少),子查詢表小的用in;無論哪個表大,用not exists都比not in 要快。對於連續的數值,能用 between 就不要用 in


l   少用like。非不得已時,使用like “xxxx%”,即like的條件越精確越好或者使用一些函數來代替like,如使用charindex(‘c’,username)>0可達到like ‘%c%’的結果

下面的查詢也將導致全表掃描:
select id from t where name like '%abc%'
若要提高效率,可以考慮全文檢索。

l   如果在 where 子句中使用參數,也會導致全表掃描。因爲SQL只有在運行時纔會解析局部變量,但優化程序不能將訪問計劃的選擇推遲到運行時;它必須在編譯時進行選擇。然而,如果在編譯時建立訪問計劃,變量的值還是未知的,因而無法作爲索引選擇的輸入項。如下面語句將進行全表掃描:
select id from t where num=@num

l   可以改爲強制查詢使用索引:
select id from t with(index(索引名)) where num=@num

l   應儘量避免在 where 子句中對字段進行表達式操作,這將導致引擎放棄使用索引而進行全表掃描。如:
select id from t where num/2=100
應改爲:
select id from t where num=100*2

l   應儘量避免在where子句中對字段進行函數操作,這將導致引擎放棄使用索引而進行全表掃描。如:
select id from t where substring(name,1,3)='abc' // oracle總有的是substr函數。
select id from t where datediff(day,createdate,'2005-11-30')=0 //查過了確實沒有datediff函數。
應改爲:
select id from t where name like 'abc%'
select id from t where createdate>='2005-11-30' andcreatedate<'2005-12-1' // 
oracle 中時間應該把char轉換成date 如:createdate >= to_date('2005-11-30','yyyy-mm-dd')

l   不要在 where 子句中的“=”左邊進行函數、算術運算或其他表達式運算,否則系統將可能無法正確使用索引。

l   在使用索引字段作爲條件時,如果該索引是複合索引,那麼必須使用到該索引中的第一個字段作爲條件時才能保證系統使用該索引,否則該索引將不會被使用,並且應儘可能的讓字段順序與索引順序相一致。

l   不要寫一些沒有意義的查詢,如需要生成一個空表結構:
select col1,col2 into #t from t where 1=0
這類代碼不會返回任何結果集,但是會消耗系統資源的,應改成這樣:
create table #t(...)

l   並不是所有索引對查詢都有效,SQL是根據表中數據來進行查詢優化的,當索引列有大量數據重複時,SQL查詢可能不會去利用索引,如一表中有字段sex,male、female幾乎各一半,那麼即使在sex上建了索引也對查詢效率起不了作用。

l   索引並不是越多越好,索引固然可以提高相應的select 的效率,但同時也降低了 insert 及 update 的效率,因爲 insert 或 update 時有可能會重建索引,所以怎樣建索引需要慎重考慮,視具體情況而定。一個表的索引數最好不要超過6個,若太多則應考慮一些不常使用到的列上建的索引是否有必要。

l   應儘可能的避免更新 clustered索引(聚集索引)數據列,因爲 clustered 索引數據列的順序就是表記錄的物理存儲順序,一旦該列值改變將導致整個表記錄的順序的調整,會耗費相當大的資源。若應用系統需要頻繁更新 clustered 索引數據列,那麼需要考慮是否應將該索引建爲clustered 索引。

l   儘量使用數字型字段,若只含數值信息的字段儘量不要設計爲字符型,這會降低查詢和連接的性能,並會增加存儲開銷。這是因爲引擎在處理查詢和連接時會逐個比較字符串中每一個字符,而對於數字型而言只需要比較一次就夠了。

l   儘可能的使用varchar/nvarchar 代替 char/nchar ,因爲首先變長字段存儲空間小,可以節省存儲空間,其次對於查詢來說,在一個相對較小的字段內搜索效率顯然要高些。

l   任何地方都不要使用 select *from t ,用具體的字段列表代替“*”,不要返回用不到的任何字段。

l   儘量使用表變量來代替臨時表。如果表變量包含大量數據,請注意索引非常有限(只有主鍵索引)。

l   避免頻繁創建和刪除臨時表,以減少系統表資源的消耗。

l   臨時表並不是不可使用,適當地使用它們可以使某些例程更有效,例如,當需要重複引用大型表或常用表中的某個數據集時。但是,對於一次性事件,最好使用導出表。

l   在新建臨時表時,如果一次性插入數據量很大,那麼可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果數據量不大,爲了緩和系統表的資源,應先create table,然後insert。

l   .如果使用到了臨時表,在存儲過程的最後務必將所有的臨時表顯式刪除,先 truncate table ,然後 drop table ,這樣可以避免系統表的較長時間鎖定。

l   儘量避免使用遊標,因爲遊標的效率較差,如果遊標操作的數據超過1萬行,那麼就應該考慮改寫。

l   使用基於遊標的方法或臨時表方法之前,應先尋找基於集的解決方案來解決問題,基於集的方法通常更有效。

l   與臨時表一樣,遊標並不是不可使用。對小型數據集使用 FAST_FORWARD 遊標通常要優於其他逐行處理方法,尤其是在必須引用幾個表才能獲得所需的數據時。在結果集中包括“合計”的例程通常要比使用遊標執行的速度快。如果開發時間允許,基於遊標的方法和基於集的方法都可以嘗試一下,看哪一種方法的效果更好。

l   在所有的存儲過程和觸發器的開始處設置 SET NOCOUNT ON ,在結束時設置 SET NOCOUNTOFF 。無需在執行存儲過程和觸發器的每個語句後向客戶端發送 DONE_IN_PROC 消息。

l   儘量避免大事務操作,提高系統併發能力。

l   儘量避免向客戶端返回大數據量,若數據量過大,應該考慮相應需求是否合理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章