读薄《高性能MySql》(四)查询性能优化
讀薄《高性能MySql》(一)MySql基本知識(shí)
讀薄《高性能MySql》(二)Scheme與數(shù)據(jù)優(yōu)化
讀薄《高性能MySql》(三)索引優(yōu)化
讀薄《高性能MySql》(四)查詢性能優(yōu)化
對(duì) MySql 進(jìn)行優(yōu)化,必須對(duì) Scheme,索引,查詢語(yǔ)句一同優(yōu)化。
通過(guò)前面的章節(jié)我們掌握了 Scheme 和 索引的優(yōu)化,最后我們來(lái)看一下查詢優(yōu)化。
為了優(yōu)化查詢,我們必須先了解查詢是怎樣執(zhí)行的,然后探討優(yōu)化器在哪些方面做得還不足,以幫助 MySql 更有效的執(zhí)行查詢。
優(yōu)化數(shù)據(jù)訪問(wèn)
在一條 Sql 語(yǔ)句執(zhí)行的很慢的時(shí)候,可以從以下兩個(gè)方面來(lái)分析:
- 是否在檢索的時(shí)候訪問(wèn)了太多的行或者列
- MySql 服務(wù)器是否在分析大量超過(guò)需要的行
請(qǐng)求了不需要的數(shù)據(jù)
萬(wàn)惡之源 SELECT *
一個(gè)很好用的觀點(diǎn)就是在每次使用 SELECT * 取出全部行的時(shí)候都要審視一下自己是否需要全部數(shù)據(jù)。
取出所有列可能使得索引覆蓋無(wú)效,一些 DBA 是嚴(yán)格禁止 SELECT * 的寫(xiě)法的。
重復(fù)查詢數(shù)據(jù)
有些地方可能會(huì)不小心的重復(fù)查詢了相同的數(shù)據(jù)。比如在論壇中,如果一個(gè)人回復(fù)多次,很有可能會(huì)一不小心每次都去請(qǐng)求這個(gè)人的資料,一個(gè)有效的方法就是使用緩存。
掃描額外的記錄
確定查詢只返回需要的數(shù)據(jù)以后,接下來(lái)該看一下為了返回需要的記錄是否掃描了太多行了。有兩個(gè)指標(biāo)我們需要關(guān)注,一個(gè)是掃描的行數(shù)和返回行數(shù)的比值,另外一個(gè)是掃描的訪問(wèn)類型。
掃描行數(shù)和返回行數(shù)的比值如果過(guò)低,則需要掃描大量的數(shù)據(jù)才能返回結(jié)果,通常可以使用如下的方法來(lái)保存數(shù)據(jù):
- 使用索引覆蓋,把所有的列放入索引中,就無(wú)需掃描表了
- 改變數(shù)據(jù)庫(kù)結(jié)構(gòu),比如采用單獨(dú)的表匯總表
- 重新寫(xiě)這個(gè) SQL 語(yǔ)句
在 EXPLAIN 語(yǔ)句中的 type 列中反應(yīng)了訪問(wèn)類型,從慢到快分別是:
全表掃描,索引掃描,范圍掃描,唯一索引查詢,常數(shù)引用。
如果查詢沒(méi)有使用合適的類型,可以合理的添加索引。
重構(gòu)查詢方式
將一個(gè)復(fù)雜查詢拆分成多個(gè)查詢
MySql 從設(shè)計(jì)上讓連接和斷開(kāi)都很快。如果只是返回一個(gè)小小的結(jié)果,MySql 非常高效。
當(dāng)然能一個(gè)查詢就解決的要盡量寫(xiě)成一個(gè)查詢,只是告訴大家不要太懼怕把查詢拆分開(kāi)來(lái)會(huì)帶來(lái)性能損失。
切分查詢
有時(shí)候一個(gè)大查詢會(huì)占用表鎖很久,影響業(yè)務(wù)。這時(shí)候可以將大查詢分為小查詢,每次執(zhí)行這個(gè)查詢的一小部分。
比如定期清除大量數(shù)據(jù)的時(shí)候,如果有一個(gè)大的語(yǔ)句一次性完成,則可能會(huì)占住很多資源,影響其他查詢。
將刪除改寫(xiě)成一次刪除一小部分?jǐn)?shù)據(jù),分散開(kāi)來(lái)在不同時(shí)間執(zhí)行,可以將服務(wù)器壓力分散到很長(zhǎng)的一個(gè)時(shí)間段中。
分解關(guān)聯(lián)查詢
很多高性能應(yīng)用會(huì)將一個(gè)大的關(guān)聯(lián)查詢分解成多個(gè)單表查詢。
- 讓緩存效率更高,許多應(yīng)用可以緩存單表查詢的結(jié)果,那么下次查詢的時(shí)候可以減少這次查詢
- 單個(gè)查詢減少 鎖的競(jìng)爭(zhēng)
- 更容易對(duì)數(shù)據(jù)庫(kù)進(jìn)行拆分
- 減少冗余記錄查詢
MySql 查詢過(guò)程
MySQL 通信協(xié)議
MySQL 客戶端和服務(wù)端的通信是半雙工的,這意味著同一個(gè)時(shí)刻內(nèi),客戶端和服務(wù)端只有一方在發(fā)送數(shù)據(jù)。一旦一方開(kāi)始發(fā)送數(shù)據(jù),另外一端必須接受完整個(gè)消息才能進(jìn)行響應(yīng)。
這就是為什么當(dāng)查詢語(yǔ)句特別長(zhǎng)的時(shí)候,max_allowed_packet 特別重要了。所以在必要的時(shí)候需要添加 LIMIT 限制。
查詢狀態(tài)
對(duì)于一個(gè) MySQL 連接,任何時(shí)刻都有一個(gè)狀態(tài),該狀態(tài)表示了 MySQL 當(dāng)前正在做什么,用 SHOW FULL PROCESSLIST 命令即可。
Sleep
線程正在等待客戶端發(fā)送新的請(qǐng)求。
Query
線程正在執(zhí)行查詢或者在將結(jié)果發(fā)送給客戶端
Locked
該線程在等待表鎖
Analyzing and statistics
線程正在收集存儲(chǔ)引擎的統(tǒng)計(jì)信息,并且生成執(zhí)行計(jì)劃。
Copying to tmp table
線程正在把數(shù)據(jù)復(fù)制到一個(gè)臨時(shí)表中,一般在 Group By 或者排序的時(shí)候會(huì)出現(xiàn)這個(gè)狀態(tài)。
Sorting result
線程正在排序數(shù)據(jù)
Sending data
線程可能在多個(gè)狀態(tài)之間傳送數(shù)據(jù),或者在向客戶端返回?cái)?shù)據(jù)。
MySQL 對(duì)關(guān)聯(lián)表順序優(yōu)化
MySQL 的優(yōu)化器會(huì)對(duì)查詢進(jìn)行靜態(tài)和動(dòng)態(tài)優(yōu)化,期中我們只挑最重要的優(yōu)化講,也就是對(duì)關(guān)聯(lián)表順序的優(yōu)化。
我們先來(lái)看一個(gè) UNION 的例子,對(duì)于 UNION 查詢,MySQL 會(huì)將單個(gè)查詢結(jié)構(gòu)放入一個(gè)臨時(shí)表(注意臨時(shí)表是沒(méi)有索引的)中,然后再重新讀出臨時(shí)表數(shù)據(jù)來(lái)完成 UNION 查詢。
MySQL 關(guān)聯(lián)執(zhí)行策略很簡(jiǎn)單,對(duì)于任何關(guān)聯(lián)都執(zhí)行嵌套循環(huán)關(guān)聯(lián)操作,即先從一個(gè)表讀出數(shù)據(jù),然后嵌套循環(huán)到下一個(gè)表中取出匹配的行,依次下去,直到找到所有的表中匹配的行為止。然后根據(jù)各個(gè)表匹配的行,返回查詢中需要的各個(gè)列。MySQL 會(huì)嘗試在最后一個(gè)關(guān)聯(lián)表中找到所有匹配的行,如果最后一個(gè)關(guān)聯(lián)表無(wú)法找到更多的行后,MySQL 返回到上一層次關(guān)聯(lián)表,看是否能找到更多的匹配記錄,依次類推迭代查詢。
關(guān)聯(lián)查詢優(yōu)化器
MySQL 優(yōu)化器決定了多個(gè)表關(guān)聯(lián)的順序,關(guān)聯(lián)優(yōu)化器可以選擇一個(gè)代價(jià)最小的關(guān)聯(lián)順序。
有時(shí)候優(yōu)化器選擇的不是最優(yōu)的順序,這時(shí)候可以使用 STRAUGHT_JOIN 關(guān)鍵字進(jìn)行查詢,讓優(yōu)化器按照你認(rèn)為最優(yōu)的順序查詢,但是一般來(lái)說(shuō)人判斷的都沒(méi)有優(yōu)化器好。
優(yōu)化器會(huì)嘗試在所有的順序中選擇一個(gè)成本最小的關(guān)聯(lián)順序,但是當(dāng)表非常多的時(shí)候,比如有 n 張表進(jìn)行關(guān)聯(lián),就要進(jìn)行 n! 次比較。當(dāng)表超過(guò) optimizer_search_depth 的時(shí)候,就會(huì)選擇貪婪搜索模式了。
MySQL 查詢優(yōu)化器限制
子查詢
MySQL 的子查詢優(yōu)化的相當(dāng)糟糕,最糟糕的一類是子查詢中 WHERE 條件包含了 IN() 的子查詢。比如用下面的語(yǔ)句查詢
SELECT * FROM film WHERE film.id in (SELECT file_id from film_actor WHERE actor_id = 1)
我們可能會(huì)認(rèn)為 MySQL 會(huì)執(zhí)行后面的語(yǔ)句選擇出 id 后才執(zhí)行前面的查詢,但是 MySQL 會(huì)將外層查詢壓入子查詢中
SELECT * FORM film WHERE EXISTS(SELECT * FROM film_actor WHERE actor_id = 1 AND film_actor.film.id = film.id)
這個(gè)查詢會(huì)對(duì) film 進(jìn)行全表掃描,性能非常糟糕。
所以我們最好用聯(lián)合查詢來(lái)代替這個(gè)查詢。
這個(gè)問(wèn)題直到 MySQL 5.5 還存在,MySQL 另外一個(gè)分支 MariaDB 在原有的基礎(chǔ)上做了大量的改進(jìn),例如這里帶 IN 的子查詢。
當(dāng)一個(gè)查詢能被寫(xiě)成子查詢和聯(lián)合查詢的時(shí)候,最好通過(guò)一些測(cè)試來(lái)判斷哪個(gè)寫(xiě)法更快一些
UNION
有時(shí)候 MySQL 無(wú)法將閑置條件由外層推到內(nèi)層,這使得本能限制掃描行數(shù)的 LIMIT 在內(nèi)層查詢中不起作用。
如果希望 UNION 的各個(gè)子句能根據(jù) LIMIT 只取出部分結(jié)果集,或者希望能先排好序再分別使用這些子句,那么需要分別對(duì)這些查詢使用 LIMIT 和 ORDER BY。
(SELECT * FROM XXX LIMIT 20) UNION ALL (SELECT * FROM XXX LIMIT 20)
并發(fā)執(zhí)行
MySQL 無(wú)法利用多核特性來(lái)并發(fā)執(zhí)行查詢。
最大值和最小值
對(duì)于 MIN 和 MAX 查詢,MySQL 的優(yōu)化做的不是很好,
SELECT MIN(id) FROM actor
因?yàn)?id 是遞增的,所以只需要掃描一行即可,但是 MySQL 仍然會(huì)做全表掃描。可以改下面的寫(xiě)法
SELECT id FROM actor LIMIT 1
特定優(yōu)化查詢
一般來(lái)說(shuō),使用 Percona Toolkit 中的 pt-query-advisor 能夠解析查詢?nèi)罩?#xff0c;分析查詢模式,然后給出詳細(xì)的建議來(lái)幫助你優(yōu)化 SQL 語(yǔ)句。
優(yōu)化 COUNT 查詢
當(dāng) COUNT 的值不可能為空的時(shí)候,MySQL 會(huì)轉(zhuǎn)向統(tǒng)計(jì)行數(shù)。如果我們想要統(tǒng)計(jì)行數(shù)的時(shí)候,最好直接使用 COUNT(*)。
使用近似值
有時(shí)候某些業(yè)務(wù)不需要精確值,此時(shí)可以用近似值來(lái)代替,EXPLAIN 出來(lái)的優(yōu)化器估算的行數(shù)就是一個(gè)不錯(cuò)的近似值,執(zhí)行 EXPLAIN 不需要去真正的執(zhí)行查詢,效率高很多。
優(yōu)化關(guān)聯(lián)查詢
- 確保 ON 或者 USING 上的列有索引,在創(chuàng)建索引的時(shí)候需要考慮到關(guān)聯(lián)列的順序,比如說(shuō)表 A,B 用列 c 進(jìn)行關(guān)聯(lián)的時(shí)候,如果優(yōu)化器的關(guān)聯(lián)順序是 B,A,則只需要在 A 上建立索引即可。
- 確保任何的 GROUP BY 和 ORDER BY 只涉及到一個(gè)表中的列
優(yōu)化子查詢
關(guān)于子查詢給出的最主要的優(yōu)化方法是:盡量使用關(guān)聯(lián)查詢代替子查詢,因?yàn)?MySQL 的子查詢優(yōu)化的非常爛。不過(guò)這條意見(jiàn)只在舊版本有用,在 MySQL 5.6 以上和 MariaDB 中,可以忽略掉這條優(yōu)化。
優(yōu)化 GROUP BY 和 DISTINCT
MySQL 經(jīng)常用同樣的方法來(lái)優(yōu)化這兩個(gè)查詢,它們都會(huì)用索引來(lái)優(yōu)化,這也是最有效的優(yōu)化辦法。
當(dāng)無(wú)法使用索引的時(shí)候,MySQL 會(huì)用臨時(shí)表或者文件排序來(lái)執(zhí)行 GROUP BY。
如果需要對(duì)關(guān)聯(lián)查詢做分組,那么通常采用標(biāo)識(shí)列來(lái)進(jìn)行分組效率會(huì)比較高。
優(yōu)化 LIMIT 分頁(yè)
當(dāng)系統(tǒng)需要進(jìn)行分頁(yè)操作的時(shí)候通常會(huì)使用 LIMIT 加 偏移量的操作,同時(shí)加上合適的 ORDER BY 語(yǔ)句。如果有對(duì)應(yīng)的索引,效率通常會(huì)不錯(cuò)。
但是當(dāng)偏移量非常大的時(shí)候,LIMIT 10000,20,這種語(yǔ)句會(huì)導(dǎo)致掃描了10020 行,但是只返回 20 行。
優(yōu)化這種查詢的方法有:
- 使用索引覆蓋,只搜索索引覆蓋的行然后通過(guò)一次查詢把所有需要的數(shù)據(jù)查找出來(lái)
- 通過(guò)延遲關(guān)聯(lián),后面會(huì)討論這個(gè)方法
優(yōu)化 SQL_CALC_FOUND_ROWS
分頁(yè)的時(shí)候有時(shí)候會(huì)通過(guò)在 LIMIT 語(yǔ)句中加上 SQL_CALC_FOUND_ROWS。這樣就可以獲取去掉 LIMIT 條件后查詢的行數(shù),加上這個(gè)提示以后,不管是否需要,都會(huì)把全部的行都掃描一遍,而不是在滿足了 LIMIT 的大小后停止掃描,這樣會(huì)帶來(lái)很大開(kāi)銷。
解決這個(gè)問(wèn)題有兩個(gè)方法
- 采用 EXPLAIN ROW 中的近似值,有時(shí)候不需要那么精準(zhǔn)的數(shù)據(jù)
- 先獲得比較多的緩存集,比如設(shè)置一個(gè) 100 頁(yè)和一個(gè) 100 頁(yè)以后的按鈕,當(dāng)用戶需要 100 頁(yè)后的按鈕再去獲取。
優(yōu)化 UNION 查詢
除非確實(shí)需要服務(wù)器消除重復(fù)的行,否則必須要使用 UNION ALL。
如果沒(méi)有 ALL 關(guān)鍵字,MySQL 會(huì)給臨時(shí)表加上 DISTINCT 選項(xiàng),然后做一次查重操作,這將帶來(lái)極大的開(kāi)銷。
轉(zhuǎn)載于:https://www.cnblogs.com/zjmeow/p/10104769.html
總結(jié)
以上是生活随笔為你收集整理的读薄《高性能MySql》(四)查询性能优化的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Centos7.x/redhat7.x修
- 下一篇: PCA----降维