MySQL在大數(shù)據(jù)、高并發(fā)場(chǎng)景下的SQL語(yǔ)句優(yōu)化和"最佳實(shí)踐"
本文主要針對(duì)中小型應(yīng)用或網(wǎng)站,重點(diǎn)探討日常程序開(kāi)發(fā)中SQL語(yǔ)句的優(yōu)化問(wèn)題,所謂“大數(shù)據(jù)”、“高并發(fā)”僅針對(duì)中小型應(yīng)用而言,專業(yè)的數(shù)據(jù)庫(kù)運(yùn)維大神請(qǐng)無(wú)視。以下實(shí)踐為個(gè)人在實(shí)際開(kāi)發(fā)工作中,針對(duì)相對(duì)“大數(shù)據(jù)”和相對(duì)“高并發(fā)”場(chǎng)景的一些應(yīng)對(duì)策略,部分措施并沒(méi)有經(jīng)過(guò)嚴(yán)格的對(duì)比測(cè)試和原理分析,如有錯(cuò)漏歡迎各種批評(píng)指教。
減少查詢的影響結(jié)果集,避免出現(xiàn)全表掃描。
影響結(jié)果集是SQL優(yōu)化的核心。影響結(jié)果集不是查詢返回的記錄數(shù),而是查詢所掃描的結(jié)果數(shù)。通過(guò)Explain或Desc分析SQL,rows列的值即為影響結(jié)果集(還可以通過(guò)慢查詢?nèi)罩镜腞ows_examined后面的數(shù)字得到)。
以下是我常用的一些SQL優(yōu)化策略:
- 去掉不必要的查詢和搜索。其實(shí)在項(xiàng)目的實(shí)際應(yīng)用中,很多查詢條件是可有可無(wú)的,能從源頭上避免的多余功能盡量砍掉,這是最簡(jiǎn)單粗暴的解決方案。
- 合理使用索引和復(fù)合索引。建索引是SQL優(yōu)化中最有效的手段。查找、刪除、更新以及排序時(shí)常用的字段可以適當(dāng)建立索引。不過(guò)要注意,單條查詢不能同時(shí)使用多個(gè)索引,只能使用一個(gè)索引。查詢條件較多時(shí),可以使用多個(gè)字段合并的復(fù)合索引。切記,使用復(fù)合索引時(shí),查詢條件的字段順序需要與復(fù)合索引的字段順序保持一致。
- 謹(jǐn)慎使用not in等可能無(wú)法使用索引的條件。索引也不是什么時(shí)候都可以發(fā)揮作用的,當(dāng)出現(xiàn)"not in","!=","like '%xx%'","is null"等條件時(shí),索引是無(wú)效的。使用這些條件的時(shí)候,請(qǐng)放到能有效使用索引的條件的右邊。設(shè)計(jì)表結(jié)構(gòu)時(shí),個(gè)人建議盡可能用int類型代替varchar類型,int類型部分時(shí)候可以通過(guò)大于或小于代替"!="等條件,同時(shí)也方便滿足一些需要按類型排序的需求,至于可讀性的問(wèn)題,完善好數(shù)據(jù)庫(kù)設(shè)計(jì)文檔才是明智的選擇。同時(shí)建議把所有可能的字段設(shè)置為"not null",并設(shè)置默認(rèn)值,避免在where字句中出現(xiàn)"is null"的判斷。
- 不要在where子句中的“=”左邊進(jìn)行函數(shù)、算術(shù)運(yùn)算或其他表達(dá)式運(yùn)算,否則系統(tǒng)將無(wú)法正確使用索引。盡可能少用MySQL的函數(shù),類似Now()完全可以通過(guò)程序?qū)崿F(xiàn)并賦值,部分函數(shù)也可以通過(guò)適當(dāng)?shù)慕⑷哂嘧侄蝸?lái)間接替代。
- 在where條件中使用or,可能導(dǎo)致索引無(wú)效。可用 "union all" 或者 "union" (會(huì)過(guò)濾重復(fù)數(shù)據(jù),效率比前者低) 代替,或程序上直接分開(kāi)兩次獲取數(shù)據(jù)再合并,確保索引的有效利用。
- 不使用select * ,倒不是能提高查詢效率,主要是減少輸出的數(shù)據(jù)量,提高傳輸速度。
- 避免類型轉(zhuǎn)換,這里所說(shuō)的“類型轉(zhuǎn)換”是指where子句中出現(xiàn)字段的類型和傳入的參數(shù)類型不一致的時(shí)候發(fā)生的類型轉(zhuǎn)換。
- 分頁(yè)查詢的優(yōu)化。頁(yè)數(shù)比較多的情況下,如limit 10000,10 影響的結(jié)果集是10010行,查詢速度會(huì)比較慢。推薦的解決方案是:先只查詢主鍵select id from table where .. order by .. limit 10000,10(搜索條件和排序請(qǐng)建立索引),再通過(guò)主鍵去獲取數(shù)據(jù)。
- 統(tǒng)計(jì)相關(guān)的查詢。影響結(jié)果集往往巨大,且部分SQL語(yǔ)句本身已經(jīng)難以優(yōu)化。因此,應(yīng)避免在業(yè)務(wù)高峰期執(zhí)行統(tǒng)計(jì)相關(guān)的查詢,或者僅在從庫(kù)中執(zhí)行統(tǒng)計(jì)查詢。部分統(tǒng)計(jì)數(shù)據(jù),可以通過(guò)冗余的數(shù)據(jù)結(jié)構(gòu)保存,同時(shí)建議把數(shù)據(jù)先保存在內(nèi)存、緩存中(如redis),再按一定策略寫入數(shù)據(jù)庫(kù)。
不使用任何連表查詢,通過(guò)分庫(kù)和分表實(shí)現(xiàn)負(fù)載均衡。
隨著數(shù)據(jù)量的增加,連表操作往往會(huì)導(dǎo)致影響結(jié)果集大增,從SQL優(yōu)化的層面已經(jīng)解決不了問(wèn)題了。
此時(shí),分庫(kù)和分表是解決數(shù)據(jù)庫(kù)性能壓力的***選擇(具體分庫(kù)和分表的方案通常結(jié)合實(shí)際業(yè)務(wù)的應(yīng)用場(chǎng)景來(lái)確定,此處略過(guò))。這里重點(diǎn)談,如何更好的實(shí)現(xiàn)或者過(guò)渡到分庫(kù)、分表的分布式數(shù)據(jù)庫(kù)架構(gòu)。
核心點(diǎn)就是必須先去除數(shù)據(jù)表之間的關(guān)聯(lián),即不用外鍵,不使用任何連表查詢。為了確保不進(jìn)行連表操作,在設(shè)計(jì)數(shù)據(jù)庫(kù)表結(jié)構(gòu)的時(shí)候,就需要設(shè)計(jì)適度冗余的字段來(lái)達(dá)到不連表的目的。
對(duì)于一些操作日志、支付記錄等,設(shè)計(jì)一些記錄用戶信息的字段,個(gè)人認(rèn)為其實(shí)不能算冗余,畢竟用戶信息往往會(huì)更改,但是這種類似操作日志的表確實(shí)是需要記錄用戶操作時(shí)的信息,并且不需要在用戶更新信息時(shí)同步更新。
實(shí)際開(kāi)發(fā)中,為了實(shí)現(xiàn)不進(jìn)行連表而冗余的字段,往往是需要在原表更新數(shù)據(jù)的時(shí)候同步更新冗余字段的數(shù)據(jù)的,如果應(yīng)用層沒(méi)有對(duì)數(shù)據(jù)表操作做合理封裝,這往往是個(gè)棘手的問(wèn)題,也不方便維護(hù)。
當(dāng)然,現(xiàn)在主流的應(yīng)用框架,一般采用orm的方式處理數(shù)據(jù)表,所以問(wèn)題不大。相反,不連表事實(shí)上還可以提高開(kāi)發(fā)效率,比如通過(guò)用戶ID獲取用戶姓名操作,如果不連表就可以確保各個(gè)業(yè)務(wù)模塊都通過(guò)同樣的方式去獲取用戶姓名,調(diào)用同一個(gè)封裝好的方法,這樣,就能很方便的統(tǒng)一在應(yīng)用層加入緩存機(jī)制或添加統(tǒng)一的業(yè)務(wù)邏輯。
同時(shí)如果要對(duì)用戶表進(jìn)行分庫(kù)分表,通過(guò)應(yīng)用層程序就可以簡(jiǎn)單平滑的實(shí)現(xiàn)。
使用Innodb。
關(guān)于Innodb和Myisam對(duì)比,我就不多說(shuō)了。Myisam的表級(jí)鎖是致命問(wèn)題,考慮到MySQL已經(jīng)默認(rèn)使用Innodb作為數(shù)據(jù)庫(kù)引擎,個(gè)人建議大部分情況可以直接使用Innodb,其他引擎這里就不詳細(xì)討論了。
使用緩存。
1) 盡可能在程序上實(shí)現(xiàn)常用數(shù)據(jù)的緩存,目前主流的應(yīng)用框架應(yīng)該都能快速實(shí)現(xiàn)緩存的需求。如果在程序上沒(méi)有實(shí)現(xiàn)數(shù)據(jù)緩存,開(kāi)啟數(shù)據(jù)庫(kù)的query cache也是緩解數(shù)據(jù)庫(kù)壓力的方式之一,如果確認(rèn)使用,記得定時(shí)清理碎片flush query cache。
服務(wù)器相關(guān)優(yōu)化
MySQL服務(wù)配置以及分布式架構(gòu)的實(shí)現(xiàn),請(qǐng)根據(jù)實(shí)際應(yīng)用場(chǎng)景和業(yè)務(wù)需求定制,非本文重點(diǎn),不做深入探討。