這一部分提供了如何選擇數(shù)據(jù)類(lèi)型來(lái)幫助提高查詢(xún)運(yùn)行速度的一些指導(dǎo):
在可以使用短數(shù)據(jù)列的時(shí)候就不要用長(zhǎng)的。如果你有一個(gè)固定長(zhǎng)度的CHAR數(shù)據(jù)列,那么就不要讓它的長(zhǎng)度超出實(shí)際需要。如果你在數(shù)據(jù)列中存儲(chǔ)的最長(zhǎng)的值有40個(gè)字符,就不要定義成CHAR(255),而應(yīng)該定義成CHAR(40)。如果你能夠用MEDIUMINT代替BIGINT,那么你的數(shù)據(jù)表就小一些(磁盤(pán)I/O少一些),在計(jì)算過(guò)程中,值的處理速度也快一些。如果數(shù)據(jù)列被索引了,那么使用較短的值帶來(lái)的性能提高更加顯著。不僅索引可以提高查詢(xún)速度,而且短的索引值也比長(zhǎng)的索引值處理起來(lái)要快一些。
如果你可以選擇數(shù)據(jù)行的存儲(chǔ)格式,那么應(yīng)該使用最適合存儲(chǔ)引擎的那種。對(duì)于MyISAM數(shù)據(jù)表,最好使用固定長(zhǎng)度的數(shù)據(jù)列代替可變長(zhǎng)度的數(shù)據(jù)列。例如,讓所有的字符列用CHAR類(lèi)型代替VARCHAR類(lèi)型。權(quán)衡得失,我們會(huì)發(fā)現(xiàn)數(shù)據(jù)表使用了更多的磁盤(pán)空間,但是如果你能夠提供額外的空間,那么固定長(zhǎng)度的數(shù)據(jù)行被處理的速度比可變長(zhǎng)度的數(shù)據(jù)行要快一些。對(duì)于那些被頻繁修改的表來(lái)說(shuō),這一點(diǎn)尤其突出,因?yàn)樵谀切┣闆r下,性能更容易受到磁盤(pán)碎片的影響。
? 在使用可變長(zhǎng)度的數(shù)據(jù)行的時(shí)候,由于記錄長(zhǎng)度不同,在多次執(zhí)行刪除和更新操作之后,數(shù)據(jù)表的碎片要多一些。你必須使用OPTIMIZE TABLE來(lái)定期維護(hù)其性能。固定長(zhǎng)度的數(shù)據(jù)行沒(méi)有這個(gè)問(wèn)題。
? 如果出現(xiàn)數(shù)據(jù)表崩潰的情況,那么數(shù)據(jù)行長(zhǎng)度固定的表更容易重新構(gòu)造。使用固定長(zhǎng)度數(shù)據(jù)行的時(shí)候,每個(gè)記錄的開(kāi)始位置都可以被檢測(cè)到,因?yàn)檫@些位置都是固定記錄長(zhǎng)度的倍數(shù),但是使用可變長(zhǎng)度數(shù)據(jù)行的時(shí)候就不一定了。這不是與查詢(xún)處理的性能相關(guān)的問(wèn)題,但是它一定能夠加快數(shù)據(jù)表的修復(fù)速度。
盡管把MyISAM數(shù)據(jù)表轉(zhuǎn)換成使用固定長(zhǎng)度的數(shù)據(jù)列可以提高性能,但是你首先需要考慮下面一些問(wèn)題:
? 固定長(zhǎng)度的數(shù)據(jù)列速度較快,但是占用的空間也較大。CHAR(n)列的每個(gè)值(即使是空值)通常占n個(gè)字符,這是因?yàn)榘阉鎯?chǔ)到數(shù)據(jù)表中的時(shí)候,會(huì)在值的后面添加空格。VARCHAR(n)列占有的空間較小,因?yàn)橹恍枰峙浔匾淖址麄€(gè)數(shù)用于存儲(chǔ)值,加上一兩個(gè)字節(jié)來(lái)存儲(chǔ)值的長(zhǎng)度。因此,在CHAR和VARCHAR列之間進(jìn)行選擇的時(shí)候,實(shí)際上是時(shí)間與空間的對(duì)比。如果速度是主要的考慮因素,那么就使用CHAR數(shù)據(jù)列獲取固定長(zhǎng)度列的性能優(yōu)勢(shì)。如果空間很重要,那么就使用VARCHAR數(shù)據(jù)列。總而言之,你可以認(rèn)為固定長(zhǎng)度的數(shù)據(jù)行可以提高性能,雖然它占用了更大的空間。但是對(duì)于某些特殊的應(yīng)用程序,你可能希望使用兩種方式來(lái)實(shí)現(xiàn)某個(gè)數(shù)據(jù)表,然后運(yùn)行測(cè)試來(lái)決定哪種情況符合應(yīng)用程序的需求。
? 即使愿意使用固定長(zhǎng)度類(lèi)型,有時(shí)候你也沒(méi)有辦法使用。例如,長(zhǎng)于255個(gè)字符的字符串就無(wú)法使用固定長(zhǎng)度類(lèi)型。
MEMORY數(shù)據(jù)表目前都使用固定長(zhǎng)度的數(shù)據(jù)行存儲(chǔ),因此無(wú)論使用CHAR或VARCHAR列都沒(méi)有關(guān)系。兩者都是作為CHAR類(lèi)型處理的。
對(duì)于InnoDB數(shù)據(jù)表,內(nèi)部的行存儲(chǔ)格式?jīng)]有區(qū)分固定長(zhǎng)度和可變長(zhǎng)度列(所有數(shù)據(jù)行都使用指向數(shù)據(jù)列值的頭指針),因此在本質(zhì)上,使用固定長(zhǎng)度的CHAR列不一定比使用可變長(zhǎng)度VARCHAR列簡(jiǎn)單。因而,主要的性能因素是數(shù)據(jù)行使用的存儲(chǔ)總量。由于CHAR平均占用的空間多于VARCHAR,因此使用VARCHAR來(lái)最小化需要處理的數(shù)據(jù)行的存儲(chǔ)總量和磁盤(pán)I/O是比較好的。
對(duì)于BDB數(shù)據(jù)表,無(wú)論使用固定長(zhǎng)度或可變長(zhǎng)度的數(shù)據(jù)列,差別都不大。兩種方法你都可用試一下,運(yùn)行一些實(shí)驗(yàn)測(cè)試來(lái)檢測(cè)是否存在明顯的差別。
把數(shù)據(jù)列定義成不能為空(NOT NULL)。這會(huì)使處理速度更快,需要的存儲(chǔ)更少。它有時(shí)候還簡(jiǎn)化了查詢(xún),因?yàn)樵谀承┣闆r下你不需要檢查值的NULL屬性。
考慮使用ENUM數(shù)據(jù)列。如果你擁有的某個(gè)數(shù)據(jù)列的基數(shù)很低(包含的不同的值數(shù)量有限),那么可以考慮把它轉(zhuǎn)換為ENUM列。ENUM值可以被更快地處理,因?yàn)樗鼈冊(cè)趦?nèi)部表現(xiàn)為數(shù)值。
使用PROCEDURE ANALYSE()。運(yùn)行PROCEDURE ANALYSE()可以看到數(shù)據(jù)表中列的情況:
| SELECT * FROM tbl_name PROCEDURE ANALYSE(); SELECT * FROM tbl_name PROCEDURE ANALYSE(16,256); |
根據(jù)的PROCEDURE ANALYSE()輸出信息,你可能發(fā)現(xiàn),可以修改自己的數(shù)據(jù)表來(lái)利用那些效率更高的數(shù)據(jù)類(lèi)型。如果你決定改變某個(gè)數(shù)據(jù)列的類(lèi)型,需要使用ALTER TABLE語(yǔ)句。
使用OPTIMIZE TABLE來(lái)優(yōu)化那些受到碎片影響的數(shù)據(jù)表。被大量修改的數(shù)據(jù)表,特別是那些包含可變長(zhǎng)度數(shù)據(jù)列的表,容易遭受碎片的影響。碎片很糟糕,因?yàn)樗鼤?huì)導(dǎo)致用于存儲(chǔ)數(shù)據(jù)表的磁盤(pán)塊形成無(wú)用空間(空洞)。隨著時(shí)間的推移,為了得到有效的數(shù)據(jù)行,你必須讀取更多的塊,性能就會(huì)降低。這會(huì)出現(xiàn)在任何可變長(zhǎng)度的數(shù)據(jù)行上,但是對(duì)于BLOB或TEXT數(shù)據(jù)列尤其突出,因?yàn)樗鼈兊拈L(zhǎng)度差異太大了。在正常情況下使用OPTIMIZE TABLE會(huì)防止數(shù)據(jù)表的性能降低。OPTIMIZE TABLE可以用于MyISAM和BDB數(shù)據(jù)表,但是defragments只能用于MyISAM數(shù)據(jù)表。任何存儲(chǔ)引擎中的碎片整理方法都是用mysqldump來(lái)轉(zhuǎn)儲(chǔ)(dump)數(shù)據(jù)表,接著使用轉(zhuǎn)儲(chǔ)的文件刪除并重新建立那些數(shù)據(jù)表:
| % mysqldump --opt db_name tbl_name > dump.sql % mysql db_name < dump.sql |
高效率地載入數(shù)據(jù)
在大多數(shù)情況下,你所關(guān)注的是SELECT查詢(xún)的優(yōu)化,因?yàn)镾ELECT查詢(xún)是最常見(jiàn)的查詢(xún)類(lèi)型,而且如何優(yōu)化它們又不是太簡(jiǎn)單。與此形成對(duì)比,把數(shù)據(jù)載入數(shù)據(jù)庫(kù)的操作就相對(duì)直接了。然而,你仍然可以利用某些策略來(lái)改善數(shù)據(jù)載入操作的效率。基本的原理如下所示:
? 批量載入比單行載入的效率高,因?yàn)樵诿織l記錄被載入后,鍵緩存(key cache)不用刷新(flush);可以在這批記錄的末尾刷新鍵緩存。鍵緩存刷新的頻率減少得越多,數(shù)據(jù)載入的速度就越快。
? 沒(méi)有索引的數(shù)據(jù)表的載入速度比有索引的要快一些。如果存在索引,不但要把記錄添加到數(shù)據(jù)文件中,還必須修改索引來(lái)反映新增的記錄。
? 較短的SQL語(yǔ)句比較長(zhǎng)的SQL語(yǔ)句快,因?yàn)樗鼈兯婕暗椒?wù)器端分析過(guò)程較少,同時(shí)通過(guò)網(wǎng)絡(luò)把它們從客戶(hù)端發(fā)送到服務(wù)器上的速度也更快。
其中有些因素看起來(lái)是次要的(尤其是最后一個(gè)),但是如果你載入的數(shù)據(jù)很多,那么即使很小的效率差異也會(huì)導(dǎo)致一定的性能差別。我們可以從前面的一般原理得出幾條如何快速載入數(shù)據(jù)的實(shí)踐結(jié)論:
? LOAD DATA(所有形式的)比INSERT效率高,因?yàn)樗桥枯d入數(shù)據(jù)行的。服務(wù)器只需要分析和解釋一條語(yǔ)句,而不是多條語(yǔ)句。同樣,索引只需要在所有的數(shù)據(jù)行被處理過(guò)之后才刷新,而不是每行刷新一次。
? 不帶LOCAL的LOAD DATA比帶有LOCAL的LOAD DATA的速度要快。不帶LOCAL的時(shí)候,文件必須位于服務(wù)器上,而且你必須擁有FILE權(quán)限,但是服務(wù)器卻可以直接從磁盤(pán)上讀取文件。使用LOAD DATA LOCAL的時(shí)候,客戶(hù)端讀取文件并通過(guò)網(wǎng)絡(luò)把它發(fā)送給服務(wù)器,速度慢一些。
? 如果你必須使用INSERT,那么試著使用在一個(gè)語(yǔ)句中指定多個(gè)數(shù)據(jù)行的形式:
| INSERT INTO tbl_name VALUES(...),(...),... ; |
| START TRANSACTION; INSERT INTO tbl_name ... ; INSERT INTO tbl_name ... ; INSERT INTO tbl_name ... ; COMMIT; |
| LOCK TABLES tbl_name WRITE; INSERT INTO tbl_name ... ; INSERT INTO tbl_name ... ; INSERT INTO tbl_name ... ; UNLOCK TABLES; |
無(wú)論采用哪種方法,你得到的好處都是相同的:索引在所有的語(yǔ)句都被執(zhí)行之后才刷新一次,而不是每個(gè)INSERT語(yǔ)句刷新一次索引。后面介紹了在自動(dòng)提交模式下或數(shù)據(jù)表沒(méi)有被鎖定的時(shí)候發(fā)生的情況。
? 對(duì)于MyISAM數(shù)據(jù)表,減少索引刷新的另外一個(gè)策略是使用DELAYED_KEY_WRITE表選項(xiàng)。使用這個(gè)選項(xiàng)的時(shí)候,數(shù)據(jù)行會(huì)像平常一樣立即寫(xiě)入數(shù)據(jù)文件中,但是鍵緩存只是偶爾刷新一次,而不是在每次插入操作之后都需要刷新。如果要在服務(wù)器上全面地使用延遲索引刷新,那么就需要使用--delay-key-write選項(xiàng)來(lái)啟動(dòng)mysqld。在這種情況下,每個(gè)數(shù)據(jù)表的索引塊寫(xiě)入操作都會(huì)被延遲,直到這些數(shù)據(jù)塊必須為其它的索引值提供空間、或者執(zhí)行了FLUSH TABLES命令、或者數(shù)據(jù)表被關(guān)閉的時(shí)候才執(zhí)行操作。
如果你選擇了對(duì)MyISAM數(shù)據(jù)表使用延遲鍵寫(xiě)入,那么不正常的服務(wù)器關(guān)閉可能會(huì)引起索引值的丟失。這不是致命的問(wèn)題,因?yàn)镸yISAM索引可以依據(jù)數(shù)據(jù)行來(lái)進(jìn)行修復(fù),但是如果想讓修復(fù)過(guò)程出現(xiàn),你就必須使用--myisam-recover=FORCE選項(xiàng)來(lái)啟動(dòng)服務(wù)器。這個(gè)選項(xiàng)會(huì)使服務(wù)器在打開(kāi)MyISAM數(shù)據(jù)表的時(shí)候檢查它們,如果有必要就自動(dòng)地修復(fù)它們。
對(duì)于復(fù)制(replication)從屬服務(wù)器,你可能希望使用--delay-key-write=ALL來(lái)延遲所有的MyISAM數(shù)據(jù)表索引的刷新,不管在主服務(wù)器上最初是如何建立它們的。
? 使用壓縮的客戶(hù)端/服務(wù)器協(xié)議來(lái)減少網(wǎng)絡(luò)上數(shù)據(jù)傳輸?shù)臄?shù)量。對(duì)于大多數(shù)MySQL客戶(hù)端來(lái)說(shuō),我們都可以使用--compress命令行選項(xiàng)來(lái)指定它。通常,這個(gè)選項(xiàng)只是在較慢的網(wǎng)絡(luò)上使用,這是因?yàn)閴嚎s操作會(huì)花費(fèi)大量的處理器時(shí)間。
? 讓MySQL替你插入默認(rèn)值。也就是說(shuō),無(wú)論如何都不要給INSERT語(yǔ)句中那些可以賦予默認(rèn)值的列指定值。平均起來(lái),你的語(yǔ)句更短,減少了通過(guò)網(wǎng)絡(luò)發(fā)送到服務(wù)器的字符數(shù)量。此外,由于語(yǔ)句包含的值較少,服務(wù)器執(zhí)行的分析和值轉(zhuǎn)換操作也較少。
? 對(duì)于MyISAM數(shù)據(jù)表,如果你必須把大量的數(shù)據(jù)載入一個(gè)新表,最好建立不帶索引的表,載入數(shù)據(jù),然后建立索引,這樣的工作次序的速度要快一些。一次性地建立索引比每行都更新索引的速度要快一些。對(duì)于已經(jīng)帶有索引的表,如果預(yù)先刪除或禁止索引,后來(lái)再重新建立或者激活索引,那么數(shù)據(jù)載入的速度也要快一些。這些策略不能應(yīng)用于InnoDB或BDB表,它們沒(méi)有對(duì)分離的索引建立過(guò)程進(jìn)行優(yōu)化。
如果你考慮使用刪除或禁止索引的策略,把數(shù)據(jù)載入MyISAM數(shù)據(jù)表,那么在評(píng)估獲得的優(yōu)勢(shì)的時(shí)候,就需要考慮整個(gè)環(huán)境。如果你把少量的數(shù)據(jù)載入大型的數(shù)據(jù)表中,那么在沒(méi)有任何特殊準(zhǔn)備工作的情況下,重新建立索引花費(fèi)的時(shí)間可能比載入數(shù)據(jù)的時(shí)間還要長(zhǎng)。
要?jiǎng)h除并且重新建立索引,需要使用DROP INDEX和CREATE INDEX,或者使用與索引相關(guān)的ALTER TABLE。禁止和激活索引有兩種辦法:
? 你可用使用ALTER TABLE的DISABLE KEYS和ENABLE KEYS形式:
| ALTER TABLE tbl_name DISABLE KEYS; ALTER TABLE tbl_name ENABLE KEYS; |
| % myisamchk --keys-used=0 tbl_name |
| % myisamchk --recover --quick --keys-used=n tbl_name |
| % myisamchk --description tbl_name |
新聞熱點(diǎn)
疑難解答
圖片精選