PHP軟件開發中如何提高項目的運行效率
如何提高PHP程序執行效率?編程習慣分析,PHP程序原本是解釋性程序語言,其執行速度本來就不高,如果在日常開發中不註意編程習慣就很難提高其執行效率,下文從PHP編程和mysql數據效率兩個方面分析哪些提高其執行效率的要點PHP軟件開發中如何提高項目的運行效率
壹、PHP提高效率的要點
1、如果能將類的方法定義成static,就盡量定義成static,它的速度會提升將近4倍。
2、$row[’id’] 的速度是$row[id]的7倍。
3、echo 比 print 快,並且使用echo的多重參數(譯註:指用逗號而不是句點)代替字符串連接,比如echo $str1,$str2。
4、在執行for循環之前確定最大循環數,不要每循環壹次都計算最大值,最好運用foreach代替。
5、註銷那些不用的變量尤其是大數組,以便釋放內存。
6、盡量避免使用__get,__set,__autoload。
7、require_once()代價昂貴。
8、include文件時盡量使用絕對路徑,因為它避免了PHP去include_path裏查找文件的速度,解析操作系統路徑所需的時間會更少。
9、如果妳想知道腳本開始執行(譯註:即服務器端收到客戶端請求)的時刻,使用$_SERVER[‘REQUEST_TIME’]要好於time()。
10、函數代替正則表達式完成相同功能。
11、str_replace函數比preg_replace函數快,但strtr函數的效率是str_replace函數的四倍。
12、如果壹個字符串替換函數,可接受數組或字符作為參數,並且參數長度不太長,那麽可以考慮額外寫壹段替換代碼,使得每次傳遞參數是壹個字符,而不是只寫壹行代碼接受數組作為查詢和替換的參數。
13、使用選擇分支語句(譯註:即switch case)好於使用多個if,else if語句。
14、用@屏蔽錯誤消息的做法非常低效,極其低效。
15、打開apache的mod_deflate模塊,可以提高網頁的瀏覽速度。
16、數據庫連接當使用完畢時應關掉,不要用長連接。
17、錯誤消息代價昂貴。
18、在方法中遞增局部變量,速度是最快的。幾乎與在函數中調用局部變量的速度相當。
19、遞增壹個全局變量要比遞增壹個局部變量慢2倍。
20、遞增壹個對象屬性(如:$this->prop++)要比遞增壹個局部變量慢3倍。
二、MYSQL數據提高執行效率的要點
1. 為查詢緩存優化妳的查詢?
大多數的MySQL服務器都開啟了查詢緩存。這是提高性最有效的方法之壹,而且這是被MySQL的數據庫引擎處理的。當有很多相同的查詢被執行了多次的時候,這些查詢結果會被放到壹個緩存中,這樣,後續的相同的查詢就不用操作表而直接訪問緩存結果了。?
這裏最主要的問題是,對於程序員來說,這個事情是很容易被忽略的。因為,我們某些查詢語句會讓MySQL不使用緩存。請看下面的示例:?
// 查詢緩存不開啟 $r = mysql_query("SELECT username FROM user WHERE signup_date >= CURDATE()");
// 開啟查詢緩存 $today = date("Y-m-d");
$r = mysql_query("SELECT username FROM user WHERE signup_date >= '$today'");
上面兩條SQL語句的差別就是 CURDATE() ,MySQL的查詢緩存對這個函數不起作用。所以,像 NOW() 和 RAND() 或是其它的諸如此類的SQL函數都不會開啟查詢緩存,因為這些函數的返回是會不定的易變的。所以,妳所需要的就是用壹個變量來代替MySQL的函數,從而開啟緩存。?
2. EXPLAIN 妳的 SELECT 查詢?
使用 EXPLAIN 關鍵字可以讓妳知道MySQL是如何處理妳的SQL語句的。這可以幫妳分析妳的查詢語句或是表結構的性能瓶頸。?
EXPLAIN 的查詢結果還會告訴妳妳的索引主鍵被如何利用的,妳的數據表是如何被搜索和排序的……等等,等等。?
挑壹個妳的SELECT語句(推薦挑選那個最復雜的,有多表聯接的),把關鍵字EXPLAIN加到前面。妳可以使用phpmyadmin來做這個事。然後,妳會看到壹張表格。下面的這個示例中,我們忘記加上了group_id索引,並且有表聯接:
當我們為 group_id 字段加上索引後:
3. 當只要壹行數據時使用 LIMIT 1?
當妳查詢表的有些時候,妳已經知道結果只會有壹條結果,但因為妳可能需要去fetch遊標,或是妳也許會去檢查返回的記錄數。?
在這種情況下,加上 LIMIT 1 可以增加性能。這樣壹樣,MySQL數據庫引擎會在找到壹條數據後停止搜索,而不是繼續往後查少下壹條符合記錄的數據。?
下面的示例,只是為了找壹下是否有“中國”的用戶,很明顯,後面的會比前面的更有效率。(請註意,第壹條中是Select *,第二條是Select 1)?
我們可以看到,前壹個結果顯示搜索了 7883 行,而後壹個只是搜索了兩個表的 9 和 16 行。查看rows列可以讓我們找到潛在的性能問題。?
// 沒有效率的: $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) {
// ... }
4. 為搜索字段建索引?
索引並不壹定就是給主鍵或是唯壹的字段。如果在妳的表中,有某個字段妳總要會經常用來做搜索,那麽,請為其建立索引吧。
從上圖妳可以看到那個搜索字串 “last_name LIKE ‘a%'”,壹個是建了索引,壹個是沒有索引,性能差了4倍左右。?
另外,妳應該也需要知道什麽樣的搜索是不能使用正常的索引的。例如,當妳需要在壹篇大的文章中搜索壹個詞時,如: “WHERE post_content LIKE ‘%apple%'”,索引可能是沒有意義的。妳可能需要使用MySQL全文索引 或是自己做壹個索引(比如說:搜索關鍵詞或是Tag什麽的)?
5. 在Join表的時候使用相當類型的例,並將其索引?
如果妳的應用程序有很多 JOIN 查詢,妳應該確認兩個表中Join的字段是被建過索引的。這樣,MySQL內部會啟動為妳優化Join的SQL語句的機制。?
而且,這些被用來Join的字段,應該是相同的類型的。例如:如果妳要把 DECIMAL 字段和壹個 INT 字段Join在壹起,MySQL就無法使用它們的索引。對於那些STRING類型,還需要有相同的字符集才行。(兩個表的字符集有可能不壹樣)?
// 在state中查找company $r = mysql_query("SELECT company_name FROM users
LEFT JOIN companies ON (users.state = companies.state)
WHERE users.id = $user_id");
// 兩個 state 字段應該是被建過索引的,而且應該是相當的類型,相同的字符集。
6. 千萬不要 ORDER BY RAND()?
想打亂返回的數據行?隨機挑壹個數據?真不知道誰發明了這種用法,但很多新手很喜歡這樣用。但妳確不了解這樣做有多麽可怕的性能問題。?
如果妳真的想把返回的數據行打亂了,妳有N種方法可以達到這個目的。這樣使用只讓妳的數據庫的性能呈指數級的下降。這裏的問題是:MySQL會不得不去執行RAND()函數(很耗CPU時間),而且這是為了每壹行記錄去記行,然後再對其排序。就算是妳用了Limit 1也無濟於事(因為要排序)?
下面的示例是隨機挑壹條記錄?
// 千萬不要這樣做: $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");
7. 避免 SELECT *?
從數據庫裏讀出越多的數據,那麽查詢就會變得越慢。並且,如果妳的數據庫服務器和WEB服務器是兩臺獨立的服務器的話,這還會增加網絡傳輸的負載。?
所以,妳應該養成壹個需要什麽就取什麽的好的習慣。?
// 不推薦 $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']}";
8. 永遠為每張表設置壹個ID?
我們應該為數據庫裏的每張表都設置壹個ID做為其主鍵,而且最好的是壹個INT型的(推薦使用UNSIGNED),並設置上自動增加的 AUTO_INCREMENT標誌。?
就算是妳 users 表有壹個主鍵叫 “email”的字段,妳也別讓它成為主鍵。使用 VARCHAR 類型來當主鍵會使用得性能下降。另外,在妳的程序中,妳應該使用表的ID來構造妳的數據結構。?
而且,在MySQL數據引擎下,還有壹些操作需要使用主鍵,在這些情況下,主鍵的性能和設置變得非常重要,比如,集群,分區……?
在這裏,只有壹個情況是例外,那就是“關聯表”的“外鍵”,也就是說,這個表的主鍵,通過若幹個別的表的主鍵構成。我們把這個情況叫做“外鍵”。比如:有壹個“學生表”有學生的ID,有壹個“課程表”有課程ID,那麽,“成績表”就是“關聯表”了,其關聯了學生表和課程表,在成績表中,學生ID和課程ID叫“外鍵”其***同組成主鍵。?
9. 使用 ENUM 而不是 VARCHAR?
ENUM 類型是非常快和緊湊的。在實際上,其保存的是 TINYINT,但其外表上顯示為字符串。這樣壹來,用這個字段來做壹些選項列表變得相當的完美。?
如果妳有壹個字段,比如“性別”,“國家”,“民族”,“狀態”或“部門”,妳知道這些字段的取值是有限而且固定的,那麽,妳應該使用 ENUM 而不是 VARCHAR。?
MySQL也有壹個“建議”(見第十條)告訴妳怎麽去重新組織妳的表結構。當妳有壹個 VARCHAR 字段時,這個建議會告訴妳把其改成 ENUM 類型。使用 PROCEDURE ANALYSE() 妳可以得到相關的建議。?
10. 從 PROCEDURE ANALYSE() 取得建議?
PROCEDURE ANALYSE() 會讓 MySQL 幫妳去分析妳的字段和其實際的數據,並會給妳壹些有用的建議。只有表中有實際的數據,這些建議才會變得有用,因為要做壹些大的決定是需要有數據作為基礎的。?
例如,如果妳創建了壹個 INT 字段作為妳的主鍵,然而並沒有太多的數據,那麽,PROCEDURE ANALYSE()會建議妳把這個字段的類型改成 MEDIUMINT 。或是妳使用了壹個 VARCHAR 字段,因為數據不多,妳可能會得到壹個讓妳把它改成 ENUM 的建議。這些建議,都是可能因為數據不夠多,所以決策做得就不夠準。?
在phpmyadmin裏,妳可以在查看表時,點擊 “Propose table structure” 來查看這些建議
壹定要註意,這些只是建議,只有當妳的表裏的數據越來越多時,這些建議才會變得準確。壹定要記住,妳才是最終做決定的人。?
11. 盡可能的使用 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.”?
12. 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
", $username, $state);
$stmt->close();
}
13. 無緩沖的查詢?
正常的情況下,當妳在當妳在妳的腳本中執行壹個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() 將無法使用。所以,是否使用無緩沖的查詢妳需要仔細考慮。?
14. 把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()。?
1 $r = "UPDATE users SET ip = INET_ATON('{$_SERVER['REMOTE_ADDR']}') WHERE user_id = $user_id";?
15. 固定長度的表會更快?
如果表中的所有字段都是“固定長度”的,整個表會被認為是 “static” 或 “fixed-length”。 例如,表中沒有如下類型的字段: VARCHAR,TEXT,BLOB。只要妳包括了其中壹個這些字段,那麽這個表就不是“固定長度靜態表”了,這樣,MySQL 引擎會用另壹種方法來處理。?
固定長度的表會提高性能,因為MySQL搜尋得會更快壹些,因為這些固定的長度是很容易計算下壹個數據的偏移量的,所以讀取的自然也會很快。而如果字段不是定長的,那麽,每壹次要找下壹條的話,需要程序找到主鍵。?
並且,固定長度的表也更容易被緩存和重建。不過,唯壹的副作用是,固定長度的字段會浪費壹些空間,因為定長的字段無論妳用不用,他都是要分配那麽多的空間。?
使用“垂直分割”技術(見下壹條),妳可以分割妳的表成為兩個壹個是定長的,壹個則是不定長的。?
16. 垂直分割?
“垂直分割”是壹種把數據庫中的表按列變成幾張表的方法,這樣可以降低表的復雜度和字段的數目,從而達到優化的目的。(以前,在銀行做過項目,見過壹張表有100多個字段,很恐怖)?
示例壹:在Users表中有壹個字段是家庭地址,這個字段是可選字段,相比起,而且妳在數據庫操作的時候除了個人信息外,妳並不需要經常讀取或是改寫這個字段。那麽,為什麽不把他放到另外壹張表中呢? 這樣會讓妳的表有更好的性能,大家想想是不是,大量的時候,我對於用戶表來說,只有用戶ID,用戶名,口令,用戶角色等會被經常使用。小壹點的表總是會有好的性能。?
示例二: 妳有壹個叫 “last_login” 的字段,它會在每次用戶登錄時被更新。但是,每次更新時會導致該表的查詢緩存被清空。所以,妳可以把這個字段放到另壹個表中,這樣就不會影響妳對用戶 ID,用戶名,用戶角色的不停地讀取了,因為查詢緩存會幫妳增加很多性能。?
另外,妳需要註意的是,這些被分出去的字段所形成的表,妳不會經常性地去Join他們,不然的話,這樣的性能會比不分割時還要差,而且,會是極數級的下降。?
17. 拆分大的 DELETE 或 INSERT 語句?
如果妳需要在壹個在線的網站上去執行壹個大的 DELETE 或 INSERT 查詢,妳需要非常小心,要避免妳的操作讓妳的整個網站停止相應。因為這兩個操作是會鎖表的,表壹鎖住了,別的操作都進不來了。?
Apache 會有很多的子進程或線程。所以,其工作起來相當有效率,而我們的服務器也不希望有太多的子進程,線程和數據庫鏈接,這是極大的占服務器資源的事情,尤其是內存。?
如果妳把妳的表鎖上壹段時間,比如30秒鐘,那麽對於壹個有很高訪問量的站點來說,這30秒所積累的訪問進程/線程,數據庫鏈接,打開的文件數,可能不僅僅會讓妳泊WEB服務Crash,還可能會讓妳的整臺服務器馬上掛了。?
所以,如果妳有壹個大的處理,妳定妳壹定把其拆分,使用 LIMIT 條件是壹個好的方法。下面是壹個示例:
while (1) {
//每次只做1000條 mysql_query("DELETE FROM logs WHERE log_date <= '2009-11-01' LIMIT 1000");
if (mysql_affected_rows() == 0) {
// 沒得可刪了,退出! break;
}
// 每次都要休息壹會兒 usleep(50000);
}
18. 越小的列會越快?
對於大多數的數據庫引擎來說,硬盤操作可能是最重大的瓶頸。所以,把妳的數據變得緊湊會對這種情況非常有幫助,因為這減少了對硬盤的訪問。?
參看 MySQL 的文檔 Storage Requirements 查看所有的數據類型。?
如果壹個表只會有幾列罷了(比如說字典表,配置表),那麽,我們就沒有理由使用 INT 來做主鍵,使用 MEDIUMINT, SMALLINT 或是更小的 TINYINT 會更經濟壹些。如果妳不需要記錄時間,使用 DATE 要比 DATETIME 好得多。?
當然,妳也需要留夠足夠的擴展空間,不然,妳日後來幹這個事,妳會死的很難看,參看Slashdot的例子(2009年11月06 日),壹個簡單的ALTER TABLE語句花了3個多小時,因為裏面有壹千六百萬條數據。?
19. 選擇正確的存儲引擎?
在 MySQL 中有兩個存儲引擎 MyISAM 和 InnoDB,每個引擎都有利有弊。酷殼以前文章《MySQL: InnoDB 還是 MyISAM?》討論和這個事情。?
MyISAM 適合於壹些需要大量查詢的應用,但其對於有大量寫操作並不是很好。甚至妳只是需要update壹個字段,整個表都會被鎖起來,而別的進程,就算是讀進程都無法操作直到讀操作完成。另外,MyISAM 對於 SELECT COUNT(*) 這類的計算是超快無比的。?
InnoDB 的趨勢會是壹個非常復雜的存儲引擎,對於壹些小的應用,它會比 MyISAM 還慢。他是它支持“行鎖” ,於是在寫操作比較多的時候,會更優秀。並且,他還支持更多的高級應用,比如:事務。?
下面是MySQL的手冊?
* target=”_blank”MyISAM Storage Engine?
* InnoDB Storage Engine?
20. 使用壹個對象關系映射器(Object Relational Mapper)?
使用 ORM (Object Relational Mapper),妳能夠獲得可靠的性能增漲。壹個ORM可以做的所有事情,也能被手動的編寫出來。但是,這需要壹個高級專家。?
ORM 的最重要的是“Lazy Loading”,也就是說,只有在需要的去取值的時候才會去真正的去做。但妳也需要小心這種機制的副作用,因為這很有可能會因為要去創建很多很多小的查詢反而會降低性能。?
ORM 還可以把妳的SQL語句打包成壹個事務,這會比單獨執行他們快得多得多。?