前言: 程序員
大部分人都一致認爲一個數據庫應用系統(這裏的數據庫應用系統概指全部使用數據庫的系統)的性能瓶頸最容易出如今數據的操做方面,而數據庫應用系統的大部分數據操做都是經過數據庫管理軟件所提供的相關接口來完成的。因此數據庫管理軟件也就很天然的成爲了數據庫應用系統的性能瓶頸所在,這是當前業界比較廣泛的一個見解。但咱們的應用系統的性能瓶頸真的徹底是由於數據庫管理軟件和數據庫主機自身形成的嗎?咱們將經過本章的內容來進行一個較爲深刻的分析,讓你們瞭解到一個數據庫應用系統的性能到底與哪些地方有關,讓你們尋找出各自應用系統的出現性能問題的根本緣由,而儘量清楚的知道該如何去優化本身的應用系統。 數據庫
考慮到本書的數據庫對象是MySQL,而MySQL最多的使用場景是WEB應用,那麼咱們就以一個WEB應用系統爲例,逐個分析其系統構成,結合筆者在大型互聯網公司從事DBA工做多年的經驗總結,分析出數據庫應用系統中各個環境對性能的影響。 編程
應用系統中的每個功能在設計初衷確定都是出於爲用戶提供某種服務,或者知足用戶的某種需求,可是,並非每個功能在最後都能很成功,甚至有些功能的推出可能在整個系統中是多此一舉。不只沒有爲用戶提升任何體驗度,也沒有爲用戶改進多少功能易用性,反而在整個系統中成爲一個累贅,帶來資源的浪費。 安全
不合理需求形成資源投入產出比太低 性能優化
需求是否合理不少時候可能並非很容易界定,尤爲是做爲技術人員來講,可能更難以肯定一個需求的合理性。即便指出,也不必定會被產品經歷們承認。那做爲技術人員的咱們怎麼來證實一個需求是否合理呢? 服務器
第1、每次產品經理們提出新的項目(或者功能需求)的時候,應該要求他們同時給出該項目的預期收益的量化指標,以備項目上前後統計評估投入產出比率; 網絡
第2、在每次項目進行過程當中,應該詳細記錄全部的資源投入,包括人力投入,硬件設施的投入,以及其餘任何項目相關的資源投入; 架構
第3、項目(或者功能需求)上線以後應該及時經過手機相關數據統計出項目的實際收益值,以便計算投入產出比率的時候使用; 併發
第4、技術部門應該儘量推進設計出一個項目(或者功能需求)的投入產出比率的計算規則。在項目上線一段時間以後,經過項目實際收益的統計數據和項目的投入資源量,計算出整個項目的實際投入產出值,並公佈給全部參與項目的部門知曉,同時存放以備後查。 框架
有了實際的投入產出比率,咱們就能夠和項目立項之初產品經理們的預期投入產出比率作出比較,斷定出這個項目作的是否值得。並且當積累了較多的項目投入產出比率以後,咱們能夠根據歷史數據分析出一個項目合理的投入產出比率應該是多少。這樣,在項目立項之初,咱們就能夠斷定出產品經理們的預期投入產出比率是否合理,項目是否真的有進行的必要。
有了實際的投入產出比率以後,咱們還能夠拿出數據給老闆們看,讓他知道功能並非越多越好,讓他知道有些功能是應該撤下來的,即便撤下該功能可能須要投入很多資源。
實際上,通常來講,在產品開發及運營部門內部都會作上面所說的這些事情的。但不少時候可能更多隻是一種形式化的過程。在有些比較規範的公司可能也完成了上面的大部分流程,可是要麼數據不公開,要麼公開給其餘部門的數據存在必定的誤差,不具有真實性。
爲何會這樣?其實就一個緣由,就是部門之間的利益衝突及業績衝突問題。產品經理們老是但願儘量的讓用戶以爲本身設計的產品功能齊全,讓老闆以爲本身作了不少事情。可是歷來都不會去關心由於作一個功能所帶來的成本投入,或者說是不會特別的關心這一點。並且不少時候他們也並不能太理解技術方面帶來的複雜度給產品自己帶來的負面影響。
這裏咱們就拿一個看上去很簡單的功能來分析一下。
需求:一個論壇帖子總量的統計附加要求:實時更新
在不少人看來,這個功能很是容易實現,不就是執行一條SELECT COUNT(*)的Query就能夠獲得結果了麼?是的,確實只須要如此簡單的一個Query就能夠獲得結果。可是,若是咱們採用不是MyISAM存儲引擎,而是使用的Innodb的存儲引擎,那麼你們能夠試想一下,若是存放帖子的表中已經有上千萬的帖子的時候,執行這條Query語句須要多少成本?恐怕再好的硬件設備,恐怕都不可能在10秒以內完成一次查詢吧。若是咱們的訪問量再大一點,還有人以爲這是一件簡單的事情麼?
既然這樣查詢不行,那咱們是否是該專門爲這個功能建一個表,就只有一個字段,一條記錄,就存放這個統計量,每次有新的帖子產生的時候,都將這個值增長1,這樣咱們每次都只須要查詢這個表就能夠獲得結果了,這個效率確定可以知足要求了。確實,查詢效率確定可以知足要求,但是若是咱們的系統帖子產生很快,在高峯時期可能每秒就有幾十甚至上百個帖子新增操做的時候,恐怕這個統計表又要成爲你們的噩夢了。要麼由於併發的問題形成統計結果的不許確,要麼由於鎖資源爭用嚴重形成總體性能的大幅度降低。
其實這裏問題的焦點不該該是實現這個功能的技術細節,而是在於這個功能的附加要求"實時更新"上面。當一個論壇的帖子數量很大了以後,到底有多少人會關注這個統計數據是不是實時變化的?有多少人在意這個數據在短期內的不精確性?我想恐怕不會有人會傻傻的盯着這個統計數字並追究當本身發了一個帖子而後回頭刷新頁面發現這個統計數字沒有加1吧?即便明明白白的告訴用戶這個統計數據是每過多長時間段更新一次,那有怎樣?難道會有不少用戶就此很不爽麼?
只要去掉了這個"實時更新"的附加條件,咱們就能夠很是容易的實現這個功能了。就像以前所提到的那樣,經過建立一個統計表,而後經過一個定時任務每隔必定時間段去更新一次裏面的統計值,這樣既能夠解決統計值查詢的效率問題,又能夠保證不影響新發貼的效率,一箭雙鵰。
實際上,在咱們應用的系統中還有不少不少相似的功能點能夠優化。如某些場合的列表頁面參與列表的數據量達到一個數量級以後,徹底能夠不用準確的顯示這個列表總共有多少條信息,總共分了多少頁,而只須要一個大概的估計值或者一個時間段以前的統計值。這樣就省略了咱們的分頁程序須要在分之前實時COUNT出知足條件的記錄數。
其實,在不少應用系統中,實時和準實時,精確與基本準確,在不少地方所帶來的性能消耗多是幾個性能的差異。在系統性能優化中,應該儘可能分析出那些能夠不實時和不徹底精確的地方,做出一些相應的調整,可能會給你們帶來意想不到的巨大性能提高。
無用功能堆積使系統過分複雜影響總體性能
不少時候,爲系統增長某個功能可能並不須要花費太多的成本,而要想將一個已經運行了一段時間的功能從原有系統中撤下來倒是很是困難的。
首先,對於開發部門,可能要從新整理不少的代碼,找出可能存在與增長該功能所編寫的代碼有交集的其餘功能點,刪除沒有關聯的代碼,修改有關聯的代碼;
其次,對於測試部門,因爲功能的變更,必需要回歸測試全部相關的功能點是否正常。可能因爲界定困難,不得不將回歸範圍擴展到很大,測試工做量也很大。
最後,全部與撤除下線某個功能相關的工做參與者來講,又沒法帶來任何實質性的收益,而偏偏相反是,帶來的只多是風險。
因爲上面的這幾個因素,可能不多有公司可以有很完善的項目(或者功能)下線機制,也不多有公司能作到及時將系統中某些不合適的功能下線。因此,咱們所面對的應用系統可能老是愈來愈複雜,愈來愈龐大,短時間內的複雜可能並沒有太大問題,可是隨着時間的積累,咱們所面對的系統就會變得極其臃腫。不只維護困難,性能也會愈來愈差。尤爲是有些並不合理的功能,在設計之初或者是剛上線的時候因爲數據量較小,帶來不了多少性能損耗。可隨着時間的推移,數據庫中的數據量愈來愈大,數據檢索愈來愈困難,對真個系統帶來的資源消耗也就愈來愈大。
並且,因爲系統複雜度的不斷增長,給後續其餘功能的開發帶來實現的複雜度,可能不少原本很簡單的功能,由於系統的複雜而不得不增長不少的邏輯判斷,形成系統應用程序的計算量不斷增長,自己性能就會受到影響。而若是這些邏輯判斷還須要與數據庫交互經過持久化的數據來完成的話,所帶來的性能損失就更大,對整個系統的性能影響也就更大了。
一個WEB應用系統,天然離不開Web應用程序(Web App)和應用程序服務器(App Server)。App Server咱們能控制的內容很少,大多都是使用已經久經考驗的成熟產品,你們能作的也就只是經過一些簡單的參數設置調整來進行調優,不作細究。而Web App大部分都是各自公司根據業務需求自行開發,可控性要好不少。因此咱們從Web應用程序着手分析一個應用程序架構的不一樣設計對整個系統性能的影響將會更合適。
上一節中商業需求告訴了咱們一個系統應該有什麼不該該有什麼,系統架構則則決定了咱們系統的構建環境。就像修建一棟房子同樣,在清楚了這棟房子的用途以後,會先有建築設計師來畫出一章基本的造型圖,而後還須要結構設計師爲咱們設計出結構圖。系統架構設計的過程就和結構工程好似設計結構圖同樣,須要爲整個系統搭建出一個儘量最優的框架,讓整個系統可以有一個穩定高效的結構體系讓咱們實現各類商業需求。
談到應用系統架構的設計,可能有人的內心會開始嘀咕,一個DBA有什麼資格談論人家架構師(或者程序員)所設計的架構?其實你們徹底沒有必要這樣去考慮,咱們談論架構只是分析各類情形下的性能消耗區別,僅僅是根據本身的專業特長來針對相應架構給出咱們的建議及意見,並非要批判架構總體的好壞,更不是爲了推翻某個架構。並且咱們所考慮的架構大多數時候也只是數據層面相關的架構。咱們數據庫中存放的數據都是適合在數據庫中存放的嗎?
對於有些開發人員來講,數據庫就是一個操做最方便的萬能存儲中心,但願什麼數據都存放在數據庫中,不管是須要持久化的數據,仍是臨時存放的過程數據,不管是普通的純文本格式的字符數據,仍是多媒體的二進制數據,都喜歡所有塞如數據庫中。由於對於應用服務器來講,數據庫不少時候都是一個集中式的存儲環境,不像應用服務器那樣可能有不少臺;並且數據庫有專門的 DBA去幫忙維護,而不像應用服務器不少時候還須要開發人員去作一些維護;還有一點很關鍵的就是數據庫的操做很是簡單統一,不像文件操做或者其餘類型的存儲方式那麼複雜。
其實我我的認爲,如今的不少數據庫爲咱們提供了太多的功能,反而讓不少並非太瞭解數據庫的人錯誤的使用了數據庫的不少並非太擅長或者對性能影響很大的功能,最後卻所有怪罪到數據庫身上。
實際上,如下幾類數據都是不適合在數據庫中存放的:
將二進制多媒體數據存放在數據庫中,一個問題是數據庫空間資源耗用很是嚴重,另外一個問題是這些數據的存儲很消耗數據庫主機的CPU資源。這種數據主要包括圖片,音頻、視頻和其餘一些相關的二進制文件。這些數據的處理本不是數據的優點,若是咱們硬要將他們塞入數據庫,確定會形成數據庫的處理資源消耗嚴重。
咱們都知道,數據庫爲了保證事務的安全性(支持事務的存儲引擎)以及可恢復性,都是須要記錄全部變動的日誌信息的。而流水隊列數據的用途就決定了存放這種數據的表中的數據會不斷的被INSERT,UPDATE和DELETE,而每個操做都會生成與之對應的日誌信息。在MySQL中,若是是支持事務的存儲引擎,這個日誌的產生量更是要翻倍。而若是咱們經過一些成熟的第三方隊列軟件來實現這個Queue數據的處理功能,性能將會成倍的提高。
對於5.0.3以前的MySQL版本,VARCHAR類型的數據最長只能存放255個字節,若是須要存儲更長的文本數據到一個字段,咱們就必須使用TEXT類型(最大可存放64KB)的字段,甚至是更大的
LONGTEXT類型(最大4GB)。而TEXT類型數據的處理性能要遠比VARCHAR類型數據的處理性能低下不少。從5.0.3版本開始,VARCHAR類型的最大長度被調整到64KB了,可是當實際數據小於255 Bytes的時候,實際存儲空間和實際的數據長度同樣,可一旦長度超過255 Bytes以後,所佔用的存儲空間就是實際數據長度的兩倍。
因此,超大文本數據存放在數據庫中不只會帶來性能低下的問題,還會帶來空間佔用的浪費問題。
是否合理的利用了應用層Cache機制?
對於Web應用,活躍數據的數據量老是不會特別的大,有些活躍數據更是不多變化。對於這類數據,咱們是否有必要每次須要的時候都到數據庫中去查詢呢?若是咱們可以將變化相對較少的部分活躍數據經過應用層的Cache機制Cache到內存中,對性能的提高確定是成數量級的,並且因爲是活躍數據,對系統總體的性能影響也會很大。
固然,經過Cache機制成功的案例數不勝數,可是失敗的案例也一樣並很多見。如何合理的經過
Cache技術讓系統性能獲得較大的提高也不是經過寥寥幾筆就能說明的清楚,這裏我僅根據以往的經驗列舉一下什麼樣的數據適合經過Cache技術來提升系統性能:
因爲這些配置信息變更的頻率很是低,訪問機率又很高,因此很是適合存使用Cache;
咱們的數據層實現都是最精簡的嗎?
從以往的經驗來看,一個合理的數據存取實現和一個拙劣的實現相比,在性能方面的差別常常會超出一個甚至幾個數量級。咱們先來分析一個很是簡單且常常會遇到相似狀況的示例:
在咱們的示例網站系統中,如今要實現每一個用戶查看各自相冊列表(假設每一個列表顯示10張相片)的時候,可以在相片名稱後面顯示該相片的留言數量。這個需求你們認爲應該如何實現呢?我想90%的開發開發工程師會經過以下兩步來實現該需求:
WHERE photh_id = ?" 來獲得每張相冊的回覆數量而後再瓶裝展示對象。
此外可能還有部分人想到了以下的方案:
咱們來對以上兩個方案作一下簡單的比較:
BY操做,比第一種解決方案增長了了排序分組操做;
咱們先從以上6點來作一個性能消耗的分析:
綜合上面的這6點比較,咱們能夠很容易得出結論,從總體資源消耗來看,第二中方案會遠遠優於第一種解決方案。而在實際開發過程當中,咱們的程序員卻不多選用。主要緣由其實有兩個,一個是第二種方案在程序代碼實現方面可能會比第一種方案略爲複雜,尤爲是在當前編程環境中面向對象思想的普及,開發工程師可能會更習慣於以對象爲中心的思考方式來解決問題。還有一個緣由就是咱們的程序員可能對SQL語句的使用並非特別的熟悉,並不必定可以想到第二條SQL語句所實現的功能。對於第一個緣由,咱們可能只能經過增強開發工程師的性能優化意識來讓你們可以自覺糾正,而第二個緣由的解決就正是須要咱們出馬的時候了。 SQL語句正是咱們的專長,按期對開發工程師進行一些相應的數據庫知識包括SQL語句方面的優化培訓,可能會給你們帶來意想不到的收穫的。這裏咱們還僅僅只是經過一個很長見的簡單示例來講明數據層架構實現的區別對總體性能的影響,實際上能夠簡單的歸結爲過渡依賴嵌套循環的使用或者說是過渡弱化SQL語句的功能形成性能消耗過多的實例。後面我將進一步分析一下更多的由於架構實現差別所帶來的性能消耗差別。
過分依賴數據庫SQL語句的功能形成數據庫操做效率低下
前面的案例是開發工程師過渡弱化SQL語句的功能形成的資源浪費案例,而這裏咱們再來分析一個徹底相反的案例:在羣組簡介頁面須要顯示羣名稱和簡介,每一個羣成員的nick_name,以及羣主的我的簽名信息。
需求中所需信息存放在如下四個表中:user,user_profile,groups,user_group 咱們先看看最簡單的實現方法,一條SQL語句搞定全部事情:
SELECT name,description,user_type,nick_name,sign
FROM groups,user_group,user ,user_profile
WHERE groups.id = ?
AND groups.id = user_group.group_id
AND user_group.user_id = user.id
AND user_profile.user_id = user.id
固然咱們也能夠經過以下稍微複雜一點的方法分兩步搞定:
首先取得全部須要展現的group的相關信息和全部羣組員的nick_name信息和組員類別:
SELECT name,description,user_type,nick_name
FROM groups,user_group,user
WHERE groups.id = ?
AND groups.id = user_group.group_id
AND user_group.user_id = user.id
而後在程序中經過上面結果集中的user_type找到羣主的user_id再到user_profile表中取得羣主的簽名信息:
SELECT sign FROM user_profile WHERE user_id = ?
你們應該可以看出二者的區別吧,兩種解決方案最大的區別在於交互次數和 SQL複雜度。而帶來的實際影響是第一種解決方案對user_profile表有沒必要要的訪問(非羣主的profile信息),形成IO訪問的直接增長在20%左右。而你們都知道,IO操做在數據庫應用系統中是很是昂貴的資源。尤爲是當這個功能的PV較大的時候,第一種方案形成的IO損失是至關大的。
重複執行相同的SQL形成資源浪費
這個問題實際上是每一個人都很是清楚也徹底認同的一個問題,可是在應用系統開發過程當中,仍然會常有這樣的現象存在。究其緣由,主要仍是開發工程師思惟中面向對象的概念太過深刻,以及爲了減小本身代碼開發的邏輯和對程序接口過分依賴所形成的。
我曾經在一個性能優化項目中遇到過一個案例,某個功能頁面一側是"分組"列表,是一列"分組"的名字。頁面主要內容則是該"分組"的全部"項目"列表。每一個"項目"以名稱(或者圖標)顯示,同時還有一個SEO相關的需求就是每一個"項目"名稱的連接地址中是須要有"分組"的名稱的。因此在"項目"列表的每一個 "項目"的展現內容中就須要獲得該項目所屬的組的名稱。按照開發工程師開發思路,很是容易產生取得全部"項目"結果集並映射成相應對象以後,再從對象集中獲取"項目"所屬組的標識字段,而後循環到"分組"表中取得須要的"組名"。而後再將拼裝成展現對象。
看到這裏,我想你們應該已經知道這裏存在的一個最大的問題就是屢次重複執行了徹底相同的 SQL 獲得徹底相同的內容。同時還犯了前面第一個案例中所犯的錯誤。或許你們看到以後會不相信有這樣的案例存在,我能夠很是確定的告訴你們,事實就是這樣。同時也請你們若是有條件的話,好好Review本身所在的系統的代碼,很是有可能一樣存在上面相似的情形。
還有部分解決方案要遠優於上面的作法,那就是不循環去取了,而是經過Join一次完成,也就是解決了第一個案例所描述的性能問題。可是又誤入了相似於第二個案例所描述的陷阱中了,由於實際上他只須要一次查詢就能夠獲得全部"項目"所屬的"分組"的名稱(全部項目都是同一個組的)。
固然,也有部分解決方案也避免了第二個案例的問題,分爲兩條SQL,兩步完成了這個需求。這樣在性能上面基本上也將近是數量級的提高了。
可是這就是性能最優的解決方案了麼?不是的,咱們甚至能夠連一次都不須要訪問就得到所須要的
"分組"名稱。首先,側欄中的"分組"列表是須要有名稱的,咱們爲何不能直接利用到呢?
固然,可能有些系統的架構決定了側欄和主要內容顯示區來源於不一樣的模板(或者其餘結構),那麼咱們也徹底能夠經過在進入這個功能頁面的連接請求中經過參數傳入咱們須要的"分組"名稱。這樣咱們就能夠徹底不須要根據"項目"相關信息去數據庫獲取所屬"分組"的信息,就能夠完成相應需求了。固然,是否須要經過請求參數來節省最後的這一次訪問,可能會根據這個功能頁面的PV來決定,若是訪問並非很是頻繁,那麼這個節省可能並非很明顯,而應用系統的複雜度卻有所增長,並且程序看上去可能也會不夠優雅,可是若是訪問很是頻繁的場景中,所節省的資源仍是比較可觀的。
上面還僅僅只是列舉了咱們平時比較常見的一些實現差別對性能所帶來的影響,除了這些實現方面所帶來的問題以外,應用系統的總體架構實現設計對系統性能的影響可能會更嚴重。下面大概列舉了一些較爲常見的架構設計實現不當帶來的性能問題和資源浪費狀況。
以上僅僅是一些比較常見的癥結,在各類不一樣的應用環境中確定還會有不少不一樣的性能問題,可能須要你們經過仔細的數據分析和對系統的充分了解才能找到,可是一旦找到癥結所在,經過相應的優化措施,所帶來的收益也是至關可觀的。
前面一節咱們介紹了應用系統的實現差別對數據庫應用系統總體性能的影響,這一節咱們將分析SQL 語句的差別對系統性能的影響。
我想對於各位讀者來講,確定都清楚SQL語句的優劣是對性能有影響的,可是到底有多大影響可能每一個人都會有不一樣的體會,每一個SQL語句在優化以前和優化以後的性能差別也是各不相同,因此對於性能差別到底有多大這個問題咱們咱們這裏就不作詳細分析了。咱們重點分析實現一樣功能的不一樣 SQL語句在性能方面會產生較大的差別的根本緣由,並經過一個較爲典型的示例來對咱們的分析作出相應的驗證。
爲何返回徹底相同結果集的不一樣SQL語句,在執行性能方面存在差別呢?這裏咱們先從SQL語句在數據庫中執行並獲取所需數據這個過程來作一個大概的分析了。
當MySQL Server的鏈接線程接收到Client端發送過來的SQL請求以後,會通過一系列的分解
Parse,進行相應的分析。而後,MySQL會經過查詢優化器模塊(Optimizer)根據該SQL所設涉及到的數據表的相關統計信息進行計算分析,而後再得出一個MySQL認爲最合理最優化的數據訪問方式,也就是咱們常說的"執行計劃",而後再根據所獲得的執行計劃經過調用存儲引擎藉口來獲取相應數據。而後再將存儲引擎返回的數據進行相關處理,並以 Client端所要求的格式做爲結果集返回給Client端的應用程序。
注:這裏所說的統計數據,是咱們經過ANALYZE TABLE命令通知MySQL對錶的相關數據作分析以後所得到到的一些數據統計量。這些統計數據對MySQL優化器而言是很是重要的,優化器所生成的執行計劃的好壞,主要就是由這些統計數據所決定的。實際上,在其餘一些數據庫管理軟件中也有相似相應的統計數據。
咱們都知道,在數據庫管理軟件中,最大的性能瓶頸就是在於磁盤IO,也就是數據的存取操做上面。而對於同一份數據,當咱們以不一樣方式去尋找其中的某一點內容的時候,所須要讀取的數據量可能會有天壤之別,所消耗的資源也天然是區別甚大。因此,當咱們須要從數據庫中查詢某個數據的時候,所消耗資源的多少主要就取決於數據庫以一個什麼樣的數據讀取方式來完成咱們的查詢請求,也就是取決於SQL語句的執行計劃。
對於惟一一個SQL語句來講,通過MySQL Parse以後分解的結構都是固定的,只要統計信息穩定,其執行計劃基本上都是比較固定的。而不一樣寫法的SQL語句,通過MySQL Parse以後分解的結構結構就可能徹底不一樣,即便優化器使用徹底同樣的統計信息來進行優化,最後所得出的執行計劃也可能徹底不同。而執行計劃又是決定一個SQL語句最終的資源消耗量的主要因素。因此,實現功能徹底同樣的SQL語句,在性能上面可能會有差異巨大的性能消耗。固然,若是功能同樣,並且通過 MySQL的優化器優化以後的執行計劃也徹底一致的不一樣SQL語句在資源消耗方面可能就相差很小了。固然這裏所指的消耗主要是IO資源的消耗,並不包括 CPU的消耗。下面咱們將經過一兩個具體的示例來分析寫法不同而功能徹底相同的兩條 SQL的在性能方面的差
異。
示例一
需求:取出某個group(假設id爲100)下的用戶編號(id),用戶暱稱(nick_name)、用戶性別(sexuality)、用戶簽名( sign)和用戶生日( birthday),並按照加入組的時間
(user_group.gmt_create)來進行倒序排列,取出前20個。
解決方案1、
SELECT id,nick_name
FROM user,user_group
WHERE user_group.group_id = 1 and user_group.user_id = user.id
limit 100,20;
解決方案2、
SELECT user.id,user.nick_name
FROM (
SELECT user_id
FROM user_group
WHERE user_group.group_id = 1 ORDER BY gmt_create desc limit 100,20) t,user WHERE t.user_id = user.id;
咱們先來看看執行計劃:
sky@localhost : example 10:32:13> explain
-> SELECT id,nick_name
-> FROM user,user_group
-> WHERE user_group.group_id = 1
-> and user_group.user_id = user.id
-> ORDER BY user_group.gmt_create desc
-> limit 100,20\G
*************************** 1. row *************************** id: 1 select_type: SIMPLE table: user_group
type: ref possible_keys: user_group_uid_gid_ind,user_group_gid_ind key: user_group_gid_ind
key_len: 4 ref: const rows: 31156 Extra: Using where; Using filesort
*************************** 2. row *************************** id: 1
select_type: SIMPLE table: user type: eq_ref
possible_keys: PRIMARY key: PRIMARY
key_len: 4 ref: example.user_group.user_id
rows: 1 Extra:
sky@localhost : example 10:32:20> explain
-> SELECT user.id,user.nick_name
-> FROM (
-> SELECT user_id
-> FROM user_group
-> WHERE user_group.group_id = 1
-> ORDER BY gmt_create desc
-> limit 100,20) t,user
-> WHERE t.user_id = user.id\G
*************************** 1. row *************************** id: 1
select_type: PRIMARY table: <derived2>
type: ALL
possible_keys: NULL key: NULL key_len: NULL ref: NULL
rows: 20 Extra:
*************************** 2. row *************************** id: 1
select_type: PRIMARY table: user type: eq_ref
possible_keys: PRIMARY key: PRIMARY
key_len: 4 ref: t.user_id
rows: 1 Extra: *************************** 3. row *************************** id: 2 select_type: DERIVED table: user_group
type: ref possible_keys: user_group_gid_ind
key: user_group_gid_ind
key_len: 4 ref: const rows: 31156
Extra: Using filesort
執行計劃對比分析:解決方案一中的執行計劃顯示MySQL在對兩個參與Join的表都利用到了索引,user_group表利用了
user_group_gid_ind 索引(key: user_group_gid_ind),user 表利用到了主鍵索引(key: PRIMARY),在參與Join前MySQL經過Where過濾後的結果集與user表進行Join,最後經過排序取出 Join後結果的"limit 100,20"條結果返回。
解決方案二的SQL語句利用到了子查詢,因此執行計劃會稍微複雜一些,首先能夠看到兩個表都和解決方案1同樣都利用到了索引(所使用的索引也徹底同樣),執行計劃顯示該子查詢以user_group爲驅動,也就是先經過 user_group進行過濾並立刻進行這一論的結果集排序,也就取得了 SQL中的 "limit 100,20"條結果,而後與user表進行Join,獲得相應的數據。這裏可能有人會懷疑在自查詢中從user_group表所取得與user表參與 Join的記錄條數並非20條,而是整個group_id=1的全部結果。那麼清你們看看該執行計劃中的第一行,該行內容就充分說明了在外層查詢中的全部的20條記錄所有被返回。
經過比較兩個解決方案的執行計劃,咱們能夠看到第一中解決方案中須要和user表參與Join的記錄數MySQL經過統計數據估算出來是31156,也就是經過user_group表返回的全部知足group_id=1的記錄數(系統中的實際數據是20000)。而第二種解決方案的執行計劃中,user 表參與Join的數據就只有20 條,二者相差很大,經過本節最初的分析,咱們認爲第二中解決方案應該明顯優於第一種解決方案。
下面咱們經過對比兩個解決覺方案的SQL實際執行的profile詳細信息,來驗證咱們上面的判斷。因爲SQL語句執行所消耗的最大兩部分資源就是IO和CPU,因此這裏爲了節約篇幅,僅列出BLOCK IO和CPU 兩項profile信息(Query Profiler 的詳細介紹將在後面章節中獨立介紹):先打開profiling功能,而後分別執行兩個解決方案的SQL語句:
sky@localhost : example 10:46:43> set profiling = 1; Query OK, 0 rows affected (0.00 sec)
sky@localhost : example 10:46:50> SELECT id,nick_name
-> FROM user,user_group
-> WHERE user_group.group_id = 1
-> and user_group.user_id = user.id
-> ORDER BY user_group.gmt_create desc
-> limit 100,20;
+--------+-----------+
| id | nick_name |
+--------+-----------+
| 990101 | 990101 |
| |
| 990102 | 990102 |
| |
| 990103 | 990103 |
| |
| 990104 | 990104 |
| |
| 990105 | 990105 |
| |
| 990106 | 990106 |
| |
| 990107 | 990107 |
| |
| 990108 | 990108 |
| |
| 990109 | 990109 |
| |
| 990110 | 990110 |
| |
| 990111 | 990111 |
| |
| 990112 | 990112 |
| |
| 990113 | 990113 |
| |
| 990114 | 990114 |
| |
| 990115 | 990115 |
| |
| 990116 | 990116 |
| |
| 990117 | 990117 |
| |
| 990118 | 990118 |
| |
| 990119 | 990119 |
| |
| 990120 | 990120 |
| |
+--------+-----------+ 20 rows in set (1.02 sec)
sky@localhost : example 10:46:58> SELECT user.id,user.nick_name
-> FROM (
-> SELECT user_id
-> FROM user_group
-> WHERE user_group.group_id = 1
-> ORDER BY gmt_create desc
-> limit 100,20) t,user
-> WHERE t.user_id = user.id;
+--------+-----------+
| id | nick_name |
+--------+-----------+
990101 | 990101 |
| |
|
990102 | 990102 |
| |
|
990103 | 990103 |
| |
|
990104 | 990104 |
| |
|
990105 | 990105 |
| |
|
990106 | 990106 |
| |
|
| 990107 | 990107 |
| |
|
| 990108 | 990108 |
| |
|
| 990109 | 990109 |
| |
|
| 990110 | 990110 |
| |
|
| 990111 | 990111 |
| |
|
| 990112 | 990112 |
| |
|
| 990113 | 990113 |
| |
|
| 990114 | 990114 |
| |
|
| 990115 | 990115 |
| |
|
| 990116 | 990116 |
| |
|
| 990117 | 990117 |
| |
|
| 990118 | 990118 |
| |
|
| 990119 | 990119 |
| |
|
| 990120 | 990120 |
| |
+--------+-----------+ 20 rows in set (0.96 sec)
查看系統中的profile信息,剛剛執行的兩個SQL語句的執行profile信息已經記錄下來了:
sky@localhost : example 10:47:07> show profiles\G
*************************** 1. row ***************************
Query_ID: 1
Duration: 1.02367600
Query: SELECT id,nick_name
FROM user,user_group
WHERE user_group.group_id = 1 and user_group.user_id = user.id ORDER BY user_group.gmt_create desc limit 100,20
*************************** 2. row ***************************
Query_ID: 2
Duration: 0.96327800
Query: SELECT user.id,user.nick_name
FROM (
SELECT user_id
FROM user_group
WHERE user_group.group_id = 1 ORDER BY gmt_create desc limit 100,20) t,user
WHERE t.user_id = user.id 2 rows in set (0.00 sec) sky@localhost : example 10:47:34> SHOW profile CPU,BLOCK IO io FOR query 1; +--------------------+----------+-----------+------------+--------------+---------------+
| Status | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out |
+--------------------+----------+-----------+------------+--------------+---------------+
| (initialization) |
| 0.000068 | 0 | 0 |
| |
0 | |
0 | |
| Opening tables |
| 0.000015 | 0 | 0 |
| |
0 | |
0 | |
| System lock |
| 0.000006 | 0 | 0 |
| |
0 | |
0 | |
| Table lock |
| 0.000009 | 0 | 0 |
| |
0 | |
0 | |
| init |
| 0.000026 | 0 | 0 |
| |
0 | |
0 | |
| optimizing |
| 0.000014 | 0 | 0 |
| |
0 | |
0 | |
| statistics |
| 0.000068 | 0 | 0 |
| |
0 | |
0 | |
| preparing |
| 0.000019 | 0 | 0 |
| |
0 | |
0 | |
| executing |
| 0.000004 | 0 | 0 |
| |
0 | |
0 | |
| Sorting result |
| 1.03614 | 0.5600349 | 0.428027 |
| |
0 | |
15632 | |
| Sending data |
| 0.071047 | 0 | 0.004 |
| |
88 | |
0 | |
| end |
| 0.000012 | 0 | 0 |
| |
0 | |
0 | |
| query end |
| 0.000006 | 0 | 0 |
| |
0 | |
0 | |
| freeing items |
| 0.000012 | 0 | 0 |
| |
0 | |
0 | |
| closing tables |
| 0.000007 | 0 | 0 |
| |
0 | |
0 | |
| logging slow query | 0.000003 | 0 | 0 |
| |
0 | |
0 | |
+--------------------+----------+-----------+------------+--------------+---------------+
16 rows in set (0.00 sec)
sky@localhost : example 10:47:40> SHOW profile CPU,BLOCK IO io FOR query 2;
+--------------------+----------+----------+------------+--------------+---------------+
| Status | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out |
+--------------------+----------+----------+------------+--------------+---------------+
| (initialization) |
| 0.000087 | 0 | 0 |
| |
0 | |
0 | |
||
| Opening tables |
| 0.000018 | 0 | 0 |
| |
0 | |
0 | |
||
| System lock |
| 0.000007 | 0 | 0 |
| |
0 | |
0 | |
||
| Table lock |
| 0.000059 | 0 | 0 |
| |
0 | |
0 | |
||
| optimizing |
| 0.00001 | 0 | 0 |
| |
0 | |
0 | |
||
| statistics |
| 0.000068 | 0 | 0 |
| |
0 | |
0 | |
||
| preparing |
| 0.000017 | 0 | 0 |
| |
0 | |
0 | |
||
| executing |
| 0.000004 | 0 | 0 |
| |
0 | |
0 | |
||
| Sorting result |
| 0.928184 | 0.572035 | 0.352022 |
| |
0 | |
32 | |
||
| Sending data |
| 0.000112 | 0 | 0 |
| |
0 | |
0 | |
||
| init |
| 0.000025 | 0 | 0 |
| |
0 | |
0 | |
||
| optimizing |
| 0.000012 | 0 | 0 |
| |
0 | |
0 | |
||
statistics |
| 0.000025 | 0 | 0 |
| |
0 | |
0 | |
||
preparing |
| 0.000013 | 0 | 0 |
| |
0 | |
0 | |
||
executing |
| 0.000004 | 0 | 0 |
| |
0 | |
0 | |
||
Sending data |
| 0.000241 | 0 | 0 |
| |
0 | |
0 | |
||
end |
| 0.000005 | 0 | 0 |
| |
0 | |
0 | |
||
query end |
| 0.000006 | 0 | 0 |
| |
0 | |
0 | |
||
| freeing items | 0.000015 | 0 |
| 0 |
| |
0 | |
0 | |
||
| closing tables | 0.000004 | 0 |
| 0 |
| |
0 | |
0 | |
||
| removing tmp table | 0.000019 | 0 |
| 0 |
| |
0 | |
0 | |
||
| closing tables | 0.000005 | 0 |
| 0 |
| |
0 | |
0 | |
||
| logging slow query | 0.000004 | 0 |
| 0 |
| |
0 | |
0 | |
+--------------------+----------+----------+------------+--------------+---------------+
咱們先看看兩條SQL執行中的IO消耗,二者區別就在於"Sorting result",咱們回顧一下前面執行計劃的對比,兩個解決方案的排序過濾數據的時機不同,排序後須要取得的數據量一個是20000,一個是20,正好和這裏的profile信息吻合,第一種解決方案的
"Sorting result"的IO值是第二種解決方案的將近500倍。
而後再來看看CPU消耗,全部消耗中,消耗最大的也是"Sorting result"這一項,第一個消耗多出的原因和上面IO消耗差別是同樣的。
結論:
經過上面兩條功能徹底相同的SQL語句的執行計劃分析,以及經過實際執行後的 profile數據的驗證,都證實了第二種解決方案優於第一種解決方案。同時經過後者的實際驗證,也再次證實了咱們前面所作的執行計劃基本決定了SQL語句性能。
前面兩節中,咱們已經分析了在一個數據庫應用系統的軟環境中應用系統的架構實現和系統中與數據庫交互的SQL語句對系統性能的影響。在這一節咱們再分析一下系統的數據模型設計實現對系統的性能影響,更通俗一點就是數據庫的Schema設計對系統性能的影響。
在不少人看來,數據庫Schema設計是一件很是簡單的事情,就大致按照系統設計時候的相關實體對象對應成一個一個的表格基本上就能夠了。而後爲了在功能上作到儘量容易擴展,再根據數據庫範式規則進行調整,作到第三範式或者第四範式,基本就算完事了。
數據庫Schema設計真的有如上面所說的這麼簡單麼?能夠很是確定的告訴你們,數據庫Schema設計所須要作的事情遠遠不止如此。若是您以前的數據庫Schema設計一直都是這麼作的,那麼在該設計應用於正式環境以後,極可能帶來很是大的性能代價。
因爲在後面的"MySQL 數據庫應用系統設計"中的"系統架構最優化"這一節中會介較爲詳細的從性能優化的角度來分析如何如何設計數據庫Schema,因此這裏暫時先不介紹如何來設計性能優異的數據庫Schema結構,僅僅經過一個實際的示例來展現Schema結構的不同在性能方面所帶來的差別。
需求概述:一個簡單的討論區系統,須要有用戶,用戶組,組討論區這三部分基本功能簡要分析:一、須要存放用戶數據的表;
原始方案一:分別用用四個表來存放用戶,分組,用戶與組關係以及各組的討論帖子的信息如
下:
user 用戶表:
+-------------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------+------+-----+---------+-------+
| id |
| int(11) |
| NO |
| |
| 0 |
| |
| |
| nick_name |
| varchar(32) |
| NO |
| |
| NULL |
| |
| |
| password |
| char(64) |
| YES |
| |
| NULL |
| |
| |
| varchar(32) |
| NO |
| |
| NULL |
| |
| |
|
| status |
| varchar(16) |
| NO |
| |
| NULL |
| |
| |
| sexuality |
| char(1) |
| NO |
| |
| NULL |
| |
| |
| msn |
| varchar(32) |
| YES |
| |
| NULL |
| |
| |
| sign |
| varchar(64) |
| YES |
| |
| NULL |
| |
| |
| birthday |
| date |
| YES |
| |
| NULL |
| |
| |
| hobby |
| varchar(64) |
| YES |
| |
| NULL |
| |
| |
| location |
| varchar(64) |
| YES |
| |
| NULL |
| |
| |
| description | varchar(1024) | YES |
| |
| NULL |
| |
| |
+-------------+---------------+------+-----+---------+-------+
groups 分組表:
+--------------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+---------------+------+-----+---------+-------+
| id | int(11) | NO |
| |
| NULL |
| |
| |
| gmt_create | datetime | NO |
| |
| NULL |
| |
| |
| gmt_modified | datetime | NO |
| |
| NULL |
| |
| |
| name | varchar(32) | NO |
| |
| NULL |
| |
| |
| status | varchar(16) | NO |
| |
| NULL |
| |
| |
| description | varchar(1024) | YES |
| |
| NULL |
| |
| |
+--------------+---------------+------+-----+---------+-------+
user_group 關係表:
+--------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-------------+------+-----+---------+-------+
user_id | int(11) | NO |
| MUL | NULL |
| |
| |
group_id | int(11) | NO |
| MUL | NULL |
| |
| |
user_type | int(11) | NO |
| | NULL |
| |
| |
gmt_create | datetime | NO |
| | NULL |
| |
| |
gmt_modified | datetime | NO |
| | NULL |
| |
| |
status | varchar(16) | NO |
| | NULL |
| |
| |
+--------------+-------------+------+-----+---------+-------+
group_message 討論組帖子表:
+--------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+-------+
| id | int(11) | NO |
| |
| NULL |
| |
| |
| gmt_create | datetime | NO |
| |
| NULL |
| |
| |
| gmt_modified | datetime | NO |
| |
| NULL |
| |
| |
| group_id | int(11) | NO |
| |
| NULL |
| |
| |
| user_id | int(11) | NO |
| |
| NULL |
| |
| |
| subject | varchar(128) | NO |
| |
| NULL |
| |
| |
| content | text | YES |
| |
| NULL |
| |
| |
+--------------+--------------+------+-----+---------+-------+
優化後方案二: user 用戶表:
+-------------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------+------+-----+---------+-------+
| id |
| int(11) |
| NO |
| |
| 0 |
| |
| |
| nick_name |
| varchar(32) |
| NO |
| |
| NULL |
| |
| |
| password |
| char(64) |
| YES |
| |
| NULL |
| |
| |
| varchar(32) |
| NO |
| |
| NULL |
| |
| |
|
| status |
| varchar(16) |
| NO |
| |
| NULL |
| |
| |
+-------------+---------------+------+-----+---------+-------+
user_profile 用戶屬性表(記錄與user一一對應):
+-------------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------+------+-----+---------+-------+
| sexuality |
| char(1) |
| NO |
| |
| NULL |
| |
| |
| msn |
| varchar(32) |
| YES |
| |
| NULL |
| |
| |
| sign |
| varchar(64) |
| YES |
| |
| NULL |
| |
| |
| birthday |
| date |
| YES |
| |
| NULL |
| |
| |
| hobby |
| varchar(64) |
| YES |
| |
| NULL |
| |
| |
| location |
| varchar(64) |
| YES |
| |
| NULL |
| |
| |
| description | varchar(1024) | YES |
| |
| NULL |
| |
| |
+-------------+---------------+------+-----+---------+-------+
groups 和 user_group這兩個表和方案一徹底同樣
group_message 討論組帖子表:
+--------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+-------+
| id | int(11) |
| NO |
| |
| NULL |
| |
| |
| gmt_create | datetime |
| NO |
| |
| NULL |
| |
| |
| gmt_modified | datetime |
| NO |
| |
| NULL |
| |
| |
| group_id | int(11) |
| NO |
| |
| NULL |
| |
| |
| user_id | int(11) |
| NO |
| |
| NULL |
| |
| |
| author | varchar(32) |
| NO |
| |
| NULL |
| |
| |
| subject | varchar(128) | NO |
| |
| NULL |
| |
| |
+--------------+--------------+------+-----+---------+-------+
group_message_content 帖子內容表(記錄與group_message一一對應):
+--------------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+---------+------+-----+---------+-------+
| group_msg_id | int(11) | NO | | NULL | |
| content | text | NO | | NULL | |
+--------------+---------+------+-----+---------+-------+
咱們先來比較一下兩個解決方案所設計的Schema的區別。區別主要體如今兩點,一個區別是在
group_message表中增長了author字段來存放發帖做者的暱稱,與user表的nick_name相對應,另一個就是第二個解決方案將user表和group_message表都分拆成了兩個表,關係分別都是一一對應。
方案二看上去比方案一要更復雜一些,首先是表的數量多了2個,而後是在group_message中冗餘存
放了做者暱稱。咱們試想一下,一個討論區系統,訪問最多的頁面會是什麼?我想你們都會很清楚是帖子標題列表頁面。而帖子標題列表頁面最主要的信息就是都是來自 group_message表中,同時帖子標題後面的做者通常都是經過用戶名成(暱稱)來展現。按照第一種解決方案來設計的 Schema,咱們就須要執行相似以下這樣的SQL語句來獲得數據:
SELECT t.id, t.subject,user.id, u.nick_name
FROM (
SELECT id, user_id, subject FROM group_message
WHERE group_id = ?
ORDER BY gmt_modified DESC LIMIT 20
) t, user u
WHERE t.user_id = u.id
可是第二中解決方案所須要執行的SQL就會簡單不少,以下:
SELECT t.id, t.subject, t.user_id, t.author FROM group_message
WHERE group_id = ?
ORDER BY gmt_modified DESC LIMIT 20
兩個SQL相比較,你們都能很明顯的看出誰優誰劣了,第一個是須要讀取兩個表的數據進行 Join,與第二個SQL相比性能差距很大,尤爲是若是第一個再寫的差一點,性能更是很是糟糕,二者所帶來的資源消耗就更相差玄虛了。
不只僅如此,因爲第一個方案中的group_message表中還包含一個大字段"content",該字段所存放的信息要佔整個表的絕大部分存儲空間,但在這條系統中執行最頻繁的 SQL之一中是徹底不須要該字段所存放信息的,可是因爲這個SQL又沒辦法作到不訪問group_message表的數據,因此第一條SQL在數據讀取過程當中會須要讀取大量沒有任何意義的數據。
在系統中用戶數據的讀取也是比較頻繁的,可是大多數地方所須要的用戶數據都只是用戶的幾個基本屬性,如用戶的id,暱稱,密碼,狀態,郵箱等,因此將用戶表的這幾個屬性單獨分離出來後,也會讓大量的SQL語句在運行的時候減小數據的檢索量,從而提升性能。
可能有人會以爲,在咱們將一個表分紅兩個表的時候,咱們若是要訪問被分拆出去的信息的時候,性能不是就會變差了嗎?是的,對於那些須要訪問如user的sign,msn等原來只須要一個表就能夠完成的SQL來講,如今都須要兩條SQL來完成,性能確實會 有所下降,可是因爲兩個表都是一對一的關聯關係,關聯字段的過濾性也很是高,並且這樣的查詢需求在整個系統中所佔有的比例也並不高,因此這裏所帶來的性能損失實際上要遠遠小於在其餘SQL上所節省出來的資源,因此徹底沒必要爲此擔憂
在本章以前的全部部分都是介紹的整個系統中的軟件環境對系統性能的影響,這一節咱們將從系統硬件環境來分析對數據庫系統的影響,並從數據庫服務器主機的角度來作一些針對性的優化建議。
任何一個系統的硬件環境都會對起性能起到很是關鍵的做用,這一點我想每一位讀者朋友都是很是清楚的。而數據庫應用系統環境中,因爲數據庫自身的特色和在系統中的角色決定了他在整個系統中是最難以擴展的部分。因此在大多數環境下,數據庫服務器主機(或者主機集羣)的性能在很大程度上決定了整個應用系統的性能。
既然咱們的數據庫主機資源如此重要,確定不少讀者朋友會但願知道,數據庫服務器主機的各部分硬件到底誰最重要,各部分對總體性能的影響各自所佔的比例是多少,以便可以根據這些比例選取合適的主機機型做爲數據庫主機。可是我只能很遺憾的告訴你們,沒有任何一個定律或者法則能夠很準確的給出這個答案。
固然,你們也沒必要太沮喪。雖然沒有哪一個法則能夠準確的知道咱們到底該如何選配一個主機的各部分硬件,可是根據應用類型的不一樣,整體上仍是有一個能夠大體遵循的原則能夠參考的。
首先,數據庫主機是存取數據的地方,那麼其IO操做天然不會少,因此數據庫主機的IO性能確定是須要最優先考慮的一個因素,這一點無論是什麼類型的數據庫應用都是適用的。不過,這裏的IO性能並不只僅只是指物理的磁盤IO,而是主機的總體IO性能,是主機整個IO系統的整體IO性能。而IO性能自己又能夠分爲兩類,一類是每秒可提供的IO訪問次數,也就是咱們常說的IOPS數量,還有一種就是每秒的IO總流量,也就是咱們常說的IO吞吐量。在主機中決定 IO性能部件主要由磁盤和內存所決定,固然也包括各類與IO相關的板卡。其次,因爲數據庫主機和普通的應用程序服務器相比,資源要相對集中不少,單臺主機上所須要進行的計算量天然也就比較多,因此數據庫主機的CPU處理能力也不能忽視。
最後,因爲數據庫負責數據的存儲,與各應用程序的交互中傳遞的數據量比其餘各種服務器都要多,因此數據庫主機的網絡設備的性能也可能會成爲系統的瓶頸。
因爲上面這三類部件是影響數據庫主機性能的最主要因素,其餘部件成爲性能瓶頸的概率要小不少,因此後面咱們經過對各類類型的應用作一個簡單的分析,再針對性的給出這三類部件的基本選型建議。
一、典型OLTP應用系統
對於各類數據庫系統環境中你們最多見的OLTP系統,其特色是併發量大,總體數據量比較多,但每次訪問的數據比較少,且訪問的數據比較離散,活躍數據佔整體數據的比例不是太大。對於這類系統的數據庫其實是最難維護,最難以優化的,對主機總體性能要求也是最高的。由於他不只訪問量很高,數據量也不小。
針對上面的這些特色和分析,咱們能夠對OLTP的得出一個大體的方向。
雖然系統整體數據量較大,可是系統活躍數據在數據總量中所佔的比例不大,那麼咱們能夠經過擴大內存容量來儘量多的將活躍數據cache到內存中;
雖然IO訪問很是頻繁,可是每次訪問的數據量較少且很離散,那麼咱們對磁盤存儲的要求是IOPS表現要很好,吞吐量是次要因素;併發量很高,CPU每秒所要處理的請求天然也就不少,因此CPU處理能力須要比較強勁;
雖然與客戶端的每次交互的數據量並非特別大,可是網絡交互很是頻繁,因此主機與客戶端交互的網絡設備對流量能力也要求不能太弱。
二、典型OLAP應用系統
用於數據分析的OLAP系統的主要特色就是數據量很是大,併發訪問很少,但每次訪問所須要檢索的數據量都比較多,並且數據訪問相對較爲集中,沒有太明顯的活躍數據概念。
基於OLAP系統的各類特色和相應的分析,針對OLAP系統硬件優化的大體策略以下:數據量很是大,因此磁盤存儲系統的單位容量須要儘可能大一些;
單次訪問數據量較大,並且訪問數據比較集中,那麼對IO系統的性能要求是須要有儘量大的每秒
IO吞吐量,因此應該選用每秒吞吐量儘量大的磁盤;
雖然IO性能要求也比較高,可是併發請求較少,因此CPU處理能力較難成爲性能瓶頸,因此CPU處理能力沒有太苛刻的要求;
雖然每次請求的訪問量很大,可是執行過程當中的數據大都不會返回給客戶端,最終返回給客戶端的數據量都較小,因此和客戶端交互的網絡設備要求並非過高;
此外,因爲OLAP系統因爲其每次運算過程較長,能夠很好的並行化,因此通常的OLAP系統都是由多臺主機構成的一個集羣,而集羣中主機與主機之間的數據交互量通常來講都是很是大的,因此在集羣中主機之間的網絡設備要求很高。
三、除了以上兩個典型應用以外,還有一類比較特殊的應用系統,他們的數據量不是特別大,可是訪問請求及其頻繁,並且大部分是讀請求。可能每秒須要提供上萬甚至幾萬次請求,每次請求都很是簡單,可能大部分都只有一條或者幾條比較小的記錄返回,就好比基於數據庫的 DNS服務就是這樣類型的服務。
雖然數據量小,可是訪問極其頻繁,因此能夠經過較大的內存來cache住大部分的數據,這可以保證很是高的命中率,磁盤IO量比較小,因此磁盤也不須要特別高性能的;併發請求很是頻繁,比須要較強的CPU處理能力才能處理;
雖然應用與數據庫交互量很是大,可是每次交互數據較少,整體流量雖然也會較大,可是通常來講普通的千兆網卡已經足夠了。
在不少人看來,性能的根本決定因素是硬件性能的好壞。但實際上,硬件性能只能在某些階段對系統性能產生根本性影響。當咱們的CPU處理能力足夠的多,IO系統的處理能力足夠強的時候,若是咱們的應用架構和業務實現不夠優化,一個原本很簡單的實現非得繞不少個彎子來回交互屢次,那再強的硬件也沒有用,由於來回的交互老是須要消耗時間。尤爲是有些業務邏輯設計不是特別合理的應用,數據庫Schema設計的不夠合理,一個任務在系統中又被分拆成不少個步驟,每一個步驟都使用了很是複雜的
Query語句。筆者曾經就遇到過這樣一個系統,該系統是購買的某知名廠商的一個項目管理軟件。該系統最初運行在一臺Dell2950的PC Server上面,使用者一直抱怨系統響應很慢,但我從服務器上面的狀態來看系統並繁忙(系統併發不是太大)。後來使用者強烈要求經過更換硬件設施來提高系統性能,雖然我一直反對,但最後在管理層的要求下,更換成了一臺Sun的S880小型機,主機CPU的處理能力至少是原來機器的3倍以上,存儲系統也從原來使用本地磁盤換成使用EMC的中斷存儲CX300。可在試用階段,發現系統總體性能沒有任何的提高,最終仍是取消了更換硬件的計劃。
因此,在應用系統的硬件配置方面,咱們應該要以一個理性的眼光來看待,只有合適的纔是最好的。並非說硬件資源越好,系統性能就必定會越好。並且,硬件系統自己老是有一個擴展極限的,若是咱們一味的但願經過升級硬件性能來解決系統的性能問題,那麼總有一天將會遇到沒法逾越的瓶頸。
到那時候,就算有再多的錢去砸也無濟於事了。
雖然本章是以影響MySQL Server性能的相關因素來展開分析,但實際上不少內容都對於大多數數據庫應用系統適用。數據庫管理軟件僅僅是實現了數據庫應用系統中的數據存取操做,和數據的持久化。數據庫應用系統的優化真正能帶來最大收益的就是商業需求和系統架構及業務實現的優化,而後是數據庫Schema設計的優化,而後纔是Query語句的優化,最後纔是數據庫管理軟件自身的一些優化。經過筆者的經驗,在整個系統的性能優化中,若是按照百分比來劃分上面幾個層面的優化帶來的性能收益,能夠得出大概以下的數據:需求和架構及業務實現優化:55%
Query語句的優化:30% 數據庫自身的優化:15%
不少時候,你們看到數據庫應用系統中性能瓶頸出如今數據庫方面,就但願經過數據庫的優化來解決問題,但無論DBA對數據庫多們瞭解,對Query語句的優化多麼精通,最終仍是很難解決整個系統的性能問題。緣由就在於並無真正找到根本的癥結所在。
因此,數據庫應用系統的優化,其實是一個須要多方面配合,多方面優化的才能產生根本性改善的事情。簡單來講,能夠經過下面三句話來簡單的歸納數據庫應用系統的性能優化:商業需求合理化,系統架構最優化,邏輯實現精簡化,硬件設施理性化。