《如何高效快速地優(yōu)化MySQL、SQL語(yǔ)句(附源碼)》要點(diǎn):
本文介紹了如何高效快速地優(yōu)化MySQL、SQL語(yǔ)句(附源碼),希望對(duì)您有用。如果有疑問(wèn),可以聯(lián)系我們。
作者介紹
優(yōu)化SQL,是DBA常見(jiàn)的工作之一.如何高效、快速地優(yōu)化一條語(yǔ)句,是每個(gè)DBA經(jīng)常要面對(duì)的一個(gè)問(wèn)題.在日常的優(yōu)化工作中,我發(fā)現(xiàn)有很多操作是在優(yōu)化過(guò)程中必不可少的步驟.然而這些步驟重復(fù)性的執(zhí)行,又會(huì)耗費(fèi)DBA很多精力.于是萌發(fā)了自己編寫(xiě)小工具,提高優(yōu)化效率的想法.
那選擇何種語(yǔ)言來(lái)開(kāi)發(fā)工具呢?
對(duì)于一名DBA來(lái)說(shuō),掌握一門(mén)語(yǔ)言配合自己的工作是非常必要的.相對(duì)于shell的簡(jiǎn)單、perl的飄逸,Python是一種嚴(yán)謹(jǐn)?shù)母呒?jí)語(yǔ)言.其具備上手快、語(yǔ)法簡(jiǎn)單、擴(kuò)展豐富、跨平臺(tái)等多種優(yōu)點(diǎn).很多人把它稱(chēng)為一種“膠水”語(yǔ)言,通過(guò)大量豐富的類(lèi)庫(kù)、模塊,可以快速搭建出自己需要的工具.
于是乎,這個(gè)小工具就成了我學(xué)習(xí)Python的第一個(gè)作業(yè),我把它稱(chēng)之為“MySQL語(yǔ)句優(yōu)化輔助工具”.而且從此以后,我深深?lèi)?ài)上了Python,并開(kāi)發(fā)了很多數(shù)據(jù)庫(kù)相關(guān)的小工具,以后有機(jī)會(huì)介紹給大家.
下面在介紹工具使用之前,首先說(shuō)明下MySQL中語(yǔ)句優(yōu)化常用的手段、方法及需要注意的問(wèn)題.這也是大家在日常手工優(yōu)化中,需要了解掌握的.
執(zhí)行計(jì)劃是語(yǔ)句優(yōu)化的主要切入點(diǎn),通過(guò)執(zhí)行計(jì)劃的判讀了解語(yǔ)句的執(zhí)行過(guò)程.在執(zhí)行計(jì)劃生成方面,MySQL與Oracle明顯不同,它不會(huì)緩存執(zhí)行計(jì)劃,每次都執(zhí)行“硬解析”.查看執(zhí)行計(jì)劃的方法,就是使用EXPLAIN命令.
當(dāng)在一個(gè)Select語(yǔ)句前使用關(guān)鍵字EXPLAIN時(shí),MySQL會(huì)解釋了即將如何運(yùn)行該Select語(yǔ)句,它顯示了表如何連接、連接的順序等信息.
當(dāng)使用EXTENDED關(guān)鍵字時(shí),EXPLAIN產(chǎn)生附加信息,可以用SHOW WARNINGS瀏覽.該信息顯示優(yōu)化器限定SELECT語(yǔ)句中的表和列名,重寫(xiě)并且執(zhí)行優(yōu)化規(guī)則后SELECT語(yǔ)句是什么樣子,并且還可能包括優(yōu)化過(guò)程的其它注解.在MySQL5.0及更新的版本里都可以使用,在MySQL5.1里它有額外增加了一個(gè)過(guò)濾列(filtered).
顯示的是查詢(xún)要訪問(wèn)的數(shù)據(jù)分片——如果有分片的話.它只能在MySQL5.1及更新的版本里使用.
另一個(gè)格式顯示執(zhí)行計(jì)劃.可以看到諸如表間關(guān)聯(lián)方式等信息.
下面說(shuō)明一下EXPLAIN輸出的字段含義,并由此學(xué)習(xí)如何判斷一個(gè)執(zhí)行計(jì)劃.
MySQL選定的執(zhí)行計(jì)劃中查詢(xún)的序列號(hào).如果語(yǔ)句里沒(méi)有子查詢(xún)等情況,那么整個(gè)輸出里就只有一個(gè)SELECT,這樣一來(lái)每一行在這個(gè)列上都會(huì)顯示一個(gè)1.如果語(yǔ)句中使用了子查詢(xún)、集合操作、臨時(shí)表等情況,會(huì)給ID列帶來(lái)很大的復(fù)雜性.如上例中,WHERE部分使用了子查詢(xún),其id=2的行表示一個(gè)關(guān)聯(lián)子查詢(xún).
語(yǔ)句所使用的查詢(xún)類(lèi)型.是簡(jiǎn)單SELECT還是復(fù)雜SELECT(如果是后者,顯示它屬于哪一種復(fù)雜類(lèi)型).常用有以下幾種標(biāo)記類(lèi)型.
子查詢(xún)內(nèi)層的第一個(gè)SELECT,依賴(lài)于外部查詢(xún)的結(jié)果集.
子查詢(xún)中的UNION,且為UNION中從第二個(gè)SELECT開(kāi)始的后面所有SELECT,同樣依賴(lài)于外部查詢(xún)的結(jié)果集.
子查詢(xún)中的最外層查詢(xún),注意并不是主鍵查詢(xún).
除子查詢(xún)或UNION之外的其他查詢(xún).
子查詢(xún)內(nèi)層查詢(xún)的第一個(gè)SELECT,結(jié)果不依賴(lài)于外部查詢(xún)結(jié)果集.
結(jié)果集無(wú)法緩存的子查詢(xún).
UNION語(yǔ)句中的第二個(gè)SELECT開(kāi)始后面的所有SELECT,第一個(gè)SELECT為PRIMARY.
UNION中的合并結(jié)果.從UNION臨時(shí)表獲取結(jié)果的SELECT.
衍生表查詢(xún)(FROM子句中的子查詢(xún)).MySQL會(huì)遞歸執(zhí)行這些子查詢(xún),把結(jié)果放在臨時(shí)表里.在內(nèi)部,服務(wù)器就把當(dāng)做一個(gè)”衍生表”那樣來(lái)引用,因?yàn)榕R時(shí)表就是源自子查詢(xún).
這一步所訪問(wèn)的數(shù)據(jù)庫(kù)中表的名稱(chēng)或者SQL語(yǔ)句指定的一個(gè)別名表.這個(gè)值可能是表名、表的別名或者一個(gè)為查詢(xún)產(chǎn)生的臨時(shí)表的標(biāo)識(shí)符,如派生表、子查詢(xún)或集合.
表的訪問(wèn)方式.以下列出了各種不同類(lèi)型的表連接,依次是從最好的到最差的.
系統(tǒng)表,表只有一行記錄.這是const表連接類(lèi)型的一個(gè)特例.
讀常量,最多只有一行匹配的記錄.由于只有一行記錄,優(yōu)化程序里該行記錄的字段值可以被當(dāng)作是一個(gè)恒定值.const用于在和PRIMARY KEY或UNIQUE索引中有固定值比較的情形.
最多只會(huì)有一條匹配結(jié)果,一般是通過(guò)主鍵或唯一鍵索引來(lái)訪問(wèn).從該表中會(huì)有一行記錄被讀取出來(lái)以和從前一個(gè)表中讀取出來(lái)的記錄做聯(lián)合.與const類(lèi)型不同的是,這是最好的連接類(lèi)型.它用在索引所有部分都用于做連接并且這個(gè)索引是一個(gè)PRIMARY KEY或UNIQUE類(lèi)型.eq_ref可以用于在進(jìn)行”=”做比較時(shí)檢索字段.比較的值可以是固定值或者是表達(dá)式,表達(dá)示中可以使用表里的字段,它們?cè)谧x表之前已經(jīng)準(zhǔn)備好了.
JOIN語(yǔ)句中驅(qū)動(dòng)表索引引用的查詢(xún).該表中所有符合檢索值的記錄都會(huì)被取出來(lái)和從上一個(gè)表中取出來(lái)的記錄作聯(lián)合.ref用于連接程序使用鍵的最左前綴或者是該鍵不是PRIMARY KEY或UNIQUE索引(換句話說(shuō),就是連接程序無(wú)法根據(jù)鍵值只取得一條記錄)的情況.當(dāng)根據(jù)鍵值只查詢(xún)到少數(shù)幾條匹配的記錄時(shí),這就是一個(gè)不錯(cuò)的連接類(lèi)型.ref還可以用于檢索字段使用”=”操作符來(lái)比較的時(shí)候.
與ref的唯一區(qū)別就是在使用索引引用的查詢(xún)之外再增加一個(gè)空值的查詢(xún).這種連接類(lèi)型類(lèi)似ref,不同的是MySQL會(huì)在檢索的時(shí)候額外的搜索包含NULL值的記錄.這種連接類(lèi)型的優(yōu)化是從MySQL 4.1.1開(kāi)始的,它經(jīng)常用于子查詢(xún).
查詢(xún)中同時(shí)使用兩個(gè)(或更多)索引,然后對(duì)索引結(jié)果進(jìn)行合并(merge),再讀取表數(shù)據(jù).這種連接類(lèi)型意味著使用了Index Merge優(yōu)化方法.
子查詢(xún)中的返回結(jié)果字段組合是主鍵或唯一約束.
子查詢(xún)中的返回結(jié)果字段組合是一個(gè)索引(或索引組合),但不是一個(gè)主鍵或唯一索引.這種連接類(lèi)型類(lèi)似unique_subquery.它用子查詢(xún)來(lái)代替IN,不過(guò)它用于在子查詢(xún)中沒(méi)有唯一索引的情況下.
索引范圍掃描.只有在給定范圍的記錄才會(huì)被取出來(lái),利用索引來(lái)取得一條記錄.
全索引掃描.連接類(lèi)型跟ALL一樣,不同的是它只掃描索引樹(shù).它通常會(huì)比ALL快點(diǎn),因?yàn)樗饕募ǔ1葦?shù)據(jù)文件小.MySQL在查詢(xún)的字段知識(shí)單獨(dú)的索引的一部分的情況下使用這種連接類(lèi)型.
全文索引掃描.
全表掃描.
該字段是指MySQL在搜索表記錄時(shí)可能使用哪個(gè)索引.如果沒(méi)有任何索引可以使用,就會(huì)顯示為null.
查詢(xún)優(yōu)化器從possible_keys中所選擇使用的索引.key字段顯示了MySQL實(shí)際上要用的索引.當(dāng)沒(méi)有任何索引被用到的時(shí)候,這個(gè)字段的值就是NULL.
被選中使用索引的索引鍵長(zhǎng)度.key_len字段顯示了MySQL使用索引的長(zhǎng)度.當(dāng)key字段的值為NULL時(shí),索引的長(zhǎng)度就是NULL.
列出是通過(guò)常量,還是某個(gè)表的某個(gè)字段來(lái)過(guò)濾的.ref字段顯示了哪些字段或者常量被用來(lái)和key配合從表中查詢(xún)記錄出來(lái).
該字段顯示了查詢(xún)優(yōu)化器通過(guò)系統(tǒng)收集的統(tǒng)計(jì)信息估算出來(lái)的結(jié)果集記錄條數(shù).
該字段顯示了查詢(xún)中MySQL的附加信息.
這個(gè)列式在MySQL5.1里新加進(jìn)去的,當(dāng)使用EXPLAIN EXTENDED時(shí)才會(huì)出現(xiàn).它顯示的是針對(duì)表里符合某個(gè)條件(WHERE子句或聯(lián)接條件)的記錄數(shù)的百分比所作的一個(gè)悲觀估算.
EXPLAIN除了可以顯示執(zhí)行計(jì)劃外,還可以顯示SQL改寫(xiě).所謂SQL改寫(xiě),是指MySQL在對(duì)SQL語(yǔ)句進(jìn)行優(yōu)化前,會(huì)基于一些原則進(jìn)行語(yǔ)句的改寫(xiě),以方便后面的優(yōu)化器進(jìn)行優(yōu)化生成更優(yōu)的執(zhí)行計(jì)劃.該功能是通過(guò)EXPLAIN EXTENDED+SHOW WARNINGS配合使用.下面通過(guò)示例說(shuō)明一下.
從上面示例中,可看到原有語(yǔ)句中的IN子查詢(xún)被改寫(xiě)成為表間關(guān)聯(lián)的方式.
查看統(tǒng)計(jì)信息也是優(yōu)化語(yǔ)句中必不可少的一步.通過(guò)統(tǒng)計(jì)信息可以快速了解對(duì)象的存儲(chǔ)特征如何.下面說(shuō)明主要的兩類(lèi)統(tǒng)計(jì)信息——表、索引.
系統(tǒng)參數(shù)很多,下面介紹幾個(gè).
排序區(qū)大小.其大小直接影響排序使用的算法.如果系統(tǒng)中排序都比較大、內(nèi)存充足且并發(fā)量不是很大的情況,可以適當(dāng)增加此參數(shù).這個(gè)參數(shù)是針對(duì)單個(gè)Thead的.
Join操作使用內(nèi)存區(qū)域大小.只有當(dāng)Join是ALL、index、range或index_merge時(shí)使用到Join Buffer.如果join語(yǔ)句較多,可以適當(dāng)增大join_buffer_size.需要注意到是,這個(gè)值針對(duì)單個(gè)Thread.每個(gè)Thread都會(huì)自己創(chuàng)建獨(dú)立的Buffer,而不是整個(gè)系統(tǒng)共享的Buffer,不要設(shè)置過(guò)大而造成系統(tǒng)內(nèi)存不足.
如果內(nèi)存內(nèi)的臨時(shí)表超過(guò)該值,MySQL自動(dòng)將它轉(zhuǎn)換為硬盤(pán)上的MyISAM表.如果執(zhí)行許多高級(jí)GROUP BY查詢(xún)并且有大量?jī)?nèi)存,則可以增加tmp_table_size的值.
讀查詢(xún)操作所能使用的緩沖區(qū)大小.這個(gè)參數(shù)是針對(duì)單個(gè)Thead的.
在MySQL中,還有一些參數(shù)是可以用來(lái)控制優(yōu)化器行為的.
這個(gè)參數(shù)控制優(yōu)化器在窮舉執(zhí)行計(jì)劃時(shí)的限度.如果查詢(xún)長(zhǎng)時(shí)間處于”statistics”狀態(tài),可以考慮調(diào)低此參數(shù).
默認(rèn)是打開(kāi)的,這讓優(yōu)化器會(huì)根據(jù)需要掃描的行數(shù)來(lái)決定是否跳過(guò)某些執(zhí)行計(jì)劃.
這個(gè)變量包含了一些開(kāi)啟/關(guān)閉優(yōu)化器特性的標(biāo)志位.
示例 — 干預(yù)優(yōu)化器行為(ICP特性)
默認(rèn)情況下,ICP特性是開(kāi)啟的.查看一下優(yōu)化器行為.
基于二級(jí)索引的過(guò)濾查詢(xún),使用了ICP特性,從Extra中的”Using index condition”可見(jiàn).如果通過(guò)優(yōu)化器開(kāi)關(guān),干預(yù)優(yōu)化器行為,又會(huì)如何呢?
從Extra可見(jiàn),ICP特性已經(jīng)禁用.
MySQL中也內(nèi)置了一些狀態(tài),通過(guò)這些狀態(tài)變量也可反映出語(yǔ)句執(zhí)行的一些情況,方便定位問(wèn)題.手工執(zhí)行的話,可以在執(zhí)行語(yǔ)句的前后分別執(zhí)行SHOW STATUS命令,查看狀態(tài)的變化.當(dāng)然,因狀態(tài)變量很多,對(duì)比起來(lái)不太方便,后面我介紹的小工具,可以解決這個(gè)問(wèn)題.
狀態(tài)變量很多,這里介紹幾個(gè).
排序算法已經(jīng)執(zhí)行的合并的數(shù)量.如果這個(gè)變量值較大,應(yīng)考慮增加sort_buffer_size系統(tǒng)變量的值.
在范圍內(nèi)執(zhí)行的排序的數(shù)量.
已經(jīng)排序的行數(shù).
通過(guò)掃描表完成的排序的數(shù)量.
索引中第一條被讀的次數(shù).讀取索引頭的次數(shù),如果這個(gè)值很高,說(shuō)明全索引掃描很多.
根據(jù)鍵讀一行的請(qǐng)求數(shù).如果較高,說(shuō)明查詢(xún)和表的索引正確.
按照鍵順序讀下一行的請(qǐng)求數(shù).如果你用范圍約束或如果執(zhí)行索引掃描來(lái)查詢(xún)索引列,該值增加.
按照鍵順序讀前一行的請(qǐng)求數(shù).
根據(jù)固定位置讀一行的請(qǐng)求數(shù).如果執(zhí)行大量查詢(xún)并需要對(duì)結(jié)果進(jìn)行排序該值較高.則可能使用了大量需要MySQL掃描整個(gè)表的查詢(xún)或連接沒(méi)有正確使用鍵.
在數(shù)據(jù)文件中讀下一行的請(qǐng)求數(shù).如果正進(jìn)行大量的表掃描,該值較高.通常說(shuō)明表索引不正確或?qū)懭氲牟樵?xún)沒(méi)有利用索引.
MySQL的Query Profiler是一個(gè)使用非常方便的Query診斷分析工具,通過(guò)該工具可以獲取一條Query在整個(gè)執(zhí)行過(guò)程中多種資源的消耗情況,如CPU、IO、IPC、SWAP等,以及發(fā)生的PAGE FAULTS、CONTEXT SWITCHE等,同時(shí)還能得到該Query執(zhí)行過(guò)程中的MySQL所調(diào)用的各個(gè)函數(shù)在源文件中的位置.
mysql> set profiling=1;
默認(rèn)情況下profiling的值為0表示MySQL SQL Profiler處于OFF狀態(tài),開(kāi)啟SQL性能分析器后profiling的值為1.
使用”show profile”命令獲取當(dāng)前系統(tǒng)中保存的多個(gè)Query的profile的概要信息.
+———-+————+———————–+
| Query_ID | Duration?? | Query?????????????????? |
+———-+————+———————–+
|??????? 1 | 0.00039300 | select count(*) from t1 |
+———-+————+———————–+
在獲取概要信息之后,就可以根據(jù)概要信息的Query_ID來(lái)獲取某個(gè)Query的執(zhí)行過(guò)程中詳細(xì)的profile信息.
mysql> show profile cpu,block io for query 1;
前面談到了多種手段,對(duì)于SQL語(yǔ)句的調(diào)優(yōu)都有所幫助.通過(guò)下面這個(gè)小工具,可以自動(dòng)調(diào)用命令將上面這些內(nèi)容一次性推給DBA,大大加速優(yōu)化的過(guò)程.
-p? 指定配置文件名稱(chēng)
-s? 指定SQL語(yǔ)句
共分兩節(jié)信息,分別是[database]描述數(shù)據(jù)庫(kù)連接信息,[option]運(yùn)行配置信息.
db_user???? = testuser
db_pwd????? = testpwd
db_name???? = test
sql_plan??? = ON //是否顯示執(zhí)行計(jì)劃
obj_stat??? = ON //是否顯示相關(guān)對(duì)象(表、索引)統(tǒng)計(jì)信息
ses_status? = ON //是否顯示運(yùn)行前后狀態(tài)信息(激活后會(huì)真實(shí)執(zhí)行SQL)
sql_profile = ON?? //是否顯示PROFILE跟蹤信息(激活后會(huì)真實(shí)執(zhí)行SQL)
包含運(yùn)行數(shù)據(jù)庫(kù)的地址信息及數(shù)據(jù)版本信息.
用戶(hù)執(zhí)行輸入的SQL,這部分主要是為了后續(xù)對(duì)比SQL改寫(xiě)時(shí)使用.語(yǔ)句顯示時(shí)使用了格式化.
腳本選擇顯示了部分與SQL性能相關(guān)的參數(shù).這部分是寫(xiě)死在代碼中的,如需擴(kuò)展需要修改腳本.
下面是和優(yōu)化器相關(guān)的一些參數(shù),通過(guò)調(diào)整這些參數(shù)可以人為干預(yù)優(yōu)化器行為.
就是調(diào)用explain extended的輸出結(jié)果.如果結(jié)果過(guò)長(zhǎng),可能出現(xiàn)顯示串行的問(wèn)題(暫時(shí)未解決).
通過(guò)這里可判斷優(yōu)化器是否對(duì)SQL進(jìn)行了某種優(yōu)化(例如子查詢(xún)的處理).
統(tǒng)計(jì)信息
在SQL語(yǔ)句中所有涉及到的表及其索引的統(tǒng)計(jì)信息都會(huì)在這里顯示出來(lái).
在會(huì)話級(jí)別對(duì)比了執(zhí)行前后的狀態(tài)(SHOW STATUS),并將出現(xiàn)變化的部分顯示出來(lái).需要注意的是,因?yàn)槭占癄顟B(tài)數(shù)據(jù)是采用SELECT方式,會(huì)造成個(gè)別指標(biāo)的誤差(例如Com_select).
調(diào)用SHOW PROFILE得到的詳細(xì)信息.
根據(jù)PROFILE的資源消耗情況,顯示不同階段消耗對(duì)比情況(TOP N),直觀顯示”瓶頸”所在.
轉(zhuǎn)載請(qǐng)注明本頁(yè)網(wǎng)址:
http://www.fzlkiss.com/jiaocheng/4413.html