1.查詢緩存優化你的查詢php
大多數的MySQL服務器都開啓了查詢緩存。這是提升性最有效的方法之一,並且這是被MySQL的數據庫引擎處理的。當有不少相同的查詢被執行了屢次的時 候,這些查詢結果會被放到一個緩存中,這樣,後續的相同的查詢就不用操做表而直接訪問緩存結果了。這裏最主要的問題是,對於程序員來講,這個事情是很容易 被忽略的。由於,咱們某些查詢語句會讓MySQL不使用緩存。請看下面的示例:
// 查詢緩存不開啓
$r = mysql_query("SELECT username FROM user WHERE signup_date >= CURDATE()");mysql
// 開啓查詢緩存
$today = date("Y-m-d");
$r = mysql_query("SELECT username FROM user WHERE signup_date >= '$today'");程序員
上面兩條SQL語句的差異就是 CURDATE() ,MySQL的查詢緩存對這個函數不起做用。因此,像 NOW() 和 RAND() 或是其它的諸如此類的SQL函數都不會開啓查詢緩存,由於這些函數的返回是會不定的易變的。因此,你所須要的就是用一個變量來代替MySQL的函數,從而 開啓緩存。
咱們能夠看到,前一個結果顯示搜索了 7883 行,然後一個只是搜索了兩個表的 9 和 16 行。查看rows列可讓咱們找到潛在的性能問題。sql
// 沒有效率的:
$r = mysql_query("SELECT * FROM user WHERE country = 'China'");
if (mysql_num_rows($r) > 0) {
// ...
}數據庫
// 有效率的:
$r = mysql_query("SELECT 1 FROM user WHERE country = 'China' LIMIT 1");
if (mysql_num_rows($r) > 0) {
// ...
}緩存
從上圖你能夠看到那個搜索字串 「last_name LIKE ‘a%’」,一個是建了索引,一個是沒有索引,性能差了4倍左右。另外,你應該也須要知道什麼樣的搜索是不能使用正常的索引的。例如,當你須要在一篇大的 文章中搜索一個詞時,如: 「WHERE post_content LIKE ‘%apple%’」,索引多是沒有意義的。你可能須要使用MySQL全文索引 或是本身作一個索引(好比說:搜索關鍵詞或是Tag什麼的)
// 在state中查找company
$r = mysql_query("SELECT company_name FROM users
LEFT JOIN companies ON (users.state = companies.state)
WHERE users.id = $user_id");安全
// 兩個state 字段應該是被建過索引的,並且應該是至關的類型,相同的字符集。服務器
下面的示例是隨機挑一條記錄
// 千萬不要這樣作:
$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");數據結構
// 不推薦
$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']}";
A. 儘量的使用 NOT NULL
除非你有一個很特別的緣由去使用 NULL 值,你應該老是讓你的字段保持 NOT NULL。這看起來好像有點爭議,請往下看。首先,問問你本身「Empty」和「NULL」有多大的區別(若是是INT,那就是0和NULL)?若是你覺 得它們之間沒有什麼區別,那麼你就不要使用NULL。(你知道嗎?在 Oracle 裏,NULL 和 Empty 的字符串是同樣的!) 不要覺得 NULL 不須要空間,其須要額外的空間,而且,在你進行比較的時候,你的程序會更復雜。 固然,這裏並非說你就不能使用NULL了,現實狀況是很複雜的,依然會有些狀況下,你須要使用NULL值。下面摘自MySQL本身的文檔:
「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.」
B. Prepared Statements
Prepared Statements很像存儲過程,是一種運行在後臺的SQL語句集合,咱們能夠從使用 prepared statements 得到不少好處,不管是性能問題仍是安全問題。Prepared Statements 能夠檢查一些你綁定好的變量,這樣能夠保護你的程序不會受到「SQL注入式」攻擊。固然,你也能夠手動地檢查你的這些變量,然而,手動的檢查容易出問題, 並且很常常會被程序員忘了。當咱們使用一些framework或是ORM的時候,這樣的問題會好一些。在性能方面,當一個相同的查詢被使用屢次的時候,這 會爲你帶來可觀的性能優點。你能夠給這些Prepared Statements定義一些參數,而MySQL只會解析一次。雖然最新版本的MySQL在傳輸Prepared Statements是使用二進制形勢,因此這會使得網絡傳輸很是有效率。固然,也有一些狀況下,咱們須要避免使用Prepared Statements,由於其不支持查詢緩存。但聽說版本5.1後支持了。在PHP中要使用prepared statements,你能夠查看其使用手冊:mysqli 擴展 或是使用數據庫抽象層,如: PDO.
// 建立 prepared statement
if ($stmt = $mysqli->prepare("SELECT username FROM user WHERE state=?")) {
// 綁定參數 $stmt->bind_param("s", $state); // 執行 $stmt->execute(); // 綁定結果 $stmt->bind_result($username); // 移動遊標 $stmt->fetch(); printf("%s is from %s\n", $username, $state); $stmt->close();
}
C. 無緩衝的查詢
正常的狀況下,當你在當你在你的腳本中執行一個SQL語句的時候,你的程序會停在那裏直到沒這個SQL語句返回,而後你的程序再往下繼續執行。你可使用無 緩衝查詢來改變這個行爲。關於這個事情,在PHP的文檔中有一個很是不錯的說明: 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.」
上 面那句話翻譯過來是說,mysql_unbuffered_query() 發送一個SQL語句到MySQL而並不像mysql_query()同樣去自動fethch和緩存結果。這會至關節約不少可觀的內存,尤爲是那些會產生大 量結果的查詢語句,而且,你不須要等到全部的結果都返回,只須要第一行數據返回的時候,你就能夠開始立刻開始工做於查詢結果了。然而,這會有一些限制。因 爲你要麼把全部行都讀走,或是你要在進行下一次的查詢前調用mysql_free_result() 清除結果。並且, mysql_num_rows() 或 mysql_data_seek() 將沒法使用。因此,是否使用無緩衝的查詢你須要仔細考慮。
D. 把IP地址存成 UNSIGNED INT
不少程序員都會建立一個 VARCHAR(15) 字段來存放字符串形式的IP而不是整形的IP。若是你用整形來存放,只須要4個字節,而且你能夠有定長的字段。並且,這會爲你帶來查詢上的優點,尤爲是當 你須要使用這樣的WHERE條件:IP between ip1 and ip2。咱們必須要使用UNSIGNED INT,由於 IP地址會使用整個32位的無符號整形而你的查詢,你可使用 INET_ATON() 來把一個字符串IP轉成一個整形,並使用 INET_NTOA() 把一個整形轉成一個字符串IP。在PHP中,也有這樣的函數 ip2long() 和 long2ip()。
$r = "UPDATE users SET ip = INET_ATON('{$_SERVER['REMOTE_ADDR']}') WHERE user_id = $user_id";
E. 固定長度的表會更快
若是表中的全部字段都是「固定長度」的,整個表會被認爲是 「static」 或 「fixed-length」。 例如,表中沒有以下類型的字段: VARCHAR,TEXT,BLOB。只要你包括了其中一個這些字段,那麼這個表就不是「固定長度靜態表」了,這樣,MySQL 引擎會用另外一種方法來處理。固定長度的表會提升性能,由於MySQL搜尋得會更快一些,由於這些固定的長度是很容易計算下一個數據的偏移量的,因此讀取的 天然也會很快。而若是字段不是定長的,那麼,每一次要找下一條的話,須要程序找到主鍵。而且,固定長度的表也更容易被緩存和重建。不過,惟一的反作用是, 固定長度的字段會浪費一些空間,由於定長的字段不管你用不用,他都是要分配那麼多的空間。使用「垂直分割」技術(見下一條),你能夠分割你的表成爲兩個一 個是定長的,一個則是不定長的。
F. 垂直分割
「垂直分割」是一種把數據庫中的表按列變成幾張表的方法,這樣能夠下降表的複雜度和字段的數目,從而達到優化的目的。(之前,在銀行作過項目,見過一張表有100多個字段,很恐怖)
示 例一:在Users表中有一個字段是家庭地址,這個字段是可選字段,相比起,並且你在數據庫操做的時候除了我的信息外,你並不須要常常讀取或是改寫這個字 段。那麼,爲何不把他放到另一張表中呢? 這樣會讓你的表有更好的性能,你們想一想是否是,大量的時候,我對於用戶表來講,只有用戶ID,用戶名,口令,用戶角色等會被常用。小一點的表老是會有 好的性能。 示 例二: 你有一個叫 「last_login」 的字段,它會在每次用戶登陸時被更新。可是,每次更新時會致使該表的查詢緩存被清空。因此,你能夠把這個字段放到另外一個表中,這樣就不會影響你對用戶 ID,用戶名,用戶角色的不停地讀取了,由於查詢緩存會幫你增長不少性能。另外,你須要注意的是,這些被分出去的字段所造成的表,你不會常常性地去 Join他們,否則的話,這樣的性能會比不分割時還要差,並且,會是極數級的降低。
while (1) {
//每次只作1000條
mysql_query("DELETE FROM logs WHERE log_date <= '2009-11-01' LIMIT 1000");
if (mysql_affected_rows() == 0) {
// 沒得可刪了,退出! break;
}
// 每次都要休息一下子
usleep(50000);
}
·target=」_blank」MyISAM Storage Engine
·InnoDB Storage Engine
been there down that