數據庫操做是當今 Web 應用程序中的主要瓶頸。 不只是 DBA(數據庫管理員)須要爲各類性能問題操心,程序員爲作出準確的結構化表,優化查詢性能和編寫更優代碼,也要費盡心思。 在本文中,我列出了一些針對程序員的 MySQL 優化技術。 |
達爾文 0人頂mysql 頂 翻譯的不錯哦!程序員 |
1.優化查詢的查詢緩存大部分MySQL服務器都有查詢緩存功能。這是提升性能的最有效的方法之一,這是由數據庫引擎私下處理的。當同一個查詢被屢次執行,結果會直接從緩存裏提取,這樣速度就很快。sql 主要的問題是,這對程序員來講太簡單了,不容易看到,咱們不少人都容易忽略。咱們其實是能夠組織查詢緩存執行任務的。數據庫 // do I have any users from Alabama? // what NOT to do: $r = mysql_query("SELECT * FROM user WHERE state = 'Alabama'"); if (mysql_num_rows($r) > 0) { // ... } // much better: $r = mysql_query("SELECT 1 FROM user WHERE state = 'Alabama' LIMIT 1"); if (mysql_num_rows($r) > 0) { // ... } 查詢緩存在第一行不執行的緣由在於CURDTE()功能的使用。這適用於全部的非肯定性功能,就像NOW()和RAND()等等。。。由於功能返回的結果是可變的。MySQL決定禁用查詢器的查詢緩存。咱們所須要作的是經過添加一額外一行PHP,在查詢前阻止它發生。緩存 |
2. EXPLAIN你的選擇查詢使用EXPLAIN關鍵詞能夠幫助瞭解MySQL是怎樣運行你的查詢的。這有助於發現瓶頸和查詢或表結構的其它問題。安全 EXPLAIN的查詢結果會展現哪個索引被使用過,表示怎樣掃描和儲存的,等等。。。服務器 選擇一個SELECT查詢(一個有鏈接的複雜查詢會更好),在它的前面添加關鍵詞EXPLAIN,這樣就能夠直接使用數據庫了。結果會以一個漂亮的表來展現。例如,就比如我執行鏈接時忘了添加一欄的索引:網絡 如今它只會從表2裏面掃描9和16行,而非掃描7883行。經驗法則是乘以全部「行」那一欄的數字,你的查詢性能會跟結果數字成比例的。 |
3. 獲取惟一行時使用LIMIT 1有時當你查表時,你已經知道你正在查找的結果只有一行。你可能正在獲取惟一記錄,或者你可能只是查詢是否存在知足你的WHERE子句條件的記錄。
// what NOT to do: $r = mysql_query("SELECT username FROM user ORDER BY RAND() LIMIT 1"); // much better: $r = mysql_query("SELECT count(*) FROM user"); $d = mysql_fetch_row($r); $rand = mt_rand(0,$d[0] - 1); $r = mysql_query("SELECT username FROM user LIMIT $rand, 1");
|
4. 索引搜索字段索引不只僅是爲了主鍵或惟一鍵。若是你會在你的表中按照任何列搜索,你就都應該索引它們。 正如你所看到的,這個規則也適用於如 "last_name LIKE 'a%'"的部分字符串搜索。當從字符串的開頭搜索時,MySQL就可使用那一列的索引。 你也應該明白什麼樣搜索能夠不使用有規律的索引。例如,當搜索一個單詞時(例如,"WHERE post_content LIKE '%apple%'"),你將不會看到普通索引的好處。你最好使用 mysql 全文搜索或者構建你本身的索引解決方案。
|
5. 索引並對鏈接使用一樣的字段類型若是你的應用程序包含許多鏈接查詢, 你須要確保鏈接的字段在兩張表上都創建了索引。 這會影響MySQL如何內部優化鏈接操做。 此外,被鏈接的字段,須要使用一樣類型。例如, 若是你使用一個DECIMAL字段, 鏈接另外一張表的INT字段, MySQL將沒法使用至少一個索引。 即便字符編碼也須要使用相同的字符類型。 // looking for companies in my state $r = mysql_query("SELECT company_name FROM users LEFT JOIN companies ON (users.state = companies.state) WHERE users.id = $user_id"); // both state columns should be indexed // and they both should be the same type and character encoding // or MySQL might do full table scans 6. 不要ORDER BY RAND()起初這是一個聽起來挺酷的技巧, 讓許多菜鳥程序員陷入了這個陷阱。但你可能不知道,一旦你開始在查詢中使用它,你建立了很是可怕的查詢瓶頸。 若是你真的須要對結果隨機排序, 這有一個更好的方法。補充一些額外代碼,你將能夠防止當數據成指數級增加時形成的瓶頸。關鍵問題是,MySQL必須在排序以前對錶中的每一行執行RAND()操做(這須要處理能力),而且僅僅給出一行。 // what NOT to do: $r = mysql_query("SELECT username FROM user ORDER BY RAND() LIMIT 1"); // much better: $r = mysql_query("SELECT count(*) FROM user"); $d = mysql_fetch_row($r); $rand = mt_rand(0,$d[0] - 1); $r = mysql_query("SELECT username FROM user LIMIT $rand, 1"); 因此挑選一個小於結果數的隨機數,並將其用做LIMIT子句中的偏移量。 |
7. 避免使用SELECT *從數據表中讀取的數據越多,查詢操做速度就越慢。它增長了磁盤操做所需的時間。此外,當數據庫服務器與Web服務器分開時,因爲必須在服務器之間傳輸數據,將會有更長的網絡延遲。 // not preferred $r = mysql_query("SELECT * FROM user WHERE user_id = 1"); $d = mysql_fetch_assoc($r); echo "Welcome {$d['username']}"; // better: $r = mysql_query("SELECT username FROM user WHERE user_id = 1"); $d = mysql_fetch_assoc($r); echo "Welcome {$d['username']}"; // the differences are more significant with bigger result sets 8. 幾乎老是有一個id字段在每一個以id列爲PRIMARY KEY的數據表中,優先選擇AUTO_INCREMENT或者INT。 也能夠優選使用UNSIGNED,由於該值不能爲負的。 即便你擁有一個具備惟一用戶名字段的用戶表,也不要將其做爲主鍵。 VARCHAR字段做爲主鍵(檢索)速度較慢。經過內部ID引用全部的用戶數據,你的代碼中將更加結構化。 有些後臺操做是由MySQL引擎自己完成的,它在內部使用主鍵字段。當數據庫設置越複雜(集羣,分區等...),這就變得更加劇要了。 |
9. 相比VARCHAR優先使用ENUMENUM枚舉類型是很是快速和緊湊的。在內部它們像TINYINT同樣存儲,但它們能夠包含和顯示字符串值。這使他們成爲某些領域的完美候選。 若是有一個字段只包含幾種不一樣的值,請使用ENUM而不是VARCHAR。例如,它能夠是名爲「status」的列,而且只包含諸如「active」,「inactive」,「pending」,「expired」等的值... |
10. 使用PROCEDURE ANALYSE()獲取建議PROCEDURE ANALYSE() 將使用MySQL分析列結構和表中的實際數據,爲你提供一些建議。它只有在數據表中有實際數據時纔有用,由於這在分析決策時很重要。 例如,若是你建立了一個INT類型的主鍵,但沒有太多行,MySQL則可能建議您改用MEDIUMINT。或者若是你使用VARCHAR字段,若是表裏只有不多的取值,你可能會獲得一個建議是將其轉換爲ENUM。 你也能夠在其中一個表視圖中單擊phpmyadmin中的「建議表結構」連接來執行此操做。 請記住,這些只是建議。 若是你的數據表變得愈來愈大,他們甚至可能不是正確的建議。至於如何修改最終是你來決定。 |
11. 若是能夠的話使用NOT NULL除非你有很是重要的理由使用NULL值,不然你應該設置你的列爲NOT NULL。 首先,問一下你本身在空字符串值和NULL值之間(對應INT字段:0 vs. NULL)是否有任何的不一樣.若是沒有理由一塊兒使用這兩個,那麼你就不須要一個NULL字段(你知道在Oracle中NULL和空字符串是同樣的嗎?)。 NULL列須要額外的空間,他們增長了你的比較語句的複雜度。若是能夠的話儘可能避免它們。固然,我理解一些人,他們也許有很是重要的理由使用NULL值,這不老是一件壞事。 摘自MySQL 文檔:
|
12. 預處理語句使用預處理語句有諸多好處,包括更高的性能和更好的安全性。 預處理語句默認狀況下會過濾綁定到它的變量,這對於避免SQL注入攻擊極爲有效。固然你也能夠指定要過濾的變量。但這些方法更容易出現人爲錯誤,也更容易被程序員遺忘。這在使用框架或 ORM 的時候會出現一些問題。 既然咱們關注性能,那就應該說說這個方面的好處。當在應用中屢次使用同一個查詢的時候,它的好處特別明顯。既然向同一個預備好的語句中傳入不一樣的參數值,MySQL 對這個語句也只會進行一次解析。 同時,最新版本的 MySQL 在傳輸預備好的語句時會採用二進制形式,這樣作的做用很是明顯,並且對減小網絡延遲頗有幫助。 曾經有一段時間,許多程序員爲了一個重要的緣由則避免使用預處理語句。這個緣由就是,它們不會被MySQL 緩存。不過在 5.1 版本的某個時候,查詢緩存也獲得的支持。 想在 PHP 中使用預處理語句,你能夠看看 mysqli 擴展 或使用數據抽象層,如 PDO。 // create a prepared statement if ($stmt = $mysqli->prepare("SELECT username FROM user WHERE state=?")) { // bind parameters $stmt->bind_param("s", $state); // execute $stmt->execute(); // bind result variables $stmt->bind_result($username); // fetch value $stmt->fetch(); printf("%s is from %s\n", $username, $state); $stmt->close(); }
|
13. 無緩衝查詢一般當你從腳本執行一個查詢,在它能夠繼續後面的任務以前將須要等待查詢執行完成。你可使用無緩衝的查詢來改變這一狀況。 在PHP 文檔中對 mysql_unbuffered_query() f函數有一個很好的解釋:
然而,它有必定的侷限性。你必須在執行另外一個查詢以前讀取全部的行或調用mysql_free_result() 。另外你不能在結果集上使用mysql_num_rows() 或 mysql_data_seek() 。 |
14. 使用 UNSIGNED INT 存儲IP地址不少程序員沒有意識到可使用整數類型的字段來存儲 IP 地址,因此一直使用 VARCHAR(15) 類型的字段。使用 INT 只須要 4 個字節的空間,並且字段長度固定。 必須確保列是 UNSINGED INT 類型,由於 IP 地址可能會用到 32 位無符號整型數據的每個位。 在查詢中可使用 INET_ATON() 來把一個IP轉換爲整數,用 INET_NTOA() 來進行相反的操做。在 PHP 也有相似的函數,ip2long() 和 long2ip()。 $r = "UPDATE users SET ip = INET_ATON('{$_SERVER['REMOTE_ADDR']}') WHERE user_id = $user_id";
|
15. 固定長度(靜態)的表會更快(譯者注:這裏提到的表的長度,實際是指表頭的長度,即表中每條數據佔用的空間大小,而不是指表的數據量) 若是表中全部列都是「固定長度」,那麼這個表被認爲是「靜態」或「固定長度」的。不固定的列類型包括 VARCHAR、TEXT、BLOB等。即便表中只包含一個這些類型的列,這個表就再也不是固定長度的,MySQL 引擎會以不一樣的方式來處理它。 固定長度的表會提升性能,由於 MySQL 引擎在記錄中檢索的時候速度會更快。若是想讀取表中的某一地,它能夠直接計算出這一行的位置。若是行的大小不固定,那就須要在主鍵中進行檢索。 它們也易於緩存,崩潰後容易重建。不過它們也會佔用更多空間。例如,若是你把一個 VARCHAR(20) 的字符改成 CHAR(20) 類型,它會老是佔用 20 個字節,無論裏面存的是什麼內容。 你可使用「垂直分區」技術,將長度變化的列拆分到另外一張表中。來看看: |
17. 拆分大型DELETE或INSERT語句若是你須要在網站上執行大型DELETE或INSERT查詢,則須要注意不要影響網絡流量。當執行大型語句時,它會鎖表並使你的Web應用程序中止。 Apache運行許多並行進程/線程。 所以它執行腳本效率很高。因此服務器不指望打開過多的鏈接和進程,這很消耗資源,特別是內存。 若是你鎖表很長時間(如30秒或更長),在一個高流量的網站,會致使進程和查詢堆積,處理這些進程和查詢可能須要很長時間,最終甚至使你的網站崩潰。 若是你的維護腳本須要刪除大量的行,只需使用LIMIT子句,以免阻塞。 while (1) { mysql_query("DELETE FROM logs WHERE log_date <= '2009-10-01' LIMIT 10000"); if (mysql_affected_rows() == 0) { // done deleting break; } // you can even pause a bit usleep(50000); }
|
18. 越小的列越快對於數據庫引擎來講,磁盤空間多是最須要注意的瓶頸。對性能而言,「小」和「緊縮」有助於減小磁盤傳輸量。 MySQL 文檔中有一個列表,列舉了各類數據類型所須要的存儲空間。 若是數據表預計只會有少許的行,那就不必把主鍵定義爲 INT 類型,能夠用 MEDIUMINT、SMALLINT 甚至 TINYINT 來代替。(譯者注:對於日期數據,)若是不須要時間部分,就應該使用 DATE 而不是 DATETIME。 請確保留出合理的數據成長空間,否則就可能形成像Slashdot那樣的結果(譯者注:Slashdot 由於數據增加將評論表的主鍵改成了 INT 型,但沒有修改其父表中的相應的數據類型,雖然一個 ALTER 語句就能夠解決問題,可是須要至少中止某些業務三個小時)。 19. 選擇正確的存儲引擎MySQL 有兩個主要的存儲引擎:MyISAM 和 InnoDB,它們各有利弊。 MyISAM 適用於讀請求特別多的應用,但不適用於有大量寫請求的狀況。甚至你只是要更新一行中的某個字段,都會形成整張表被鎖,而後直到這個查詢完成,其它進程都不能從這張表讀取數據。MyISAM 在計算 SELECT COUNT(*) 這種類型的查詢時速度很是快。 InnoDB 是一個複雜的存儲引擎,在多數小型應用中它比 MyISAM 慢。可是它支持行級鎖,有更好的尺度。它還支持一些高級特性,好比事務。 |
20. 使用對象關係映射器(ORM, Object Relational Mapper)經過使用ORM(對象關係映射器),你能夠得到必定的性能提高。ORM能夠完成的一切事情,手動編碼也可完成。但這可能意味着須要太多額外的工做,而且須要高水平的專業知識。 |
21. 當心使用持久鏈接持久鏈接意味着減小重建鏈接到MySQL的成本。 當持久鏈接被建立時,它將保持打開狀態直到腳本完成運行。 由於Apache重用它的子進程,下一次進程運行一個新的腳本時,它將重用相同的MySQL鏈接。
理論上看起來不錯。 但從我我的(和許多其餘人)的經驗看來,這個功能可能會致使更多麻煩。 你可能會出現鏈接數限制問題、內存問題等等。 Apache老是並行運行的,它建立許多子進程。 這是持久鏈接在這種環境中不能很好工做的主要緣由。 在你考慮使用mysql_pconnect()以前,請諮詢你的系統管理員。 |