今天,數據庫的操做愈來愈成爲整個應用的性能瓶頸了,這點對於Web應用尤爲明顯。關於數據庫的性能,這並不僅是DBA才須要擔憂的事,而這更是咱們程序員須要去關注的事情。當咱們去設計數據庫表結構,對操做數據庫時(尤爲是查表時的SQL語句),咱們都須要注意數據操做的性能。這裏,咱們不會講過多的SQL語句的優化,而只是針對MySQL這一Web應用最多的數據庫。但願下面的這些優化技巧對你有用。php
1.爲查詢緩存優化你的查詢mysql
大多數的MySQL服務器都開啓了查詢緩存。這是提升性最有效的方法之一,並且這是被MySQL的數據庫引擎處理的。當有不少相同的查詢被執行了屢次的時候,這些查詢結果會被放到一個緩存中,這樣,後續的相同的查詢就不用操做表而直接訪問緩存結果了。程序員
這裏最主要的問題是,對於程序員來講,這個事情是很容易被忽略的。由於,咱們某些查詢語句會讓MySQL不使用緩存。請看下面的示例:算法
上面兩條SQL語句的差異就是CURDATE(),MySQL的查詢緩存對這個函數不起做用。因此,像NOW()和RAND()或是其它的諸如此類的SQL函數都不會開啓查詢緩存,由於這些函數的返回是會不定的易變的。因此,你所須要的就是用一個變量來代替MySQL的函數,從而開啓緩存。sql
2.EXPLAIN你的SELECT查詢數據庫
使用EXPLAIN關鍵字可讓你知道MySQL是如何處理你的SQL語句的。這能夠幫你分析你的查詢語句或是表結構的性能瓶頸。緩存
EXPLAIN的查詢結果還會告訴你你的索引主鍵被如何利用的,你的數據表是如何被搜索和排序的……等等,等等。安全
挑一個你的SELECT語句(推薦挑選那個最複雜的,有多表聯接的),把關鍵字EXPLAIN加到前面。你可使用phpmyadmin來作這個事。而後,你會看到一張表格。下面的這個示例中,咱們忘記加上了group_id索引,而且有表聯接:ww.phperz.com服務器
當咱們爲group_id字段加上索引後:網絡
咱們能夠看到,前一個結果顯示搜索了7883行,然後一個只是搜索了兩個表的9和16行。查看rows列可讓咱們找到潛在的性能問題。
3.當只要一行數據時使用LIMIT1
當你查詢表的有些時候,你已經知道結果只會有一條結果,但由於你可能須要去fetch遊標,或是你也許會去檢查返回的記錄數。
在這種狀況下,加上LIMIT 1能夠增長性能。這樣同樣,MySQL數據庫引擎會在找到一條數據後中止搜索,而不是繼續日後查少下一條符合記錄的數據。
下面的示例,只是爲了找一下是否有「中國」的用戶,很明顯,後面的會比前面的更有效率。(請注意,第一條中是Select *,第二條是Select 1)
php程序員站
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類型,還須要有相同的字符集才行。(兩個表的字符集有可能不同)程序員站
6.千萬不要ORDER BY RAND()
想打亂返回的數據行?隨機挑一個數據?真不知道誰發明了這種用法,但不少新手很喜歡這樣用。但你確不瞭解這樣作有多麼可怕的性能問題。
若是你真的想把返回的數據行打亂了,你有N種方法能夠達到這個目的。這樣使用只讓你的數據庫的性能呈指數級的降低。這裏的問題是:MySQL會不得不去執行RAND()函數(很耗CPU時間),並且這是爲了每一行記錄去記行,而後再對其排序。就算是你用了Limit 1也無濟於事(由於要排序)
下面的示例是隨機挑一條記錄
7.避免 SELECT *
從數據庫裏讀出越多的數據,那麼查詢就會變得越慢。而且,若是你的數據庫服務器和WEB服務器是兩臺獨立的服務器的話,這還會增長網絡傳輸的負載。
因此,你應該養成一個須要什麼就取什麼的好的習慣。
8.永遠爲每張表設置一個ID
咱們應該爲數據庫裏的每張表都設置一個ID作爲其主鍵,並且最好的是一個INT型的(推薦使用UNSIGNED),並設置上自動增長的AUTO_INCREMENT標誌。
就算是你users表有一個主鍵叫「email」的字段,你也別讓它成爲主鍵。使用VARCHAR類型來當主鍵會使用得性能降低。另外,在你的程序中,你應該使用表的ID來構造你的數據結構。
並且,在MySQL數據引擎下,還有一些操做須要使用主鍵,在這些狀況下,主鍵的性能和設置變得很是重要,好比,集羣,分區……
在這裏,只有一個狀況是例外,那就是「關聯表」的「外鍵」,也就是說,這個表的主鍵,經過若干個別的表的主鍵構成。咱們把這個狀況叫作「外鍵」。好比:有一個「學生表」有學生的ID,有一個「課程表」有課程ID,那麼,「成績表」就是「關聯表」了,其關聯了學生表和課程表,在成績表中,學生ID和課程ID叫「外鍵」其共同組成主鍵。www~phperz~com
9.使用ENUM而不是VARCHAR
ENUM類型是很是快和緊湊的。在實際上,其保存的是TINYINT,但其外表上顯示爲字符串。這樣一來,用這個字段來作一些選項列表變得至關的完美。
若是你有一個字段,好比「性別」,「國家」,「民族」,「狀態」或「部門」,你知道這些字段的取值是有限並且固定的,那麼,你應該使用ENUM而不是VARCHAR。
MySQL也有一個「建議」(見第十條)告訴你怎麼去從新組織你的表結構。當你有一個VARCHAR字段時,這個建議會告訴你把其改爲ENUM類型。使用PROCEDURE ANALYSE() 你能夠獲得相關的建議。
10.從PROCEDURE ANALYSE()取得建議p程序員站
PROCEDURE ANALYSE() 會讓MySQL幫你去分析你的字段和其實際的數據,並會給你一些有用的建議。只有表中有實際的數據,這些建議纔會變得有用,由於要作一些大的決定是須要有數據做爲基礎的。
例如,若是你建立了一個INT字段做爲你的主鍵,然而並無太多的數據,那麼,PROCEDURE ANALYSE()會建議你把這個字段的類型改爲MEDIUMINT。或是你使用了一個VARCHAR字段,由於數據很少,你可能會獲得一個讓你把它改爲ENUM的建議。這些建議,都是可能由於數據不夠多,因此決策作得就不夠準。
在phpmyadmin裏,你能夠在查看錶時,點擊「Propose table structure」來查看這些建議
必定要注意,這些只是建議,只有當你的表裏的數據愈來愈多時,這些建議纔會變得準確。必定要記住,你纔是最終作決定的人。
11.儘量的使用NOT NULL php程序員站
除非你有一個很特別的緣由去使用NULL值,你應該老是讓你的字段保持NOT NULL。這看起來好像有點爭議,請往下看。
首先,問問你本身「Empty」和「NULL」有多大的區別(若是是INT,那就是0和NULL)?若是你以爲它們之間沒有什麼區別,那麼你就不要使用NULL。(你知道嗎?在Oracle裏,NULL 和 Empty的字符串是同樣的!)
不要覺得 NULL 不須要空間,其須要額外的空間,而且,在你進行比較的時候,你的程序會更復雜。固然,這裏並非說你就不能使用NULL了,現實狀況是很複雜的,依然會有些狀況下,你須要使用NULL值。
下面摘自MySQL本身的文檔:
12. Prepared Statements
Prepared Statements很像存儲過程,是一種運行在後臺的SQL語句集合,咱們能夠從使用prepared statements得到不少好處,不管是性能問題仍是安全問題。
Prepared Statements能夠檢查一些你綁定好的變量,這樣能夠保護你的程序不會受到「SQL注入式」攻擊。固然,你也能夠手動地檢查你的這些變量,然而,手動的檢查容易出問題,並且很常常會被程序員忘了。當咱們使用一些framework或是ORM的時候,這樣的問題會好一些。
在性能方面,當一個相同的查詢被使用屢次的時候,這會爲你帶來可觀的性能優點。你能夠給這些Prepared Statements定義一些參數,而MySQL只會解析一次。
雖然最新版本的MySQL在傳輸Prepared Statements是使用二進制形勢,因此這會使得網絡傳輸很是有效率。
固然,也有一些狀況下,咱們須要避免使用Prepared Statements,由於其不支持查詢緩存。但聽說版本5.1後支持了。 php程序員之家
在PHP中要使用prepared statements,你能夠查看其使用手冊:mysqli擴展或是使用數據庫抽象層,如:PDO.
13.無緩衝的查詢
正常的狀況下,當你在當你在你的腳本中執行一個SQL語句的時候,你的程序會停在那裏直到沒這個SQL語句返回,而後你的程序再往下繼續執行。你可使用無緩衝查詢來改變這個行爲。ww~phperz~com
關於這個事情,在PHP的文檔中有一個很是不錯的說明:mysql_unbuffered_query()函數:
上面那句話翻譯過來是說,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()。
perz.com
15.固定長度的表會更快
若是表中的全部字段都是「固定長度」的,整個表會被認爲是 「static」 或 「fixed-length」。 例如,表中沒有以下類型的字段: VARCHAR,TEXT,BLOB。只要你包括了其中一個這些字段,那麼這個表就不是「固定長度靜態表」了,這樣,MySQL 引擎會用另外一種方法來處理。
固定長度的表會提升性能,由於MySQL搜尋得會更快一些,由於這些固定的長度是很容易計算下一個數據的偏移量的,因此讀取的天然也會很快。而若是字段不是定長的,那麼,每一次要找下一條的話,須要程序找到主鍵。
而且,固定長度的表也更容易被緩存和重建。不過,惟一的反作用是,固定長度的字段會浪費一些空間,由於定長的字段不管你用不用,他都是要分配那麼多的空間。 php程序員站
使用「垂直分割」技術(見下一條),你能夠分割你的表成爲兩個一個是定長的,一個則是不定長的。
16.垂直分割
「垂直分割」是一種把數據庫中的表按列變成幾張表的方法,這樣能夠下降表的複雜度和字段的數目,從而達到優化的目的。(之前,在銀行作過項目,見過一張表有100多個字段,很恐怖)
示例一:在Users表中有一個字段是家庭地址,這個字段是可選字段,相比起,並且你在數據庫操做的時候除了我的信息外,你並不須要常常讀取或是改寫這個字段。那麼,爲何不把他放到另一張表中呢?這樣會讓你的表有更好的性能,你們想一想是否是,大量的時候,我對於用戶表來講,只有用戶ID,用戶名,口令,用戶角色等會被常用。小一點的表老是會有好的性能。
示例二:你有一個叫「last_login」的字段,它會在每次用戶登陸時被更新。可是,每次更新時會致使該表的查詢緩存被清空。因此,你能夠把這個字段放到另外一個表中,這樣就不會影響你對用戶ID,用戶名,用戶角色的不停地讀取了,由於查詢緩存會幫你增長不少性能。hp程序員之家
另外,你須要注意的是,這些被分出去的字段所造成的表,你不會常常性地去Join他們,否則的話,這樣的性能會比不分割時還要差,並且,會是極數級的降低。 php程
17.拆分大的DELETE或INSERT語句
若是你須要在一個在線的網站上去執行一個大的DELETE或INSERT查詢,你須要很是當心,要避免你的操做讓你的整個網站中止相應。由於這兩個操做是會鎖表的,表一鎖住了,別的操做都進不來了。
Apache會有不少的子進程或線程。因此,其工做起來至關有效率,而咱們的服務器也不但願有太多的子進程,線程和數據庫連接,這是極大的佔服務器資源的事情,尤爲是內存。
若是你把你的表鎖上一段時間,好比30秒鐘,那麼對於一個有很高訪問量的站點來講,這30秒所積累的訪問進程/線程,數據庫連接,打開的文件數,可能不只僅會讓你泊WEB服務Crash,還可能會讓你的整臺服務器立刻掛了。
因此,若是你有一個大的處理,你定你必定把其拆分,使用LIMIT條件是一個好的方法。下面是一個示例:
18.越小的列會越快
對於大多數的數據庫引擎來講,硬盤操做多是最重大的瓶頸。因此,把你的數據變得緊湊會對這種狀況很是有幫助,由於這減小了對硬盤的訪問。
參看MySQL的文檔Storage Requirements查看全部的數據類型。p程序員站
若是一個表只會有幾列罷了(好比說字典表,配置表),那麼,咱們就沒有理由使用INT來作主鍵,使用MEDIUMINT,SMALLINT或是更小的TINYINT會更經濟一些。若是你不須要記錄時間,使用DATE要比DATETIME好得多。
固然,你也須要留夠足夠的擴展空間,否則,你往後來幹這個事,你會死的很難看,參看Slashdot的例子(2009年11月06日),一個簡單的ALTER TABLE語句花了3個多小時,由於裏面有一千六百萬條數據。 perz~com
19.選擇正確的存儲引擎
在MySQL中有兩個存儲引擎MyISAM和InnoDB,每一個引擎都有利有弊。酷殼之前文章《MySQL: InnoDB 仍是 MyISAM?》討論和這個事情。hp程序員之家
MyISAM適合於一些須要大量查詢的應用,但其對於有大量寫操做並非很好。甚至你只是須要update一個字段,整個表都會被鎖起來,而別的進程,就算是讀進程都沒法操做直到讀操做完成。另外,MyISAM對於 SELECT COUNT(*) 這類的計算是超快無比的。 www~phperz~com
InnoDB的趨勢會是一個很是複雜的存儲引擎,對於一些小的應用,它會比 MyISAM還慢。他是它支持「行鎖」 ,因而在寫操做比較多的時候,會更優秀。而且,他還支持更多的高級應用,好比:事務。
下面是MySQL的手冊
20.使用一個對象關係映射器(Object Relational Mapper)
使用 ORM (Object Relational Mapper),你可以得到可靠的性能增漲。一個ORM能夠作的全部事情,也能被手動的編寫出來。可是,這須要一個高級專家。 phperz~com
ORM的最重要的是「Lazy Loading」,也就是說,只有在須要的去取值的時候纔會去真正的去作。但你也須要當心這種機制的反作用,由於這頗有可能會由於要去建立不少不少小的查詢反而會下降性能。hperz.com
ORM還能夠把你的SQL語句打包成一個事務,這會比單獨執行他們快得多得多。
目前,我的最喜歡的PHP的ORM是:Doctrine。
21.當心「永久連接」
「永久連接」的目的是用來減小從新建立MySQL連接的次數。當一個連接被建立了,它會永遠處在鏈接的狀態,就算是數據庫操做已經結束了。並且,自從咱們的Apache開始重用它的子進程後——也就是說,下一次的HTTP請求會重用Apache的子進程,並重用相同的MySQL連接。
PHP手冊:mysql_pconnect()
在理論上來講,這聽起來很是的不錯。可是從我的經驗(也是大多數人的)上來講,這個功能製造出來的麻煩事更多。由於,你只有有限的連接數,內存問題,文件句柄數,等等。
並且,Apache運行在極端並行的環境中,會建立不少不少的了進程。這就是爲何這種「永久連接」的機制工做地很差的緣由。在你決定要使用「永久連接」以前,你須要好好地考慮一下你的整個系統的架構。