????一直以來就想總結一下自己這么多年來在關系數據庫上積累的經驗。奈何自己是一個比較懶的人一直不想動手去寫。扎克伯格曾說過:“想做一件事的話,最好的辦法就是先開始”。索性就先寫一點東西,這些東西不會太長,自然也不會包括太多細節(如果有紙上談兵的情況,也敬請諒解)。
? ? 本文的主要內容是對關系數據庫的設計及優化總結出一些基本原則,這些原則可以幫助提高數據庫的設計質量的同時避免一些常見的性能問題。(軟件行業沒有銀彈,任何原則是指一種參考,困難的是懂得何時打破這些原則)
原則一?OLTP負載盡量遵循第三范式
????不少程序員對數據庫范式理解不透徹,甚至嗤之以鼻,為了簡便在不同的表冗余字段,甚至害怕一對一,一對多,多對多這種關系。阿里還流傳著不要關聯數據表的神奇傳說。關系數據庫不使用關系,那就沒有必要用關系數據庫了。
????遵守第三范式帶來的好就是減少數據冗余,同時使得模型中對單一表的CRUD操作不會互相影響,專業術語叫做更新異常。更新異常往往是大量bug的滋生地。更新異常甚至會大大影響查詢性能。
????最后重申一下本條原則適用范圍,當你在使用關系數據庫(鍵值對,文檔數據庫除外)去設計業務模型的時候(OLTP負載就是頻繁的增加刪除修改型的應用場景),盡量遵循業務實體中的關系,并將它們設計為滿足第三范式的數據模型。
原則二 選擇正確的數據類型和長度
????很多程序員在設計數據類型和長度方面顯得非常草率,甚至是不負責任的。例如將日期類型存儲為字符串型,將數字類型也設計為字符串型,一切類型均為字符串型,并隨意給與50,100,200的可變長度,一了百了。這種隨意性是對程序質量的褻瀆。很多人說這不影響開發,能跑起來就行了。
????本條原則要強調,針對你要解決的問題請慎重選擇數據類型。例如時間請用合適的日期類型(datetime,date, smalldate)。整數請選擇int,bigint等,貨幣類型請選擇decimal類型。不合適的類型會導致數據轉換,在程序中也會導致數據轉換,這不僅僅是性能殺手,更是眾多bug的來源。
????請慎重選擇適合的長度,如果業務需求僅需要10個長度的字符,那就給與10個長度,不要因為害怕長度不夠而給與20個或者200個長度。慷慨往往是不明智的。
???? 在具有大量數據的表中,不合適的長度會浪費大量存儲空間,這將導致數據庫每一頁中存儲的數據行數就會變少,數據庫引擎加載到內存中的頁也就隨之變少,進而影響到查詢的性能。同時數據庫的備份也明顯增大,影響備份還原的效率。
????如果遵循本條原則精心選擇數據類型和長度(尤其是對那些可能包含海量數據的大表)將顯著提高數據模型的設計質量,為后續的程序開發奠定了良好的基礎。
原則三?請正確創建索引
????在我的早期職業生涯的時候,曾遇到過不給任何表增加索引的設計(僅有主鍵),理由是索引會減緩INSERT,UPDATE效率。也遇到過給每個字段都增加索引的匪夷所思的設計。
????要了解如何正確使用索引,必須了解索引是提高查詢效率?大多數人都知道索引類似書本最后一頁的詞匯索引,告訴你某一詞匯在書中的多少頁。但很多人對多因的基本結構缺乏了解,雖然大多知道索引是B-Tree,但不知道B-Tree為什么能提高查詢效率。簡單的講就是B-Tree是一顆特殊的平衡樹,找到某一數據所用的時間總是一定的。例如:在1000000萬個訂單中找到任意訂單記錄所需時間是一樣的。這是由B-Tree結構保證的,數據庫引擎從根節點出發查找一條特定記錄總是跨越2-3層,所以時間總是相等的,這個時間并不與數據量相關,而僅僅與樹的深度相關。這就是索引能提高查詢效率的基本原理。
????請創建合適的聚集索引,很多人把聚集索引和主鍵相混淆,因為大多數數據庫引擎會自動在主鍵上創建聚集索引。其實聚集索引就是按照主鍵順序來實際組織磁盤上數據的物理順序,因為物理數據只能按照一種順序組織,所以一個表只能有一個聚集索引。聚集索引就是物理數據本身,這是一個非常重要的概念。聚集索引一般使用自增長的數據類型,int類型是大多數的選擇,這可以避免數據庫引擎維護聚集索引。
????創建合適的非聚集索引,按照某些字段查詢記錄的需求是非聚集索引使用的適合場景,例如按照姓名,年齡,性別查詢一些員工的時候,如果在這三個字段上創建一個非聚集索引,將會顯著改善查詢性能。非聚集索引的頁只包含非聚集索引數據,如果要查詢表中其他列的數據,就要做一次lookup操作通過聚集索引把對應的記錄提取出來。(為避免lookup操作,就產生了覆蓋索引)
????考慮創建覆蓋索引,例如很多場景下僅需要根據姓名查詢,但是需要同時提取年齡等信息(select name,age where name = ’張三‘), 我們僅僅為name創建一個非聚集索引就可以大幅度提高查詢效率,但如果為name在增加一個覆蓋索引name(age),就可以直接從索引中提取需要的數據,而不必要再去做一個lookup操作從聚集索引中查找,這將大大提高查詢性能。
????考慮重建索引,對索引性能影響最大的就是碎片,頻繁的insert,update 可能會導致大量的頁拆分,進而導致索引頁的連續地址和物理的連續地址不同,影響索引順序掃描,導致性能下降。索引中有個填充率的參數就是為了避免頁拆分而預留的空間,但是填充率過低又會導致索引頁過多降低索引查詢效率。所以好的解決辦法是當碎片過多的時候,可以考慮重建索引。重建后索引頁的順序和物理順序會保持一直。
????索引帶來的附加好處,索引帶來查詢性能提升的同時,還可以大大降低鎖的爭用,因為查詢可以利用索引,優化器會盡可能少的對數據行和頁進行加鎖,并發能力就可能提高。優化器可以利用索引的排序,有效降低對CPU的消耗。對于Order by 或 gourp by 子句將會帶來性能提升,同時降低CPU使用率。CPU使用率降低會提高數據庫服務器的吞吐量。
????最后,請為那些經常被執行的查詢創建合適的索引(主要分析where后面的條件)。同時請謹慎考慮索引創建,如果你不確定數據庫引擎是否正確使用索引,請不要隨意創建索引,因為索引會占用磁盤空間,還會導致insert,update效率降低。
原則四?使用合適的隔離級別(以SQLServer為例)
????關系數據庫最大的基本特征就是保持數據一致性,為了保持數據一致性而施加了很多鎖。事務的隔離級別主要是用于控制某一事務受其他事務的影響的程度,這一程度會影響其他事務對同一數據的讀取和修改操作,進而影響并發能力。
????較低的隔離級別可以增強許多用戶同時訪問數據的能力,但也增加了用戶可能遇到的并發副作用(例如臟讀或丟失更新)的數量。相反,較高的隔離級別減少了用戶可能遇到的并發副作用的類型,但需要更多的系統資源,并增加了一個事務阻塞其他事務的可能性。應平衡應用程序的數據完整性要求與每個隔離級別的開銷,在此基礎上選擇相應的隔離級別。最高隔離級別(可序列化)保證事務在每次重復讀取操作時都能準確檢索到相同的數據,但需要通過執行某種級別的鎖定來完成此操作,而鎖定可能會影響多用戶系統中的其他用戶。最低隔離級別(未提交讀)可以檢索其他事務已經修改、但未提交的數據。在未提交讀中,所有并發副作用都可能發生,但因為沒有讀取鎖定或版本控制,所以開銷最少。
?重要
選擇事務隔離級別不影響為保護數據修改而獲取的鎖。事務總是在其修改的任何數據上獲取排他鎖并在事務完成之前持有該鎖,不管為該事務設置了什么樣的隔離級別。對于讀取操作,事務隔離級別主要定義保護級別,以防受到其他事務所做更改的影響。
? ? 例如SQLServer的默認隔離級別READ COMMITTED,數據庫引擎會使用共享鎖防止其他事務在當前事務執行讀取操作期間修改行。共享鎖還會阻止語句在其他事務完成之前讀取由這些事務修改的行。共享鎖類型確定它將于何時釋放。行鎖在處理下一行之前釋放。頁鎖在讀取下一頁時釋放,表鎖在語句完成時釋放。如果查詢某個表中獲取大量數據并未有效使用索引,共享鎖可能會升級到鎖頁和鎖表,其他對該表的insert update delete操作將不得不等到鎖釋放,給用戶帶來的影響就是這些操作變慢了。
? ? 如果業務允許讀取一定的未提交數據,可以考慮使用?READ UNCOMMITTED,數據庫引擎會使用最少的鎖,當查詢數據時候,不會阻塞insert update delete 操作。一般對于查詢日志表,審計表等允許容忍一定的臟讀情況下使用該隔離級別,這樣可以不阻塞業務表的操作。
? ? 如果需要更高的一致性,例如在一個事務修改某一行但未提交時候,限制其他事務修改同樣的行,那么可以使用?REPEATABLE READ。但是并發級別會大大降低,除非你確保需要此種業務場景,否則應謹慎使用。
????對于需要頻讀取數據,并又要盡量保證不阻塞其他事務insert,update,delete操作的場景,編寫查詢語句的時候可以考慮使用READ_COMMITTED_SNAPSHOT 隔離級別。已提交讀隔離使用行版本控制提供語句級讀取一致性。讀取操作只需要 SCH-S 表級別的鎖,不需要頁鎖或行鎖。也就是說,SQL Server數據庫引擎使用行版本控制來呈現每個語句,其中包含在語句開始時存在的數據的事務一致性快照。不使用鎖來防止其他事務更新數據。?該隔離級別會導致更新時候出現版本沖突異常,這點需要應用程序抓取異常并進行重試。該隔離級別就可以滿足讀寫互不影響。(很多人并未完全理解事務隔離級別,就盲目采用所謂讀寫分離技術增加設計的復雜度。)
總結
????關系數據庫引擎依然是當今最最流行,滿足常見商業邏輯需要的ACID特性的技術之一,并且建立在可靠的數學集合理論之上。設計精良的關系數據庫模型會提升應用程序的可靠性。設計糟糕的模型會導致上層應用的失敗。
????以上所屬的一些原則有助于提提高數據模型設計的質量,充分利用數據庫提供的特性,可以簡化設計方案。