Mysql查詢性能優化

Mysql查詢性能優化

慢查詢優化基礎:優化數據訪問

  • 查詢須要的記錄。查詢100條,應用層僅須要10條。
  • 多表關聯時返回所有列。*,多表關聯,字段查詢要加前綴。
  • 老是取出所有列。*
  • 重複查詢相同的數據。例如:在用戶評論的地方須要查詢用戶頭像URL,那麼用戶屢次評論的時候,可能就會反覆查詢這個數據。比較好的方案,當初次查詢的時候將這個數據緩存起來,須要的時候從緩存中取出,這樣性能顯然會更好。

重構查詢方式

切分查詢

  • 將大查詢切分紅小查詢,每一個查詢功能徹底同樣,只完成一小部分,每次只返回一小部分查詢結果。若是一次性完成的話,則可能須要一次鎖住不少數據、佔滿整個事務日誌、耗盡系統資源、阻塞不少小的但重要的查詢。

分解關聯查詢

  • 不少高性能的應用都會對關聯查詢進行分解。
  • 簡單地,能夠對每個表進行一次單表查詢,而後將結果在應用程序中進行關聯。
select * from tag join tag_post on tag_post.id = tag.id join post on post.id = tag_post.id where tag.tag = 'msyql'; 分解爲: select * from tag from where tag = 'msyql'; select * from tag_post where id = 1234; select * from post where id in (1,2,3); 
優點
  • 讓緩存的效率更高。許多應用程序能夠方便地緩存單表查詢對應的結果對象。例如:上面查詢中的tag已經被緩存了,那麼應用就能夠跳過第一個查詢。再例如,應用中已經緩存了ID爲1,2的內容,那麼第三個查詢的in()中就能夠少了幾個ID,對MYSQL的查詢緩存來講,若是關聯中的某個表發生了變化,那麼久沒法使用查詢緩存了,而拆分後,若是某個表不多改變,那麼基於該表的查詢就能夠重複利用查詢緩存結果了。
  • 將查詢分解後,執行單個查詢就能夠減小鎖的競爭。
  • 在應用層作關聯,能夠更容易對數據庫進行拆分,更容易作到高性能和高擴展。
  • 查詢自己效率也可能會有所提高。使用IN()代替關聯查詢,可讓MYSQL按照ID順序進行查詢,這可能比隨機的關聯要更搞笑。
  • 能夠減小冗餘記錄的查詢。在應用層作關聯查詢,意味着對於某條記錄應用只須要查詢一次,而在數據庫中作關聯查詢,則可能須要重複地訪問一部分數據。從這點看,這樣的重構還可能會減小網絡和內存的消耗。
  • 更進一步,這樣作至關於在應用中實現了哈希關聯,而不是使用MYSQL的潛逃循環關聯。某些場景哈希關聯的效率要高不少。
  • 在不少場景下,經過重構查詢將關聯放到應用程序中將會更加高效,這樣的場景有不少,好比:當應用可以方便地緩存單個查詢的結果的時候,當能夠將數據分佈到不一樣的MYSQL服務器上的時候,當可以使用IN的方式代替關聯查詢的時候、當查詢中使用同一個數據表的時候。

查詢執行基礎

MYSQL接收到請求都作了什麼?

  1. 客戶端發送一條查詢給服務器。
  2. 服務器先檢查查詢緩存,若是命中了緩存,則馬上返回存儲在緩存中的結果。不然進入下一階段。
  3. 服務器進行SQL解析、預處理,再由優化器生成對應的執行計劃。
  4. MYSQL根據優化器生成的執行計劃,調用存儲引擎的API來執行查詢。
  5. 將結果返回給客戶端。

MYSQLk客戶端/服務端通訊協議mysql

MYSQL客戶端和服務端之間的通訊協議是「半雙工」的,這意味着,在任何一個時刻,要麼是由服務器向客戶端發送數據,要麼是由客戶端向服務器發送數據,這兩個動做不能同時發生。一旦一端開始發送消息,另外一端要接收完整個消息才能響應它。這就像來回拋球的遊戲:在任什麼時候刻,只能一我的控制球,並且只能空值求得人才能將球拋回去。
客戶端用一個單獨的數據包將數據傳給服務器,這也是爲何當查詢的語句很長的時候,參數mac_allow_package就特別重要了。一旦客戶端發送了請求,它能作的事情就只能是等待結果了。
相反的,通常服務器響應給用戶的數據一般不少,由多個數據包組成。當服務器開始響應客戶端請求時,客戶端必須完整地接收整個返回結果,而不能簡單地只取前面幾條結果,而後讓服務器中止發送數據。這種狀況下,客戶端若接收完成的結果,而後取前面幾條須要的結果,或者接完幾條結果後就「粗暴」地斷開鏈接,都不是好主意。這也是在必要的時候必定要在查詢中加上LIMIT限制的緣由。

查詢狀態

對於一個MYSQL鏈接,或者說一個線程,任什麼時候刻都有一個狀態,該狀態表示了MYSQL當前正在作什麼。有不少方式能查詢當前狀態,最簡單的是使用show full processlist命令。一個查詢的生命週期中,狀態會變化不少次。
  • Sleep
    • 線程正在等待客戶端發送新的請求。
  • Query
    • 線程正在執行查詢或者正在將結果發送給客戶端。
  • Locked
    • 在MYSQL服務器層,該線程正在等待表鎖。在存儲引擎級別實現的鎖。例如:Innodb的行鎖,並不會體如今線程狀態中。對於MyISAM來講這是一個比較典型的狀態,但在其餘沒有行鎖的引擎中也常常出現。
  • Analyzing and statistics
    • 線程正在收集存儲引擎的統計信息,並生成查詢執行計劃。
  • Copying to tmp table [on disk]
    • 線程正在執行查詢,而且將其結果集都複製到一個臨時表中,這種狀態通常要麼在作Group By操做,要麼是文件排序操做,或者是UNION操做。若是這個狀態後面還有「on disk」標記,那表示MYSQL正在講一個內存臨時表放到磁盤上。
  • Sorting result
    • 線程正在對結果集進行排序。
  • Sending data
    • 這表示多種狀況:線程可能在多個狀態之間傳送數據,或者在生成結果集,或者在客戶端返回數據。

瞭解這些狀態的基本含義很是有用,這可讓你很好地瞭解當前「誰正在持球」。在一個繁忙的服務器上,可能會看到大量的不正常狀態,例如statistics正佔用大量的時間。這一般表示,某個地方有異常了。sql

查詢優化

MYSQL如何執行關聯查詢

對於UNION查詢,MYSQL先將一系列的單個查詢結果放到一個臨時表中,而後再從新讀取臨時表數據來完成UNION查詢。

在MYSQL的概念中,每一個查詢都是一次關聯,因此讀取結果臨時表也是一次關聯。數據庫

當前MYSQL關聯執行的策略很簡單:MYSQL對任何關聯都執行嵌套循環關聯操做,即MYSQL先在一個表中循環取出單條數據,而後再嵌套循環到下一個表中尋找匹配的行,依次下去,知道找到全部表中匹配的行爲止。而後根據各個表匹配的行,返回查詢中須要的各個列。MYSQL會嘗試在最後一個關聯表中找到全部匹配的行,若是最後一個關聯表沒法找到更多的行之後,MYSQL返回到上一層次關聯表,看是否可以找到更多匹配記錄,一次類推迭代執行。緩存

簡單的內鏈接查詢: select tab1.col1, tab2.col2 from tab1 inner join tab2 using(col3) where tab1.col1 in (1,2); 實際執行的僞代碼表示: outer_iter = iterator over tabl1 where col1 in (1,2) outer_row = outer_iter.next while outer_row inner_iter = iterator over tab2 where col3 = outer_row.col3 inner_row = inner_iter.next while inner_row output [ outer_row.col1, inner_row.col2] inner_row = inner_iter.next end outer_row = outer_iter.next end 
簡單的外鏈接查詢: select tab1.col1, tab2.col2 from tab1 outer join tab2 using(col3) where tab1.col1 in (1,2); 實際執行的僞代碼表示: outer_iter = iterator over tabl1 where col1 in (1,2) outer_row = outer_iter.next while outer_row inner_iter = iterator over tab2 where col3 = outer_row.col3 inner_row = inner_iter.next if inner_row while inner_row output [ outer_row.col1, inner_row.col2] inner_row = inner_iter.next end else output [ outer_row.col, null ] end outer_row = outer_iter.next end 

MYSQL的臨時表是沒有任何索引的,在編寫複雜的子查詢和關聯查詢的時候須要注意這一點。這一點對UNION查詢也是同樣的。性能優化

關聯子查詢

MYSQL的子查詢實現得很是糟糕。最糟糕的一類查詢是where條件中包含IN()的子查詢語句。
select * from tab1 where col1 in ( select col2 from tab2 where col3 = 1; ) 
MYSQL對IN()列表中的 選項有專門的優化策略,通常會認爲MYSQL會先執行子查詢返回全部包含col3爲1的col2。通常來講,IN()列查詢速度很快,因此咱們會認爲上面的查詢會這樣執行:
- SELECT GROUP_CONCAT(col2) from tab2 where col3 = 1; - Reuslt : 1,2,3,4, select * from tabl1 where col1 in (1,2,3,4); 
很不幸,MYSQL不是這樣作的。MYSQL會將相關的外層表壓到子查詢中,它認爲這樣能夠更高效率地查找到數據行。也就是說,MYSQL會將查詢改爲下面的這樣:
select * from tab1 where exists ( select * from tab2 where col3 = 1 and tab1.col1 = tab2.col1 ); 
這時,子查詢須要根據col1來關聯外部表的film,由於須要到col1字段,因此MYSQL認爲沒法先執行這個子查詢。
若是tab1表數據量小,性能還不是很糟糕,若是是一個很是大的表,那這個查詢性能會很是糟糕。改寫這個子查詢
select * from tab1 inner join tab2 using(col1) where col3 = 1; && select * from tab1 where exists ( select * from tab2 where col3 = 1 and tab1.col1 = tab2.col1 ); 
一旦使用了DISTINCT和GROUP by,在查詢執行的過程當中,一般產生臨時中間表。可使用EXISTS子查詢優化

UNION的限制

經過將兩個表查詢結果集合並取前20條
(select *from tab1 order by col1) union all (select * from tab2 order by col2) limit 20; 優化爲: (select *from tab1 order by col1 limit 20) union all (select * from tab2 order by limit 20) 

UNION 臨時表的數據會大大減小服務器

 

優化COUNT()查詢

Count()是一個特殊的函數,有兩種很是不一樣的做用:它能夠統計某個列的數量,也能夠統計行數。在統計列值時要求列值是非空的(不統計NULL)。若是在COUNT()的括號中指定了列或者列的表達式,則統計的就是這個表達式有值的結果數。
Count()的另一個做用是統計結果集的行數。當MYSQL肯定括號內的表達式值不可能爲空時,實際上就是在統計行數。最簡單的就是COUNT(*)。
簡單的優化
select count(*) from tab1 where col >5; 優化爲: select (select count(*) from tab1 ) - count(*) from tab1 where col <5; 掃描的數量會減小不少 子查詢也會當成常數,使用expand可知 
情景:在同一個查詢中統計一個列的不一樣值的數量,以減小查詢的語句量
select sum(if(color = blue), 1, 0) as blue , sum(if(color = red), 1, 0) as red from items ; 一樣也可使用Count 

優化關聯查詢

  • 確保ON或者USING子句中的列有索引。
  • 確保任何group by和order by只涉及到一個表中的列。

優化LIMIT分頁

select col1, col2 from tab1 order by col3 limit 50,5; 改寫成: select col1, col2 from tab1 inner join ( select col1 from tab1 order by col3 limit 50,5 ) as lim using(col1); 
  • 這裏的「延遲關聯」將大大提高查詢效率,它讓MYSQL掃描儘量少的頁面,獲取須要訪問的記錄後再根據關聯列回原表查詢須要的全部列。這個技術能夠優化LIMIT查詢。網絡

  • 有時候也能夠將LIMIT查詢轉換爲已知位置的查詢,讓MYSQL經過範圍掃描得到到對應的結果。函數

select col1, col2 from tab1 where col1 between 30 and 50; select col1, col2 from tab1 where col1 < 500 order by col1 limit 20; 
相關文章
相關標籤/搜索