Top 20+ MySQL Best Practices(20條MySQL最佳實踐)


由於作者翻譯會加入 自己的理解 以便自己學習和使用, 如果英文好的同學可看下面   如文章中有翻譯錯誤還請留言. 交流並改正. (:  原文地址   <== 

 
======================Enein翻譯=========================
 
        今天大多數web應用的數據庫操作往往是最主要的battleneck(瓶頸), 這不僅僅是DBA所要擔心的問題, 我們作為程序員也要做好我們的那部分(結構設計, 性能最好的查詢/最優化的代碼), 下面會為程序員介紹一些MySQL中的優化技術.

    Optimize Your Queries For the Query Cache  

        大多數MySQL服務的query caching(查詢緩存)是開啟的,  它是提高性能的最好辦法之一, 由database engine(數據庫引擎內部處理). 當相同的查詢執行多次,  這個結果將會從 cache 中獲取,這樣是高效的.
        但主要的問題是, 對於程序員來說它是太容易了和是隱式的. 大多數時候我們都會忽略它.  只有我們在執行下面的語句的時候才會真正的關閉 query cache.
    
// 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'");
 query cache 不工作的原因是 這個 第1行使用了 CURDATE() 函數. (同樣適用於 不確定函數  NOW() RAND() etc.)  因為返回的結果都是不同的. 所以MySQL 決定關閉 query cache 功能,  我們要做的就在(在PHP中) 在query 關閉之前做一些事情.

EXPLAIN Your SELECT Queries   

        使用EXPLAIN①關鍵字. 能告訴你MySQL在你語句執行的時候都做了什么. 這能幫助你找到查詢和表結構的瓶頸以及一些問題.
EXPLAIN的結果可以看出這個query使用沒使用indexs(索引)表是如何被搜索和排序 etc.
        寫一個SELECT語句(最好是復雜點, 有join的) 在之前加入EXPLAIN, 你可以在phpmyadmin下看它, 它將會為你展示個友好的表格, 例如:  我們忘記加入一個字段索引, 執行如下:
加入索引之后(之樣是比較好的)
 
現在不是掃描 7883行, 而是只掃描 9 行和 第16行 從兩個表中. 經驗告訴我們 看"rows" 下所有的數字, 就可以得出 查詢性能與查詢的結果數是成正比的結論.

LIMIT 1 When Getting a Unique Row 

        當你查詢你的表時, 你知道你找的是一行. 你可能會得到一個結果, 或者你只是通過WHERE條件查詢記錄的數量. 在這種情況下加入 "LIMIT 1" . 這樣數據庫引擎就會找到1條記錄后就停止,  而不是查找整個表或索引.
// 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) {  
    // ...  
}  

Index the Search Fields 

索引不僅是 "主鍵索引"/"唯一索引", 如果在你的表中有個字段你是要搜索的, 那么你也可以設置它.
上圖中.  設置索引也可以應用到 搜索語句 "last_name LIKE "a%""中.  當搜索這個字段的時候, MySQL就會利用這個字段的索引.
但你也要知道哪些搜索是不有用到索引的:
         "當搜索一個單詞(e.g "WHERE post_content LIKE '%apple%'")"
你可以使用  mysql fulltext search ②或者構建你自己的索引解決方案.

Index and Use Same Column Types for Joins 

        如果你的查詢中存在 “Join”查詢, 那你要確定你join的兩個字段在兩個表中都存在索引. 這直接影響 MySQL內部"Join"優化. 另外這個字段是被"Join"了, 還需要都為相同類型, 例如: 如果你加入了一個 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  
Do Not ORDER BY RAND()
PS: 這個我相信一般都沒有這么干地.
        這個技巧看上去挺有意思, 大多數 新手(菜鳥)都會犯這種錯誤,  你可能還不知道在你的查詢中使用它的后果.
如果你真的需要隨即行, 有很多方法可以做它, 只需要多幾行代碼;這種你可以防止數據線性增長所帶來的瓶頸.  在你對它進行排序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");  
所以不要在你的語句中直接使用MySQL的隨機方法.

Avoid 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  

Almost Always Have an id Field  

        在每個表中都有一個id字段(是主鍵的, 自增長的, int 類型的最好是 UNSIGNED③的因為這個不能為負數), 如果你的一個用戶表只有一個唯一段 "username" 你也不要把它設置成主鍵, VARCHAR類型的主鍵是很慢的. 最好的方法就在加入一個id字段.
        還可以通過MySQL引擎內部進行處理, 使用內部的主鍵(這點很重要) 在很多復雜的數據庫中都可以設置(e.g 集群, 分片etc...)
        但 " association tables"(關聯表)除外, 在兩個表中使用多對多關系 . 例如 "posts_tags" 表中有兩個字段 "post_id" "tag_id"  分別為"posts""tags"的主鍵ID 那么這個表就可以有一個主鍵ID.

Use ENUM over VARCHAR 

        ENUM④類型字段是快速和簡單的. 它們在內部被存儲像"TINYINT" 他們可以顯示為字段串類型.  這使得它們可以代替某些字段.
如果有一個字段僅僅有一些不用的值, 那么請使用 ENUM 代替 VARCHAR 例如 你有個字段 status 只存在  “active” “inactive” "pending" "expired" etc ...
     甚至還有一些優化建議for MySQL來建議你合理化你的表 當你有一個 VARCHAR字段, 它會建議你使用ENUM代替. 你可以使用 PROCEDURE ANALYSE() 詳細看下一節.

Get Suggestions with PROCEDURE ANALYSE() ⑤

        PROCEDURE ANALYSE() 返回一個可優化字段信息 來減輕表大小. 通常在實際中是很有用的.
例如 如果你創建了一個INT類型的主鍵, 但你沒有幾行, 那么可能會建議你使用 MEDIUMINT 來代替. 或 你創建一個VARCHAR字段, 那么它會建議使用ENUM代替, 因為它僅僅是一些唯一因定的值.
        在phpmyamin 中你可以敲擊 "Propose table structure" 
你要有你自己的想法 因為這只是些建議 。 如果你的表增長的很快, 數據很大. 那么這些建議可能就不適合你了. 這些僅僅是建議而已.

Use NOT NULL If You Can  

        除非你有一個特殊需求要將它設置為空, 要么就能不設置為NULL就不設置NULL.
首先, 問一下你自己 知道不知道  空字符串和null有什么區別  (或者是INT 0 VS. NULL) . 如果沒有合理理由, 那你不需要設置為NULL(在Oracle中它會認為是相同的)
        NULL是要一定的空間的, 並且會為你比較語句帶來復雜性, 如果可以盡量避免它們, 無論怎么樣, 一些人會有一些特殊的理由來把它設置為空. 那樣是不好的, MySQL doc文檔上說 
        “NULL columns require additional space in the row to record whether their values are NULL. For MyISAM tables, each NULL column takes one bit extra, rounded up to the nearest byte.”

Prepared Statements

    預聲名公有很多好處, 比如可以提高性能/提高安全性 etc...
預聲名變量 可以過濾一些 變量 為它們設置default值,  也可以很好的防止SQL注入攻擊 你也可以手動過濾這些變量, 但一些方法會出現人為錯誤 , 這就是為什么使用 一些框架來避免一些問題.
        因為我們的焦點是在性能上, 所以我也會說一下這部分的好處, 例如我們要查詢相同的語句很多次, 我們可以賦值給同一個聲明中, 這樣MySQL只會解析一次。
        而且現在最后版本的MySQL在傳輸 prepared statements 改為二進制形式, 這樣即可以提高效率又可以減少網絡延遲.
當有特殊原因時候要使用一個空的 prepared statemtns時候 將不會MySQL query cache cache到,  5.1后 也支持了.
可以看一下PHP中 你可以使用    mysqli extension  也可以 抽像出一個  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();  
}  

Unbuffered Queries 

        通常在腳本中執行一個查詢, 它將會等待查詢完成后你才可以繼續. 你也可以改變這種情況 , 使 "Unbffered Queries" 下面是 PHP doc 的 mysql_unbuffered_query()  介紹   
        “mysql_unbuffered_query() sends the SQL query query to MySQL without automatically fetching and buffering the result rows as mysql_query() does. This saves a considerable amount of memory with SQL queries that produce large result sets, and you can start working on the result set immediately after the first row has been retrieved as you don’t have to wait until the complete SQL query has been performed.”
但它有一些限制, 你還可以在你執行下一個query的時候使用  mysql_free_result()   但不允許你在結果集上使用    mysql_num_rows()  or  mysql_data_seek()
Store IP Addresses as UNSIGNED INT
        一些程序員對IP 地址會設置為 VARCHAR(15) 它們沒有意示到IP地址都是整數. 而這個int只占用4個bytes ,這樣可以修復一下大小.
在你的查詢中可以使用    INET_ATON() 把IP變為整形 , 也可以使用    INET_NTOA() 變量回來 .
        這兒可以使用PHP中的    ip2long()  and  long2ip() .
$r = "UPDATE users SET ip = INET_ATON('{$_SERVER['REMOTE_ADDR']}') WHERE user_id = $user_id";  

Fixed-length(Static) Tables are Faster 

        當表中的每一列都是 固定長度, 那么這個表就稱做    “static” or “fixed-length” . "VARCHAR, TEXT, BLOB" 這些不是固定長度, 如果你的列中包含其中之一, 這個表就不是 固定長度的表, 那MySQL引擎的處理方式也會不同.
        Fixed-length 的表可以提高性能, 因為MySQL引擎查找記錄是很快的, 當它想找一個特殊的行, 它可以很快定位到它, 如果行大小不固定, 每次都需要做一次尋址 還要去查找主鍵索引.
        他也是很容易被 cache / 拆開后重建. 但他們會帶來更多的空間. 例如: 你有 VARCHAR(20)/CHAR(20), 這個空間會一直存在, 不管里有沒有值.

Vertical Partitioning 

        垂直分區是以垂直方式來拆分你的表結構, 是一種優化的方式. 
    Example 1:  你可能會有一個用戶表, 有一個家庭住址字段, 不被經常讀的, 你可能選擇split你的表 home address 放到一個特殊的表里. 這樣你的主表將會縮減大小(表越小執行效率越快).
    Example 2:  在你的表中的一個"last_login"字段, 每次用戶登錄都會更新這個字段該表在 query cache上的數據將會被 清空 , 你可以使這個字段分到另一個表中保持更新次數降到最低.
    注意分區后的表 你要確定保不會經常插入數據, 那樣的話性能也會降低.

Split the Big DELETE or INSERT Queries 

        如果你要執行一個大的 刪除 和 插入 查詢在一個在線網站上,  你需要注意這個連接速度, 當這個query被執行, 頁面會鎖定, 這樣會帶來不好的影響.
Apache 會提供一些並行的線程、 因此它工作是高效的 很快會執行完成腳本, 所以服務沒有太多的開/閉在一次中. 那樣會消耗資源 特別是內存.
        你也可以使用LIMIT來做它:

Smaller Columns Are Faster 

        在數據庫引擎中, 磁盤是最大的瓶頸. 所以在保持最小,最簡單可能幫助我們提高性能. 減少磁盤開銷.
MySQL 文檔中有一個列表     Storage Requirements   有所有類型.
如果你的表很少的行並且主鍵還是 INT 你可以替換 MEDIUMINT SAMILINT 甚至你也可以使用TININT. 如果你不需要時間組件, 你也可以使用DATA替換 DATETIME.
        重點在於你如何合理化你的表結構. 你可能會喜歡 Slashdot .

Choose the Right Storage Engine

        MySQL中有兩個存儲引擎"MyISAM"/"InnoDB" 它們都有各自的優點和缺點.

         MyISAM 對讀取操作是很好的.  它的伸縮性不是很好當有很多寫候, 甚至你更新一行的一個字段, 這個表是被鎖定的, 其它線程也不能讀直到查詢完畢. MyISAM 在計算 SELECT COUNT(*) 是很快的.
         InnoDB 趨向於一些復雜的存儲引擎在大部分小應用里是比MyISAM慢的.  但它支持基於行的鎖定, 它還支持些更高級的功能 比如事務.
  1. MyISAM Storage Engine
  2. InnoDB Storage Engine

Use an Object Relational Mapper 

/*忽略*/

附錄

When you precede a SELECT statement with the keyword EXPLAIN, MySQL displays information from the optimizer about the query execution plan. That is, MySQL explains how it would process the statement, including information about how tables are joined and in which order”(當你在select之前加上EXPLAIN, MySQ 從優化器中顯示一些執行信息, 即, 它將如何處理語句, 表加入順序等信息.) --- MySQL
② " Full-text indexes can be used only with MyISAM tables" (但  MySQL 5.1不支持ISAM) ---  MySQL
③  unsigned      既為非負數,用此類型可以增加數據長度!
④  An ENUM is a string object with a value chosen from a list of permitted values that are enumerated explicitly in the column specification at table creation time.(是一個string 類型的 在表創建的時候一個可選值的集合)”-- MySQL
⑤ " ANALYSE() examines the result from a query and returns an analysis of the results that suggests optimal data types for each column that may help reduce table sizes.(為查詢語句返回一個可優化字段信息 來減輕表大小)" --- MySQL
======================Enein翻譯=========================
由於作者翻譯會加入 自己的理解 以便自己學習和使用. 如有轉載請注明出處謝謝.  如文章中有翻譯錯誤或有更好的方案還請留言. 交流並改正. (:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM