MySQL性能優(yōu)化的21個(gè)最佳實(shí)踐
TechTarget中國原創(chuàng)內(nèi)容,原文鏈接:http://www.searchdatabase.com.cn/showcontent_38045.htm
????? 今天,數(shù)據(jù)庫的操作越來越成為整個(gè)應(yīng)用的性能瓶頸了,這點(diǎn)對于Web應(yīng)用尤其明顯。關(guān)于數(shù)據(jù)庫的性能,這并不只是DBA才需要擔(dān)心的事,而這更是我們程序員需要去關(guān)注的事情。當(dāng)我們?nèi)ピO(shè)計(jì)數(shù)據(jù)庫表結(jié)構(gòu),對操作數(shù)據(jù)庫時(shí)(尤其是查表時(shí)的SQL語句),我們都需要注意數(shù)據(jù)操作的性能。這里,我們不會講過多的SQL語句的優(yōu)化,而只是針對MySQL這一Web應(yīng)用最多的數(shù)據(jù)庫。希望下面的這些優(yōu)化技巧對你有用。
1. 為查詢緩存優(yōu)化你的查詢
大多數(shù)的MySQL服務(wù)器都開啟了查詢緩存。這是提高性最有效的方法之一,而且這是被MySQL的數(shù)據(jù)庫引擎處理的。當(dāng)有很多相同的查詢被執(zhí)行了多次的時(shí)候,這些查詢結(jié)果會被放到一個(gè)緩存中,這樣,后續(xù)的相同的查詢就不用操作表而直接訪問緩存結(jié)果了。
這里最主要的問題是,對于程序員來說,這個(gè)事情是很容易被忽略的。因?yàn)椋覀兡承┎樵冋Z句會讓MySQL不使用緩存。請看下面的示例:
上面兩條SQL語句的差別就是 CURDATE() ,MySQL的查詢緩存對這個(gè)函數(shù)不起作用。所以,像 NOW() 和 RAND()
或是其它的諸如此類的SQL函數(shù)都不會開啟查詢緩存,因?yàn)檫@些函數(shù)的返回是會不定的易變的。所以,你所需要的就是用一個(gè)變量來代替MySQL的函數(shù),從而開啟緩存。
2. EXPLAIN 你的 SELECT 查詢
使用 EXPLAIN 關(guān)鍵字可以讓你知道MySQL是如何處理你的SQL語句的。這可以幫你分析你的查詢語句或是表結(jié)構(gòu)的性能瓶頸。
EXPLAIN 的查詢結(jié)果還會告訴你你的索引主鍵被如何利用的,你的數(shù)據(jù)表是如何被搜索和排序的……等等,等等。
挑一個(gè)你的SELECT語句(推薦挑選那個(gè)最復(fù)雜的,有多表聯(lián)接的),把關(guān)鍵字EXPLAIN加到前面。你可以使用phpmyadmin來做這個(gè)事。然后,你會看到一張表格。下面的這個(gè)示例中,我們忘記加上了group_id索引,并且有表聯(lián)接:
當(dāng)我們?yōu)?group_id 字段加上索引后:
我們可以看到,前一個(gè)結(jié)果顯示搜索了 7883 行,而后一個(gè)只是搜索了兩個(gè)表的 9 和 16 行。查看rows列可以讓我們找到潛在的性能問題。
3. 當(dāng)只要一行數(shù)據(jù)時(shí)使用 LIMIT 1
當(dāng)你查詢表的有些時(shí)候,你已經(jīng)知道結(jié)果只會有一條結(jié)果,但因?yàn)槟憧赡苄枰etch游標(biāo),或是你也許會去檢查返回的記錄數(shù)。
在這種情況下,加上 LIMIT 1 可以增加性能。這樣一樣,MySQL數(shù)據(jù)庫引擎會在找到一條數(shù)據(jù)后停止搜索,而不是繼續(xù)往后查少下一條符合記錄的數(shù)據(jù)。
下面的示例,只是為了找一下是否有“中國”的用戶,很明顯,后面的會比前面的更有效率。(請注意,第一條中是Select *,第二條是Select 1)
4. 為搜索字段建索引
索引并不一定就是給主鍵或是唯一的字段。如果在你的表中,有某個(gè)字段你總要會經(jīng)常用來做搜索,那么,請為其建立索引吧。
從上圖你可以看到那個(gè)搜索字串 “l(fā)ast_name LIKE ‘a(chǎn)%’”,一個(gè)是建了索引,一個(gè)是沒有索引,性能差了4倍左右。
另外,你應(yīng)該也需要知道什么樣的搜索是不能使用正常的索引的。例如,當(dāng)你需要在一篇大的文章中搜索一個(gè)詞時(shí),如: “WHERE
post_content LIKE ‘%apple%’”,索引可能是沒有意義的。你可能需要使用MySQL全文索引
或是自己做一個(gè)索引(比如說:搜索關(guān)鍵詞或是Tag什么的)
5. 在Join表的時(shí)候使用相當(dāng)類型的例,并將其索引
如果你的應(yīng)用程序有很多 JOIN 查詢,你應(yīng)該確認(rèn)兩個(gè)表中Join的字段是被建過索引的。這樣,MySQL內(nèi)部會啟動為你優(yōu)化Join的SQL語句的機(jī)制。
而且,這些被用來Join的字段,應(yīng)該是相同的類型的。例如:如果你要把 DECIMAL 字段和一個(gè) INT 字段Join在一起,MySQL就無法使用它們的索引。對于那些STRING類型,還需要有相同的字符集才行。(兩個(gè)表的字符集有可能不一樣)
6. 千萬不要 ORDER BY RAND()
想打亂返回的數(shù)據(jù)行?隨機(jī)挑一個(gè)數(shù)據(jù)?真不知道誰發(fā)明了這種用法,但很多新手很喜歡這樣用。但你確不了解這樣做有多么可怕的性能問題。
如果你真的想把返回的數(shù)據(jù)行打亂了,你有N種方法可以達(dá)到這個(gè)目的。這樣使用只讓你的數(shù)據(jù)庫的性能呈指數(shù)級的下降。這里的問題是:MySQL會不得不去執(zhí)行RAND()函數(shù)(很耗CPU時(shí)間),而且這是為了每一行記錄去記行,然后再對其排序。就算是你用了Limit1也無濟(jì)于事(因?yàn)橐判?
下面的示例是隨機(jī)挑一條記錄
7. 避免 SELECT *
從數(shù)據(jù)庫里讀出越多的數(shù)據(jù),那么查詢就會變得越慢。并且,如果你的數(shù)據(jù)庫服務(wù)器和WEB服務(wù)器是兩臺獨(dú)立的服務(wù)器的話,這還會增加網(wǎng)絡(luò)傳輸?shù)呢?fù)載。
所以,你應(yīng)該養(yǎng)成一個(gè)需要什么就取什么的好的習(xí)慣。
8. 永遠(yuǎn)為每張表設(shè)置一個(gè)ID
我們應(yīng)該為數(shù)據(jù)庫里的每張表都設(shè)置一個(gè)ID做為其主鍵,而且最好的是一個(gè)INT型的(推薦使用UNSIGNED),并設(shè)置上自動增加的AUTO_INCREMENT標(biāo)志。
就算是你 users 表有一個(gè)主鍵叫 “email”的字段,你也別讓它成為主鍵。使用 VARCHAR 類型來當(dāng)主鍵會使用得性能下降。另外,在你的程序中,你應(yīng)該使用表的ID來構(gòu)造你的數(shù)據(jù)結(jié)構(gòu)。
而且,在MySQL數(shù)據(jù)引擎下,還有一些操作需要使用主鍵,在這些情況下,主鍵的性能和設(shè)置變得非常重要,比如,集群,分區(qū)……
在這里,只有一個(gè)情況是例外,那就是“關(guān)聯(lián)表”的“外鍵”,也就是說,這個(gè)表的主鍵,通過若干個(gè)別的表的主鍵構(gòu)成。我們把這個(gè)情況叫做“外鍵”。比如:有一個(gè)“學(xué)生表”有學(xué)生的ID,有一個(gè)“課程表”有課程ID,那么,“成績表”就是“關(guān)聯(lián)表”了,其關(guān)聯(lián)了學(xué)生表和課程表,在成績表中,學(xué)生ID和課程ID叫“外鍵”其共同組成主鍵。
9. 使用 ENUM 而不是 VARCHAR
ENUM 類型是非常快和緊湊的。在實(shí)際上,其保存的是 TINYINT,但其外表上顯示為字符串。這樣一來,用這個(gè)字段來做一些選項(xiàng)列表變得相當(dāng)?shù)耐昝馈?/p>
如果你有一個(gè)字段,比如“性別”,“國家”,“民族”,“狀態(tài)”或“部門”,你知道這些字段的取值是有限而且固定的,那么,你應(yīng)該使用 ENUM 而不是 VARCHAR。
MySQL也有一個(gè)“建議”(見第十條)告訴你怎么去重新組織你的表結(jié)構(gòu)。當(dāng)你有一個(gè) VARCHAR 字段時(shí),這個(gè)建議會告訴你把其改成 ENUM 類型。使用 PROCEDURE ANALYSE() 你可以得到相關(guān)的建議。
10. 從 PROCEDURE ANALYSE() 取得建議
PROCEDURE ANALYSE() 會讓 MySQL 幫你去分析你的字段和其實(shí)際的數(shù)據(jù),并會給你一些有用的建議。只有表中有實(shí)際的數(shù)據(jù),這些建議才會變得有用,因?yàn)橐鲆恍┐蟮臎Q定是需要有數(shù)據(jù)作為基礎(chǔ)的。
例如,如果你創(chuàng)建了一個(gè) INT 字段作為你的主鍵,然而并沒有太多的數(shù)據(jù),那么,PROCEDURE
ANALYSE()會建議你把這個(gè)字段的類型改成 MEDIUMINT 。或是你使用了一個(gè) VARCHAR
字段,因?yàn)閿?shù)據(jù)不多,你可能會得到一個(gè)讓你把它改成 ENUM 的建議。這些建議,都是可能因?yàn)閿?shù)據(jù)不夠多,所以決策做得就不夠準(zhǔn)。
在phpmyadmin里,你可以在查看表時(shí),點(diǎn)擊 “Propose table structure” 來查看這些建議
一定要注意,這些只是建議,只有當(dāng)你的表里的數(shù)據(jù)越來越多時(shí),這些建議才會變得準(zhǔn)確。一定要記住,你才是最終做決定的人。
11. 盡可能的使用 NOT NULL
除非你有一個(gè)很特別的原因去使用 NULL 值,你應(yīng)該總是讓你的字段保持 NOT NULL。這看起來好像有點(diǎn)爭議,請往下看。
首先,問問你自己“Empty”和“NULL”有多大的區(qū)別(如果是INT,那就是0和NULL)?如果你覺得它們之間沒有什么區(qū)別,那么你就不要使用NULL。(你知道嗎?在 Oracle 里,NULL 和 Empty 的字符串是一樣的!)
不要以為 NULL 不需要空間,其需要額外的空間,并且,在你進(jìn)行比較的時(shí)候,你的程序會更復(fù)雜。 當(dāng)然,這里并不是說你就不能使用NULL了,現(xiàn)實(shí)情況是很復(fù)雜的,依然會有些情況下,你需要使用NULL值。
12. Prepared Statements
Prepared Statements很像存儲過程,是一種運(yùn)行在后臺的SQL語句集合,我們可以從使用 prepared statements 獲得很多好處,無論是性能問題還是安全問題。
Prepared Statements
可以檢查一些你綁定好的變量,這樣可以保護(hù)你的程序不會受到“SQL注入式”攻擊。當(dāng)然,你也可以手動地檢查你的這些變量,然而,手動的檢查容易出問題,而且很經(jīng)常會被程序員忘了。當(dāng)我們使用一些framework或是ORM的時(shí)候,這樣的問題會好一些。
在性能方面,當(dāng)一個(gè)相同的查詢被使用多次的時(shí)候,這會為你帶來可觀的性能優(yōu)勢。你可以給這些Prepared Statements定義一些參數(shù),而MySQL只會解析一次。
雖然最新版本的MySQL在傳輸Prepared Statements是使用二進(jìn)制形勢,所以這會使得網(wǎng)絡(luò)傳輸非常有效率。
當(dāng)然,也有一些情況下,我們需要避免使用Prepared Statements,因?yàn)槠洳恢С植樵兙彺妗5珦?jù)說版本5.1后支持了。
在PHP中要使用prepared statements,你可以查看其使用手冊:mysqli 擴(kuò)展 或是使用數(shù)據(jù)庫抽象層,如: PDO.
13. 無緩沖的查詢
正常的情況下,當(dāng)你在當(dāng)你在你的腳本中執(zhí)行一個(gè)SQL語句的時(shí)候,你的程序會停在那里直到?jīng)]這個(gè)SQL語句返回,然后你的程序再往下繼續(xù)執(zhí)行。你可以使用無緩沖查詢來改變這個(gè)行為。
mysql_unbuffered_query()
發(fā)送一個(gè)SQL語句到MySQL而并不像mysql_query()一樣去自動fethch和緩存結(jié)果。這會相當(dāng)節(jié)約很多可觀的內(nèi)存,尤其是那些會產(chǎn)生大量結(jié)果的查詢語句,并且,你不需要等到所有的結(jié)果都返回,只需要第一行數(shù)據(jù)返回的時(shí)候,你就可以開始馬上開始工作于查詢結(jié)果了。
然而,這會有一些限制。因?yàn)槟阋窗阉行卸甲x走,或是你要在進(jìn)行下一次的查詢前調(diào)用 mysql_free_result()
清除結(jié)果。而且, mysql_num_rows() 或 mysql_data_seek()
將無法使用。所以,是否使用無緩沖的查詢你需要仔細(xì)考慮。
14. 把IP地址存成 UNSIGNED INT
很多程序員都會創(chuàng)建一個(gè) VARCHAR(15)
字段來存放字符串形式的IP而不是整形的IP。如果你用整形來存放,只需要4個(gè)字節(jié),并且你可以有定長的字段。而且,這會為你帶來查詢上的優(yōu)勢,尤其是當(dāng)你需要使用這樣的WHERE條件:IP
between ip1 and ip2。
我們必需要使用UNSIGNED INT,因?yàn)?IP地址會使用整個(gè)32位的無符號整形。
而你的查詢,你可以使用 INET_ATON() 來把一個(gè)字符串IP轉(zhuǎn)成一個(gè)整形,并使用 INET_NTOA() 把一個(gè)整形轉(zhuǎn)成一個(gè)字符串IP。在PHP中,也有這樣的函數(shù) ip2long() 和 long2ip()。
15. 固定長度的表會更快
如果表中的所有字段都是“固定長度”的,整個(gè)表會被認(rèn)為是 “static” 或 “fixed-length”。
例如,表中沒有如下類型的字段:
VARCHAR,TEXT,BLOB。只要你包括了其中一個(gè)這些字段,那么這個(gè)表就不是“固定長度靜態(tài)表”了,這樣,MySQL
引擎會用另一種方法來處理。
固定長度的表會提高性能,因?yàn)镸ySQL搜尋得會更快一些,因?yàn)檫@些固定的長度是很容易計(jì)算下一個(gè)數(shù)據(jù)的偏移量的,所以讀取的自然也會很快。而如果字段不是定長的,那么,每一次要找下一條的話,需要程序找到主鍵。
并且,固定長度的表也更容易被緩存和重建。不過,唯一的副作用是,固定長度的字段會浪費(fèi)一些空間,因?yàn)槎ㄩL的字段無論你用不用,他都是要分配那么多的空間。
使用“垂直分割”技術(shù)(見下一條),你可以分割你的表成為兩個(gè)一個(gè)是定長的,一個(gè)則是不定長的。
16. 垂直分割
“垂直分割”是一種把數(shù)據(jù)庫中的表按列變成幾張表的方法,這樣可以降低表的復(fù)雜度和字段的數(shù)目,從而達(dá)到優(yōu)化的目的。(以前,在銀行做過項(xiàng)目,見過一張表有100多個(gè)字段,很恐怖)
示例一:在Users表中有一個(gè)字段是家庭地址,這個(gè)字段是可選字段,相比起,而且你在數(shù)據(jù)庫操作的時(shí)候除了個(gè)人信息外,你并不需要經(jīng)常讀取或是改寫這個(gè)字段。那么,為什么不把他放到另外一張表中呢?
這樣會讓你的表有更好的性能,大家想想是不是,大量的時(shí)候,我對于用戶表來說,只有用戶ID,用戶名,口令,用戶角色等會被經(jīng)常使用。小一點(diǎn)的表總是會有好的性能。
示例二: 你有一個(gè)叫 “l(fā)ast_login”
的字段,它會在每次用戶登錄時(shí)被更新。但是,每次更新時(shí)會導(dǎo)致該表的查詢緩存被清空。所以,你可以把這個(gè)字段放到另一個(gè)表中,這樣就不會影響你對用戶ID,用戶名,用戶角色的不停地讀取了,因?yàn)椴樵兙彺鏁湍阍黾雍芏嘈阅堋?/p>
另外,你需要注意的是,這些被分出去的字段所形成的表,你不會經(jīng)常性地去Join他們,不然的話,這樣的性能會比不分割時(shí)還要差,而且,會是極數(shù)級的下降。
17. 拆分大的 DELETE 或 INSERT 語句
如果你需要在一個(gè)在線的網(wǎng)站上去執(zhí)行一個(gè)大的 DELETE 或 INSERT 查詢,你需要非常小心,要避免你的操作讓你的整個(gè)網(wǎng)站停止相應(yīng)。因?yàn)檫@兩個(gè)操作是會鎖表的,表一鎖住了,別的操作都進(jìn)不來了。
Apache 會有很多的子進(jìn)程或線程。所以,其工作起來相當(dāng)有效率,而我們的服務(wù)器也不希望有太多的子進(jìn)程,線程和數(shù)據(jù)庫鏈接,這是極大的占服務(wù)器資源的事情,尤其是內(nèi)存。
如果你把你的表鎖上一段時(shí)間,比如30秒鐘,那么對于一個(gè)有很高訪問量的站點(diǎn)來說,這30秒所積累的訪問進(jìn)程/線程,數(shù)據(jù)庫鏈接,打開的文件數(shù),可能不僅僅會讓你泊WEB服務(wù)Crash,還可能會讓你的整臺服務(wù)器馬上掛了。
所以,如果你有一個(gè)大的處理,你定你一定把其拆分,使用 LIMIT 條件是一個(gè)好的方法。下面是一個(gè)示例:
18. 越小的列會越快
對于大多數(shù)的數(shù)據(jù)庫引擎來說,硬盤操作可能是最重大的瓶頸。所以,把你的數(shù)據(jù)變得緊湊會對這種情況非常有幫助,因?yàn)檫@減少了對硬盤的訪問。
參看 MySQL 的文檔 Storage Requirements 查看所有的數(shù)據(jù)類型。
如果一個(gè)表只會有幾列罷了(比如說字典表,配置表),那么,我們就沒有理由使用 INT 來做主鍵,使用 MEDIUMINT, SMALLINT 或是更小的 TINYINT 會更經(jīng)濟(jì)一些。如果你不需要記錄時(shí)間,使用 DATE 要比 DATETIME 好得多。
當(dāng)然,你也需要留夠足夠的擴(kuò)展空間,不然,你日后來干這個(gè)事,你會死的很難看,參看Slashdot的例子(2009年11月06日),一個(gè)簡單的ALTER TABLE語句花了3個(gè)多小時(shí),因?yàn)槔锩嬗幸磺Я偃f條數(shù)據(jù)。
19. 選擇正確的存儲引擎
在 MySQL 中有兩個(gè)存儲引擎 MyISAM 和 InnoDB,每個(gè)引擎都有利有弊。酷殼以前文章《MySQL: InnoDB 還是 MyISAM?》討論和這個(gè)事情。
MyISAM
適合于一些需要大量查詢的應(yīng)用,但其對于有大量寫操作并不是很好。甚至你只是需要update一個(gè)字段,整個(gè)表都會被鎖起來,而別的進(jìn)程,就算是讀進(jìn)程都無法操作直到讀操作完成。另外,MyISAM
對于 SELECT COUNT(*) 這類的計(jì)算是超快無比的。
InnoDB 的趨勢會是一個(gè)非常復(fù)雜的存儲引擎,對于一些小的應(yīng)用,它會比 MyISAM 還慢。他是它支持“行鎖” ,于是在寫操作比較多的時(shí)候,會更優(yōu)秀。并且,他還支持更多的高級應(yīng)用,比如:事務(wù)。
下面是MySQL的手冊
target=”_blank”MyISAM Storage Engine
InnoDB Storage Engine
20. 使用一個(gè)對象關(guān)系映射器(Object Relational Mapper)
使用 ORM (Object Relational Mapper),你能夠獲得可靠的性能增漲。一個(gè)ORM可以做的所有事情,也能被手動的編寫出來。但是,這需要一個(gè)高級專家。
ORM 的最重要的是“Lazy Loading”,也就是說,只有在需要的去取值的時(shí)候才會去真正的去做。但你也需要小心這種機(jī)制的副作用,因?yàn)檫@很有可能會因?yàn)橐?chuàng)建很多很多小的查詢反而會降低性能。
ORM 還可以把你的SQL語句打包成一個(gè)事務(wù),這會比單獨(dú)執(zhí)行他們快得多得多。
目前,個(gè)人最喜歡的PHP的ORM是:Doctrine。
21. 小心“永久鏈接”
“永久鏈接”的目的是用來減少重新創(chuàng)建MySQL鏈接的次數(shù)。當(dāng)一個(gè)鏈接被創(chuàng)建了,它會永遠(yuǎn)處在連接的狀態(tài),就算是數(shù)據(jù)庫操作已經(jīng)結(jié)束了。而且,自從我們的Apache開始重用它的子進(jìn)程后——也就是說,下一次的HTTP請求會重用Apache的子進(jìn)程,并重用相同的
MySQL 鏈接。
PHP手冊:mysql_pconnect()
在理論上來說,這聽起來非常的不錯(cuò)。但是從個(gè)人經(jīng)驗(yàn)(也是大多數(shù)人的)上來說,這個(gè)功能制造出來的麻煩事更多。因?yàn)椋阒挥杏邢薜逆溄訑?shù),內(nèi)存問題,文件句柄數(shù),等等。
而且,Apache 運(yùn)行在極端并行的環(huán)境中,會創(chuàng)建很多很多的了進(jìn)程。這就是為什么這種“永久鏈接”的機(jī)制工作地不好的原因。在你決定要使用“永久鏈接”之前,你需要好好地考慮一下你的整個(gè)系統(tǒng)的架構(gòu)。
TechTarget中國原創(chuàng)內(nèi)容,原文鏈接:http://www.searchdatabase.com.cn/showcontent_38045.htm
本文來源于網(wǎng)絡(luò)緊供學(xué)習(xí)
作者:andyao
原文link: http://andyao.iteye.com/admin/show/144033
轉(zhuǎn)載請留名
1. 簡介
在Web應(yīng)用程序體系架構(gòu)中,數(shù)據(jù)持久層(通常是一個(gè)關(guān)系數(shù)據(jù)庫)是關(guān)鍵的核心部分,它對系統(tǒng)的性能有非常重要的影響。MySQL是目前使用最多的開源數(shù)據(jù)庫,但是MySQL數(shù)據(jù)庫的默認(rèn)設(shè)置性能非常的差,僅僅是一個(gè)玩具數(shù)據(jù)庫。因此在產(chǎn)品中使用MySQL數(shù)據(jù)庫必須進(jìn)行必要的優(yōu)化。
優(yōu)化是一個(gè)復(fù)雜的任務(wù),本文描述MySQL相關(guān)的數(shù)據(jù)庫設(shè)計(jì)和查詢優(yōu)化,服務(wù)器端優(yōu)化,存儲引擎優(yōu)化。
2. 數(shù)據(jù)庫設(shè)計(jì)和查詢優(yōu)化
在MySQL Server性能調(diào)優(yōu)中,首先要考慮的就是Database Schema設(shè)計(jì),這一點(diǎn)是非常重要的。一個(gè)糟糕的Schema設(shè)計(jì)即使在性能調(diào)優(yōu)的MySQL Server上運(yùn)行,也會表現(xiàn)出很差的性能;和Schema相似,查詢語句的設(shè)計(jì)也會影響MySQL的性能,應(yīng)該避免寫出低效的SQL查詢。這一節(jié)將詳細(xì)討論這兩方面的優(yōu)化。
2.1 Schema Design
Schema的優(yōu)化取決于將要運(yùn)行什么樣的query,不同的query會有不同的Schema優(yōu)化方案。2.2節(jié)將介紹Query Design的優(yōu)化。Schema設(shè)計(jì)同樣受到預(yù)期數(shù)據(jù)集大小的影響。Schema設(shè)計(jì)時(shí)主要考慮:標(biāo)準(zhǔn)化,數(shù)據(jù)類型,索引。
2.1.1 標(biāo)準(zhǔn)化
標(biāo)準(zhǔn)化是在數(shù)據(jù)庫中組織數(shù)據(jù)的過程。其中包括,根據(jù)設(shè)計(jì)規(guī)則創(chuàng)建表并在這些表間建立關(guān)系;通過取消冗余度與不一致相關(guān)性,該設(shè)計(jì)規(guī)則可以同時(shí)保護(hù)數(shù)據(jù)并提高數(shù)據(jù)的靈活性。通常數(shù)據(jù)庫標(biāo)準(zhǔn)化是讓數(shù)據(jù)庫設(shè)計(jì)符合某一級別的范式,通常滿足第三范式即可。也有第四范式(也稱為 Boyce Codd范式,BCNF))與第五范式存在,但是在實(shí)際設(shè)計(jì)中很少考慮。忽視這些規(guī)則可能使得數(shù)據(jù)庫的設(shè)計(jì)不太完美,但這不應(yīng)影響功能。
標(biāo)準(zhǔn)化的特點(diǎn):
1) 所有的“對象”都在它自己的table中,沒有冗余。
2) 數(shù)據(jù)庫通常由E-R圖生成。
3) 簡潔,更新屬性通常只需要更新很少的記錄。
4) Join操作比較耗時(shí)。
5) Select,sort優(yōu)化措施比較少。
6) 適用于OLTP應(yīng)用。
非標(biāo)準(zhǔn)化的特點(diǎn):
1) 在一張表中存儲很多數(shù)據(jù),數(shù)據(jù)冗余。
2) 更新數(shù)據(jù)開銷很大,更新一個(gè)屬性可能會更新很多表,很多記錄。
3) 在刪除數(shù)據(jù)是有可能丟失數(shù)據(jù)。
4) Select,order有很多優(yōu)化的選擇。
5) 適用于DSS應(yīng)用。
標(biāo)準(zhǔn)化和非標(biāo)準(zhǔn)化都有各自的優(yōu)缺點(diǎn),通常在一個(gè)數(shù)據(jù)庫設(shè)計(jì)中可以混合使用,一部分表格標(biāo)準(zhǔn)化,一部分表格保留一些冗余數(shù)據(jù):
1) 對OLTP使用標(biāo)準(zhǔn)化,對DSS使用非標(biāo)準(zhǔn)化
2) 使用物化視圖。MySQL不直接支持該數(shù)據(jù)庫特性,但是可以用MyISAM表代替。
3) 冗余一些數(shù)據(jù)在表格中,例如將ref_id和name存在同一張表中。但是要注意更新問題。
4) 對于一些簡單的對象,直接使用value作為建。例如IP address等
5) Reference by PRIMARY/UNIQUE KEY。MySQL可以優(yōu)化這種操作,例如:
java 代碼
select city_name
from city,state
where state_id=state.id and state.code=‘CA’” converted to “select city_name from city where state_id=12
2.1.2 數(shù)據(jù)類型
最基本的優(yōu)化之一就是使表在磁盤上占據(jù)的空間盡可能小。這能帶來性能非常大的提升,因?yàn)閿?shù)據(jù)小,磁盤讀入較快,并且在查詢過程中表內(nèi)容被處理所占用的內(nèi)存更少。同時(shí),在更小的列上建索引,索引也會占用更少的資源。
可以使用下面的技術(shù)可以使表的性能更好并且使存儲空間最小:
1) 使用正確合適的類型,不要將數(shù)字存儲為字符串。
2) 盡可能地使用最有效(最小)的數(shù)據(jù)類型。MySQL有很多節(jié)省磁盤空間和內(nèi)存的專業(yè)化類型。
3) 盡可能使用較小的整數(shù)類型使表更小。例如,MEDIUMINT經(jīng)常比INT好一些,因?yàn)镸EDIUMINT列使用的空間要少25%。
4) 如果可能,聲明列為NOT NULL。它使任何事情更快而且每列可以節(jié)省一位。注意如果在應(yīng)用程序中確實(shí)需要NULL,應(yīng)該毫無疑問使用它,只是避免 默認(rèn)地在所有列上有它。
5) 對于MyISAM表,如果沒有任何變長列(VARCHAR、TEXT或BLOB列),使用固定尺寸的記錄格式。這比較快但是不幸地可能會浪費(fèi)一些空間。即使你已經(jīng)用CREATE選項(xiàng)讓VARCHAR列ROW_FORMAT=fixed,也可以提示想使用固定長度的行。
6) 使用sample character set,例如latin1。盡量少使用utf-8,因?yàn)閡tf-8占用的空間是latin1的3倍。可以在不需要使用utf-8的字段上面使用latin1,例如mail,url等。
2.1.3 索引
所有MySQL列類型可以被索引。對相關(guān)列使用索引是提高SELECT操作性能的最佳途徑。使用索引應(yīng)該注意以下幾點(diǎn):
1) MySQL只會使用前綴,例如key(a, b) …where b=5 將使用不到索引。
2) 要選擇性的使用索引。在變化很少的列上使用索引并不是很好,例如性別列。
3) 在Unique列上定義Unique index。
4) 避免建立使用不到的索引。
5) 在Btree index中(InnoDB使用Btree),可以在需要排序的列上建立索引。
6) 避免重復(fù)的索引。
7) 避免在已有索引的前綴上建立索引。例如:如果存在index(a,b)則去掉index(a)。
8) 控制單個(gè)索引的長度。使用key(name(8))在數(shù)據(jù)的前面幾個(gè)字符建立索引。
9) 越是短的鍵值越好,最好使用integer。
10) 在查詢中要使用到索引(使用explain查看),可以減少讀磁盤的次數(shù),加速讀取數(shù)據(jù)。
11) 相近的鍵值比隨機(jī)好。Auto_increment就比uuid好。
12) Optimize table可以壓縮和排序index,注意不要頻繁運(yùn)行。
13) Analyze table可以更新數(shù)據(jù)。
2.2 Designing queries
查詢語句的優(yōu)化是一個(gè)Case by case的問題,不同的sql有不同的優(yōu)化方案,在這里我只列出一些通用的技巧。
1) 在有index的情況下,盡量保證查詢使用了正確的index。可以使用EXPLAIN select …查看結(jié)果,分析查詢。
2) 查詢時(shí)使用匹配的類型。例如select * from a where id=5, 如果這里id是字符類型,同時(shí)有index,這條查詢則使用不到index,會做全表掃描,速度會很慢。正確的應(yīng)該是 … where id=”5” ,加上引號表明類型是字符。
3) 使用--log-slow-queries –long-query-time=2查看查詢比較慢的語句。然后使用explain分析查詢,做出優(yōu)化。
3. 服務(wù)器端優(yōu)化3.1 MySQL安裝
MySQL有很多發(fā)行版本,最好使用MySQL AB發(fā)布的二進(jìn)制版本。也可以下載源代碼進(jìn)行編譯安裝,但是編譯器和類庫的一些bug可能會使編譯完成的MySQL存在潛在的問題。
如果安裝MySQL的服務(wù)器使用的是Intel公司的處理器,可以使用intel c++編譯的版本,在LinuxWorld2005的一篇PPT中提到,使用intel C++編譯器編譯的MySQL查詢速度比正常版本快30%左右。Intel c++編譯版本可以在MySQL官方網(wǎng)站下載。
3.2 服務(wù)器設(shè)置優(yōu)化
MySQL默認(rèn)的設(shè)置性能很差,所以要做一些參數(shù)的調(diào)整。這一節(jié)介紹一些通用的參數(shù)調(diào)整,不涉及具體的存儲引擎(主要指MyISAM,InnoDB,相關(guān)優(yōu)化在4中介紹)。
--character-set:如果是單一語言使用簡單的character set例如latin1。盡量少用Utf-8,utf-8占用空間較多。
--memlock:鎖定MySQL只能運(yùn)行在內(nèi)存中,避免swapping,但是如果內(nèi)存不夠時(shí)有可能出現(xiàn)錯(cuò)誤。
--max_allowed_packet:要足夠大,以適應(yīng)比較大的SQL查詢,對性能沒有太大影響,主要是避免出現(xiàn)packet錯(cuò)誤。
--max_connections:server允許的最大連接。太大的話會出現(xiàn)out of memory。
--table_cache:MySQL在同一時(shí)間保持打開的table的數(shù)量。打開table開銷比較大。一般設(shè)置為512。
--query_cache_size: 用于緩存查詢的內(nèi)存大小。
--datadir:mysql存放數(shù)據(jù)的根目錄,和安裝文件分開在不同的磁盤可以提高一點(diǎn)性能。
4. 存儲引擎優(yōu)化
MySQL支持不同的存儲引擎,主要使用的有MyISAM和InnoDB。
4.1 MyISAM
MyISAM管理非事務(wù)表。它提供高速存儲和檢索,以及全文搜索能力。MyISAM在所有MySQL配置里被支持,它是默認(rèn)的存儲引擎,除非配置MySQL默認(rèn)使用另外一個(gè)引擎。
4.1.1 MyISAM特性
4.1.1.1 MyISAM Properties
1) 不支持事務(wù),宕機(jī)會破壞表
2) 使用較小的內(nèi)存和磁盤空間
3) 基于表的鎖,并發(fā)更新數(shù)據(jù)會出現(xiàn)嚴(yán)重性能問題
4) MySQL只緩存Index,數(shù)據(jù)由OS緩存
4.1.1.2 Typical MyISAM usages
1) 日志系統(tǒng)
2) 只讀或者絕大部分是讀操作的應(yīng)用
3) 全表掃描
4) 批量導(dǎo)入數(shù)據(jù)
5) 沒有事務(wù)的低并發(fā)讀/寫
4.1.2 MyISAM優(yōu)化要點(diǎn)
1) 聲明列為NOT NULL,可以減少磁盤存儲。
2) 使用optimize table做碎片整理,回收空閑空間。注意僅僅在非常大的數(shù)據(jù)變化后運(yùn)行。
3) Deleting/updating/adding大量數(shù)據(jù)的時(shí)候禁止使用index。使用ALTER TABLE t DISABLE KEYS。
4) 設(shè)置myisam_max_[extra]_sort_file_size足夠大,可以顯著提高repair table的速度。
4.1.3 MyISAM Table Locks
1) 避免并發(fā)insert,update。
2) 可以使用insert delayed,但是有可能丟失數(shù)據(jù)。
3) 優(yōu)化查詢語句。
4) 水平分區(qū)。
5) 垂直分區(qū)。
6) 如果都不起作用,使用InnoDB。
4.1.4 MyISAM Key Cache
1) 設(shè)置key_buffer_size variable。MyISAN最主要的cache設(shè)置,用于緩存MyISAM表格的index數(shù)據(jù),該參數(shù)只對MyISAM有影響。通常在只使用MyISAM的Server中設(shè)置25-33%的內(nèi)存大小。
2) 可以使用幾個(gè)不同的Key Caches(對一些hot data)。
a) SET GLOBAL test.key_buffer_size=512*1024;
b) CACHE INDEX t1.i1, t2.i1, t3 IN test;
2) Preload index到Cache中可以提高查詢速度。因?yàn)閜reloading index是順序的,所以非常快。
a) LOAD INDEX INTO CACHE t1, t2 IGNORE LEAVES;
4.2 InnoDB
InnoDB給MySQL提供了具有提交,回滾和崩潰恢復(fù)能力的事務(wù)安全(ACID兼容)存儲引擎。InnoDB提供row level lock,并且也在SELECT語句提供一個(gè)Oracle風(fēng)格一致的非鎖定讀。這些特色增加了多用戶部署和性能。沒有在InnoDB中擴(kuò)大鎖定的需要,因?yàn)樵贗nnoDB中row level lock適合非常小的空間。InnoDB也支持FOREIGN KEY約束。在SQL查詢中,你可以自由地將InnoDB類型的表與其它MySQL的表的類型混合起來,甚至在同一個(gè)查詢中也可以混合。
InnoDB是為在處理巨大數(shù)據(jù)量時(shí)獲得最大性能而設(shè)計(jì)的。它的CPU使用效率非常高。
InnoDB存儲引擎已經(jīng)完全與MySQL服務(wù)器整合,InnoDB存儲引擎為在內(nèi)存中緩存數(shù)據(jù)和索引而維持它自己的緩沖池。 InnoDB存儲它的表&索引在一個(gè)表空間中,表空間可以包含數(shù)個(gè)文件(或原始磁盤分區(qū))。這與MyISAM表不同,比如在MyISAM表中每個(gè)表被存在分離的文件中。InnoDB 表可以是任何大小,即使在文件尺寸被限制為2GB的操作系統(tǒng)上。
許多需要高性能的大型數(shù)據(jù)庫站點(diǎn)上使用了InnoDB引擎。著名的Internet新聞?wù)军c(diǎn)Slashdot.org運(yùn)行在InnoDB上。 Mytrix, Inc.在InnoDB上存儲超過1TB的數(shù)據(jù),還有一些其它站點(diǎn)在InnoDB上處理平均每秒800次插入/更新的負(fù)荷。
4.2.1 InnoDB特性
4.2.1.1 InnoDB Properties
1) 支持事務(wù),ACID,外鍵。
2) Row level locks。
3) 支持不同的隔離級別。
4) 和MyISAM相比需要較多的內(nèi)存和磁盤空間。
5) 沒有鍵壓縮。
6) 數(shù)據(jù)和索引都緩存在內(nèi)存hash表中。
4.2.1.2 InnoDB Good For
1) 需要事務(wù)的應(yīng)用。
2) 高并發(fā)的應(yīng)用。
3) 自動恢復(fù)。
4) 較快速的基于主鍵的操作。
4.2.2 InnoDB優(yōu)化要點(diǎn)
1) 盡量使用short,integer的主鍵。
2) Load/Insert數(shù)據(jù)時(shí)按主鍵順序。如果數(shù)據(jù)沒有按主鍵排序,先排序然后再進(jìn)行數(shù)據(jù)庫操作。
3) 在Load數(shù)據(jù)是為設(shè)置SET UNIQUE_CHECKS=0,SET FOREIGN_KEY_CHECKS=0,可以避免外鍵和唯一性約束檢查的開銷。
4) 使用prefix keys。因?yàn)镮nnoDB沒有key壓縮功能。
4.2.3 InnoDB服務(wù)器端設(shè)定
innodb_buffer_pool_size:這是InnoDB最重要的設(shè)置,對InnoDB性能有決定性的影響。默認(rèn)的設(shè)置只有8M,所以默認(rèn)的數(shù)據(jù)庫設(shè)置下面InnoDB性能很差。在只有InnoDB存儲引擎的數(shù)據(jù)庫服務(wù)器上面,可以設(shè)置60-80%的內(nèi)存。更精確一點(diǎn),在內(nèi)存容量允許的情況下面設(shè)置比InnoDB tablespaces大10%的內(nèi)存大小。
innodb_data_file_path:指定表數(shù)據(jù)和索引存儲的空間,可以是一個(gè)或者多個(gè)文件。最后一個(gè)數(shù)據(jù)文件必須是自動擴(kuò)充的,也只有最后一個(gè)文件允許自動擴(kuò)充。這樣,當(dāng)空間用完后,自動擴(kuò)充數(shù)據(jù)文件就會自動增長(以8MB為單位)以容納額外的數(shù)據(jù)。例如: innodb_data_file_path=/disk1/ibdata1:900M;/disk2/ibdata2:50M:autoextend兩個(gè)數(shù)據(jù)文件放在不同的磁盤上。數(shù)據(jù)首先放在ibdata1中,當(dāng)達(dá)到900M以后,數(shù)據(jù)就放在ibdata2中。一旦達(dá)到50MB,ibdata2將以8MB為單位自動增長。如果磁盤滿了,需要在另外的磁盤上面增加一個(gè)數(shù)據(jù)文件。
innodb_autoextend_increment: 默認(rèn)是8M, 如果一次insert數(shù)據(jù)量比較多的話, 可以適當(dāng)增加.
innodb_data_home_dir:放置表空間數(shù)據(jù)的目錄,默認(rèn)在mysql的數(shù)據(jù)目錄,設(shè)置到和MySQL安裝文件不同的分區(qū)可以提高性能。
innodb_log_file_size:該參數(shù)決定了recovery speed。太大的話recovery就會比較慢,太小了影響查詢性能,一般取256M可以兼顧性能和recovery的速度
。
innodb_log_buffer_size:磁盤速度是很慢的,直接將log寫道磁盤會影響InnoDB的性能,該參數(shù)設(shè)定了log buffer的大小,一般4M。如果有大的blob操作,可以適當(dāng)增大。
innodb_flush_logs_at_trx_commit=2: 該參數(shù)設(shè)定了事務(wù)提交時(shí)內(nèi)存中l(wèi)og信息的處理。
1) =1時(shí),在每個(gè)事務(wù)提交時(shí),日志緩沖被寫到日志文件,對日志文件做到磁盤操作的刷新。Truly ACID。速度慢。
2) =2時(shí),在每個(gè)事務(wù)提交時(shí),日志緩沖被寫到文件,但不對日志文件做到磁盤操作的刷新。只有操作系統(tǒng)崩潰或掉電才會刪除最后一秒的事務(wù),不然不會丟失事務(wù)。
3) =0時(shí), 日志緩沖每秒一次地被寫到日志文件,并且對日志文件做到磁盤操作的刷新。任何mysqld進(jìn)程的崩潰會刪除崩潰前最后一秒的事務(wù)
innodb_file_per_table:可以存儲每個(gè)InnoDB表和它的索引在它自己的文件中。
transaction-isolation=READ-COMITTED: 如果應(yīng)用程序可以運(yùn)行在READ-COMMITED隔離級別,做此設(shè)定會有一定的性能提升。
innodb_flush_method: 設(shè)置InnoDB同步IO的方式:
1) Default – 使用fsync()。
2) O_SYNC 以sync模式打開文件,通常比較慢。
3) O_DIRECT,在Linux上使用Direct IO。可以顯著提高速度,特別是在RAID系統(tǒng)上。避免額外的數(shù)據(jù)復(fù)制和double buffering(mysql buffering 和OS buffering)。
innodb_thread_concurrency: InnoDB kernel最大的線程數(shù)。
1) 最少設(shè)置為(num_disks+num_cpus)*2。
2) 可以通過設(shè)置成1000來禁止這個(gè)限制
5. 緩存
緩存有很多種,為應(yīng)用程序加上適當(dāng)?shù)木彺娌呗詴@著提高應(yīng)用程序的性能。由于應(yīng)用緩存是一個(gè)比較大的話題,所以這一部分還需要進(jìn)一步調(diào)研。
6. Reference
1) http://www.mysqlperformanceblog.com/
2) Advanced MySQL Performance Optimization, Peter Zaitsev, Tobias Asplund, MySQL Users Conference 2005
3) Improving MySQL Server Performance with Intel C++ Compiler,Peter Zaitsev,Linux World 2005
4) MySQL Performance Optimization, Peter Zaitsev, Percona Ltd, OPEN SOURCE DATABASE CONFERENCE 2006
5) MySQL Server Settings Tuning, Peter Zaitsev, co-founder, Percona Ltd, 2007
6) MySQL Reference Manual