MySQL 的性能優化最佳實踐

數據庫操作是當今 Web 應用程序中的主要瓶頸。 不僅是 DBA(數據庫管理員)需要爲各種性能問題操心,程序員爲做出準確的結構化表,優化查詢性能和編寫更優代碼,也要費盡心思。 在本文中,我列出了一些針對程序員的 MySQL 優化技術。
在我們開始學習之前,我補充一點:你可以在 Envato Market 上找到大量的 MySQL 腳本和實用程序。

優化查詢的查詢緩存

大部分MySQL服務器都有查詢緩存功能。這是提高性能的最有效的方法之一,這是由數據庫引擎私下處理的。當同一個查詢被多次執行,結果會直接從緩存裏提取,這樣速度就很快。
主要的問題是,這對程序員來說太簡單了,不容易看到,我們很多人都容易忽略。我們實際上是可以組織查詢緩存執行任務的。


// query cache does  NOT work
$r = mysql_query( "SELECT username FROM user WHERE signup_date >= CURDATE()" );
// query cache works!
$today =  date ( "Y-m-d" );
$r = mysql_query( "SELECT username FROM user WHERE signup_date >= '$today'" );

查詢緩存在第一行不執行的原因在於CURDTE()功能的使用。這適用於所有的非確定性功能,就像NOW()和RAND()等等。因爲功能返回的結果是可變的。MySQL決定禁用查詢器的查詢緩存。我們所需要做的是通過添加一額外一行PHP,在查詢前阻止它發生。

EXPLAIN 你的選擇查詢

使用EXPLAIN關鍵詞可以幫助瞭解MySQL是怎樣運行你的查詢的。這有助於發現瓶頸和查詢或表結構的其它問題。
EXPLAIN的查詢結果會展示哪一個索引被使用過,表示怎樣掃描和儲存的,等等
選擇一個SELECT查詢(一個有連接的複雜查詢會更好),在它的前面添加關鍵詞EXPLAIN,這樣就可以直接使用數據庫了。結果會以一個漂亮的表來展示。例如,就好比我執行連接時忘了添加一欄的索引:

MySQL EXPLAIN

現在它只會從表2裏面掃描9和16行,而非掃描7883行。經驗法則是乘以所有「行」那一欄的數字,你的查詢性能會跟結果數字成比例的。

獲取唯一行時使用LIMIT 1

有時當你查表時,你已經知道你正在查找的結果只有一行。你可能正在獲取唯一記錄,或者你可能只是查詢是否存在滿足你的WHERE子句條件的記錄。
在這種情況下,將LIMIT 1添加到查詢條件中可以提高性能。這樣,數據庫引擎將在找到剛剛第一個記錄之後停止掃描記錄,而不是遍歷整個表或索引。

1
2
3
4
5
6
7
8
$r = mysql_query( "SELECT * FROM user WHERE state = 'Alabama'" );
if (mysql_num_rows($r) > 0) {
     // ...
}
$r = mysql_query( "SELECT 1 FROM user WHERE state = 'Alabama' LIMIT 1" );
if (mysql_num_rows($r) > 0) {
     // ...
}

索引搜索字段

索引不僅僅是爲了主鍵或唯一鍵。如果你會在你的表中按照任何列搜索,你就都應該索引它們。

MySQL 索引字段的使用

正如你所看到的,這個規則也適用於如 「last_name LIKE ‘a%’」的部分字符串搜索。當從字符串的開頭搜索時,MySQL就可以使用那一列的索引。
你也應該明白什麼樣搜索可以不使用有規律的索引。例如,當搜索一個單詞時(例如,」WHERE post_content LIKE ‘%apple%’」),你將不會看到普通索引的好處。你最好使用 mysql 全文搜索或者構建你自己的索引解決方案。

索引並對連接使用同樣的字段類型

如果你的應用程序包含許多連接查詢, 你需要確保連接的字段在兩張表上都建立了索引。 這會影響MySQL如何內部優化連接操作。
此外,被連接的字段,需要使用同樣類型。例如, 如果你使用一個DECIMAL字段, 連接另一張表的INT字段, MySQL將無法使用至少一個索引。 即使字符編碼也需要使用相同的字符類型。

1
2
3
$r = mysql_query( "SELECT company_name FROM users
     LEFT JOIN companies ON (users.state = companies.state)
     WHERE users.id = $user_id" );

不要ORDER BY RAND()

起初這是一個聽起來挺酷的技巧, 讓許多菜鳥程序員陷入了這個陷阱。但你可能不知道,一旦你開始在查詢中使用它,你創建了非常可怕的查詢瓶頸。
如果你真的需要對結果隨機排序, 這有一個更好的方法。補充一些額外代碼,你將可以防止當數據成指數級增長時造成的瓶頸。關鍵問題是,MySQL必須在排序之前對錶中的每一行執行RAND()操作(這需要處理能力),並且僅僅給出一行。

1
2
3
4
5
$r = mysql_query( "SELECT username FROM user ORDER BY RAND() LIMIT 1" );
$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子句中的偏移量。

避免使用SELECT *

從數據表中讀取的數據越多,查詢操作速度就越慢。它增加了磁盤操作所需的時間。此外,當數據庫服務器與Web服務器分開時,由於必須在服務器之間傳輸數據,將會有更長的網絡延遲。
這是一個好習慣:當你使用SELECT語句時總是指定你需要的列。

1
2
3
4
5
6
$r = mysql_query( "SELECT * FROM user WHERE user_id = 1" );
$d = mysql_fetch_assoc( $r );
echo "Welcome {$d['username']}" ;
$r = mysql_query( "SELECT username FROM user WHERE user_id = 1" );
$d = mysql_fetch_assoc( $r );
echo "Welcome {$d['username']}" ;

幾乎總是有一個id字段

在每個以id列爲PRIMARY KEY的數據表中,優先選擇AUTO_INCREMENT或者INT。 也可以優選使用UNSIGNED,因爲該值不能爲負的。
即使你擁有一個具有唯一用戶名字段的用戶表,也不要將其作爲主鍵。 VARCHAR字段作爲主鍵(檢索)速度較慢。通過內部ID引用所有的用戶數據,你的代碼中將更加結構化。
有些後臺操作是由MySQL引擎本身完成的,它在內部使用主鍵字段。當數據庫設置越複雜(集羣,分區等…),這就變得更加重要了。
這個規則的一個可能的例外是「關聯表」,用於兩個表之間的多對多類型的關聯。例如,「posts_tags」表中包含兩列:post_id,tag_id,用於保存表名爲「post」和「tags」的兩個表之間的關係。這些表可以具有包含兩個id字段的PRIMARY鍵。

相比 VARCHAR 優先使用 ENUM

ENUM枚舉類型是非常快速和緊湊的。在內部它們像TINYINT一樣存儲,但它們可以包含和顯示字符串值。這使他們成爲某些領域的完美候選。
如果有一個字段只包含幾種不同的值,請使用ENUM而不是VARCHAR。例如,它可以是名爲「status」的列,並且只包含諸如「active」,「inactive」,「pending」,「expired」等的值…
關於如何重構你的數據表,甚至有一種方法是可以從MySQL本身得到「建議」。 當你有一個VARCHAR字段,它實際上建議你將該列類型更改爲ENUM。這通過調用PROCEDURE ANALYZE()來完成。

使用PROCEDURE ANALYSE()獲取建議

PROCEDURE ANALYSE() 將使用MySQL分析列結構和表中的實際數據,爲你提供一些建議。它只有在數據表中有實際數據時纔有用,因爲這在分析決策時很重要。
例如,如果你創建了一個INT類型的主鍵,但沒有太多行,MySQL則可能建議您改用MEDIUMINT。或者如果你使用VARCHAR字段,如果表裏只有很少的取值,你可能會得到一個建議是將其轉換爲ENUM。
你也可以在其中一個表視圖中單擊phpmyadmin中的「建議表結構」鏈接來執行此操作。

PROCEDURE ANALYSE()

請記住,這些只是建議。 如果你的數據表變得越來越大,他們甚至可能不是正確的建議。至於如何修改最終是你來決定。