最佳化概述
讓系統運行得快最重要因素是資料庫的基本設計。並且還必須清楚系統要用來做什麼,以及存在的瓶頸。
最常見的系統瓶頸有以下幾種:
磁碟搜尋
它慢慢地在磁碟中搜尋數據塊。對現代磁碟來說,平時的搜尋時間基本上小於10毫秒,因此理論上每秒鐘可以做100次磁碟搜尋。這個時間對於全新的新磁碟來說提高的不多,並且對於只有一個數據表的情況也是如此。加快搜尋時間的方法是將數據分開存放到多個磁碟中。
磁碟讀/寫
當磁碟在正確的位置上時,就需要讀取數據。對現代磁碟來說,磁碟吞吐量至少是10-20MB/秒。這比磁碟搜尋的最佳化更容易,因為可以從多個媒介中並行地讀取數據。
CPU周期
數據存儲在主記憶體中(或者它已經在主記憶體中了),這就需要處理這些數據以得到想要的結果。
記憶體頻寬
當CPU要將更多的數據存放在CPU快取中時,主記憶體的頻寬就是瓶頸了。在大多數系統中,這不是常見的瓶頸,不過也是要注意的一個因素。
MySQL設計的局限性
當使用MyISAM存儲引擎時,MySQL會使用一個快速數據表鎖以允許同時多個讀取和一個寫入。這種存儲引擎的最大問題是發生在一個單一的表上同時做穩定的更新操作及慢速查詢。如果這種情況在某個表中存在,可以使用另一種表類型。
MySQL可以同時在事務及非事務表下工作。為了能夠平滑的使用非事務表(發生錯誤時不能回滾),有以下幾條規則:
所有的欄位都有默認值
如果欄位中插入了一個"錯誤"的值,比如在數字類型欄位中插入過大數值,那么MySQL會將該欄位值置為"最可能的值"而不是給出一個錯誤。數字類型的值是0,最小或者最大的可能值。字元串類型,不是空字元串就是欄位所能存儲的最大長度。
所有的計算表達式都會返回一個值而報告條件錯誤,例如1/0返回NULL。
這些規則隱含的意思是,不能使用MySQL來檢查欄位內容。相反地,必須在存儲到資料庫前在應用程式中來檢查。
套用設計的可移植性
由於各種不同的資料庫實現了各自的SQL標準,這就需要儘量使用可移植的SQL套用。查詢和插入操作很容易就能做到可移植,不過由於更多的約束條件的要求就越發困難。想要讓一個套用在各種資料庫系統上快速運行,就變得更困難了。
為了能讓一個複雜的套用做到可移植,就要先看這個套用運行於哪種資料庫系統之上,然後看這些資料庫系統都支持哪些特性。每個資料庫系統都有某些不足。也就是說,由於設計上的一些妥協,導致了性能上的差異。
可以用MySQL的crash-me程式來看選定的資料庫伺服器上可以使用的函式,類型,限制等。crash-me不會檢查各種可能存在的特性,不過這仍然是合乎情理的理解,大約做了450次測試。一個crash-me的信息類型的例子就是,它會告訴如果想使用Informix或DB2的話,就不能使欄位名長度超過18個字元。
crash-me程式和MySQL基準使每個準資料庫都實現了的。可以通過閱讀這些基準程式是怎么寫的,自己就大概有怎樣做才能讓程式獨立於各種資料庫這方面的想法了。這些程式可以在MySQL原始碼的`sql-bench'目錄下找到。他們大部分都是用Perl寫的,並且使用DBI接口。由於它提供了獨立於資料庫的各種訪問方式,因此用DBI來解決各種移植性的問題。
如果想努力做到獨立於資料庫,這就需要對各種SQL伺服器的瓶頸都有一些很好的想法。
例如:
MySQL對於MyISAM類型的表在檢索以及更新記錄時非常快,但是在有並發的慢速讀取及寫入記錄時卻有一定的問題。
Oracle在訪問剛剛被更新的記錄時有很大的問題(直到結果被刷新到磁碟中)。
事務資料庫一般地在從日誌表中生成摘要表這方面的表現不怎么好,因為在這種情況下,行記錄鎖幾乎沒用。
為了能讓應用程式真正的做到獨立於資料庫,就必須把運算元據的接口定義的簡單且可擴展。由於C++在很多系統上都可以使用,因此使用C++作為資料庫的基類結果很合適。
如果使用了某些資料庫獨有的特定功能(比如REPLACE語句就只在MySQL中獨有),這就需要通過編寫替代方法來在其他資料庫中實現這個功能。儘管這些替代方法可能會比較慢,但是它能讓其他資料庫實現同樣的功能。
在MySQL中,可以在查詢語句中使用/*!*/語法來增加MySQL特有的關鍵字。然而在很多其他資料庫中,/**/卻被當成了注釋(並且被忽略)。
如果有時候更高的性能比數據結果的精確更重要,就像在一些Web套用中那樣,這可以使用一個套用層來快取結果,這可能會有更高的性能。通過讓舊數據在一定時間後過期,來合理的更新快取。這是處理負載高峰期時的一種方法,這種情況下,可以通過加大快取容量和過期時間直到負載趨於正常。
這種情況下,建表信息中就要包含了初始化快取的容量以及正常刷新數據表的頻率。一個實現套用層快取的可選方案是使用MySQL的查詢快取(querycache)。啟用查詢快取後,資料庫就會根據一些詳情來決定哪些結果可以被重用。它大大簡化了應用程式。
都用MySQL來做什麼
在MySQL最開始的開發過程中,MySQL本來是要準備給大客戶用的,他們是瑞典的2個最大的零售商,他們用於貨物存儲數據管理。
他們每周從所有的商店中得到交易利潤累計結果,以此給商店的老闆提供有用的信息,幫助他們分析如果更好的打廣告以影響他們的客戶。
數據量相當的大(每個月的交易累計結果大概有7百萬),而且還需要顯示4-10年間的數據。每周都得到客戶的需求,他們要求能‘瞬間’地得到數據的最新報表。
把每個月的全部信息存儲在一個壓縮的‘交易’表中以解決這個問題。有一些簡單的宏指令集,它們能根據不同的標準從存儲的‘交易’表中根據欄位分組(產品組、客戶id、商店等等)取得結果。用一個小Perl腳本動態的生成Web頁面形式的報表。這個腳本解析Web頁面,執行SQL語句,並且插入結果。還可以用PHP或者mod_perl來做這個工作,不過當時還沒有這2個工具。
為了得到圖形數據,還寫了一個簡單的C語言工具,用於執行SQL查詢並且將結果做成GIF圖片。這個工具同樣是Perl腳本解析Web頁面後動態執行的。
很多情況下,只要拷貝現有的腳本簡單的修改裡面的SQL查詢語句就能產生新的報表了。有時候,就需要在現存的累計表中增加更多的欄位或者新建一個。這個操作十分簡單,因為在磁碟上存儲有所有的交易表(總共大概有50G的交易表以及20G的其他客戶資料)。
還允許客戶通過ODBC直接訪問累計表,這樣的話,那些高級用戶就可以自己利用這些數據做試驗了。這個系統工作的很好,並且在適度的Sun Ultra SPARC工作站(2x200MHz)上處理數據沒有任何問題。最終這個系統移植到了Linux上。
MySQL基準套件
基準套件就是想告訴用戶執行什麼樣的SQL查詢表現的更好或者更差。請注意,這個基準是單執行緒的,因此它度量了操作執行的最少時間。mysql未來打算增加多執行緒測試的基準套件。
想要使用基準套件,必備以下幾個條件:
基準腳本是用Perl寫的,它用Perl的DBI模組來連線資料庫,因此必須安裝DBI模組。並且還需要每個要做測試的伺服器上都有特定的BDB驅動程式。例如,為了測試MySQL、Postgre SQL和DB2,就必須安裝DBD::mysql,DBD::Pg及DBD::DB2模組。
取得MySQL的分發原始碼後,就能在`sql-bench'目錄下看到基準套件。想要運行這些基準測試,請先搭建好服務,然後進入`sql-bench'目錄,執行run-all-tests腳本:
shell>cdsql-bench
shell>perlrun-all-tests --server=server_name
server_name可以是任何一個可用的服務。想要列出所有的可用選項和支持的服務,只要調用以下命令:
shell>perlrun-all-tests--help
crash-me腳本也是放在`sql-bench'目錄下。crash-me通過執行真正的查詢以試圖判斷資料庫都支持什麼特性、性能表現以及限制。例如,它可以判斷:
·都支持什麼欄位類型
·支持多少索引
·支持什麼樣的函式
·能支持多大的查詢
·VARCHAR欄位類型能支持多大
使用自己的基準
請確定對資料庫或者應用程式做基準測試,以發現它們的瓶頸所在。解決這個瓶頸(或者使用一個假的模組來代替)之後,就能很容易地找到下一個瓶頸了。即使應用程式當前總體的表現可以接受,不過還是至少要做好找到每個瓶頸的計畫,說不定某天就希望應用程式能有更好的性能。
從MySQL的基準套件中就能找到一個便攜可移植的基準測試程式了。
可以從基準套件中的任何一個程式,做適當的修改以適合需要。通過整個方式,就可以有各種不同的辦法來解決問題,知道哪個程式才是最快的。
當系統負載十分繁重的時候,通常就會發生問題。就有很多客戶聯繫說他們有一個(測試過的)生產系統也遭遇了負載問題。在很多情況下,性能問題歸結於資料庫的基本設計(例如,在高負載下掃描數據表的表現不好)、作業系統、或者程式庫等因素。很多時候,這些問題在還沒有正式用於生產前相對更容易解決。
最佳化SELECT語句及其他查詢
首先,影響所有語句的一個因素是:許可權設定越複雜,那么開銷就越大。使用比較簡單的GRANT語句能讓MySQL減少在客戶端執行語句時許可權檢查的開銷。例如,如果沒有設定任何表級或者欄位級的許可權,那么伺服器就無需檢查tables_priv和columns_priv表的記錄了。同樣地,如果沒有對帳戶設定任何資源限制的話,那么伺服器也就無需做資源使用統計了。如果有大量查詢的話,花點時間來規劃簡單的授權機制以減少伺服器許可權檢查的開銷是值得的。
如果問題處在一些MySQL特定的表達式或者函式上,則可以通過mysql客戶端程式使用BENCHMARK()函式做一個定時測試。它的語法是:BENCHMARK(loop_count,expression)。例如:
所有的MySQL函式都應該被最最佳化,不過仍然有些函式例外。BENCHMARK()是一個用於檢查查詢語句中是否存在問題的非常好的工具。