MySql查詢性能優化

避免向數據庫請求不須要的數據

在訪問數據庫時,應該只請求須要的行和列。請求多餘的行和列會消耗MySql服務器的CPU和內存資源,並增長網絡開銷。
例如在處理分頁時,應該使用LIMIT限制MySql只返回一頁的數據,而不是嚮應用程序返回所有數據後,再由應用程序過濾不須要的行。
當一行數據被屢次使用時能夠考慮將數據行緩存起來,避免每次使用都要到MySql查詢。
避免使用SELECT *這種方式進行查詢,應該只返回須要的列。mysql

查詢數據的方式

查詢數據的方式有全表掃描、索引掃描、範圍掃描、惟一索引查詢、常數引用等。這些查詢方式,速度從慢到快,掃描的行數也是從多到少。能夠經過EXPLAIN語句中的type列反應查詢採用的是哪一種方式。
一般能夠經過添加合適的索引改善查詢數據的方式,使其儘量減小掃描的數據行,加快查詢速度。
例如,當發現查詢須要掃描大量的數據行但只返回少數的行,那麼能夠考慮使用覆蓋索引,即把全部須要用到的列都放到索引中。這樣存儲引擎無須回表獲取對應行就能夠返回結果了。算法

分解大的查詢

能夠將一個大查詢切分紅多個小查詢執行,每一個小查詢只完成整個查詢任務的一小部分,每次只返回一小部分結果
刪除舊的數據是一個很好的例子。若是隻用一條語句一次性執行一個大的刪除操做,則可能須要一次鎖住不少數據,佔滿整個事務日誌,耗盡系統資源、阻塞不少小的但重要的查詢。將一個大的刪除操做分解成多個較小的刪除操做能夠將服務器上本來一次性的壓力分散到屢次操做上,儘量小地影響MySql性能,減小刪除時鎖的等待時間。同時也減小了MySql主從複製的延遲。
另外一個例子是分解關聯查詢,即對每一個要關聯的表進行單表查詢,而後將結果在應用程序中進行關聯。下面的這個查詢:sql

SELECT * FROM tag
    JOIN tag_post ON tag_post.tag_id=tag.id
    JOIN post ON tag_post.post_id=post.id
WHERE tag.tag = 'mysql';

能夠分解成下面這些查詢來代替:數據庫

SELECT * FROM tag WHERE tag = 'mysql';
SELECT * FROM tag_post WHERE tag_id = 1234;
SELECT * FROM post WHERE post.id in (123,456,567,9098,8904);

將一個關聯查詢拆解成多個單表查詢有以下有點:緩存

  1. 讓緩存的效率更高。若是緩存的是關聯查詢的結果,那麼其中的一個表發生變化,整個緩存就失效了。而拆分後,若是隻是某個表不多的改動,並不會破壞全部的緩存。
  2. 能夠減小鎖的競爭
  3. 更容易對數據庫進行拆分,更容易作到高性能和可擴展。
  4. 查詢自己的效率也有可能會有所提高。例如上面用IN()代替關聯查詢比隨機的關聯更加高效。

優化MIN()和MAX()

添加索引能夠優化MIN()和MAX()表達式。例如,要找到某一列的最小值,只須要查詢對應B-Tree索引的最左端的記錄便可。相似的,若是要查詢列中的最大值,也只須要讀取B-Tree索引的最後一條記錄。對於這種查詢,EXPLAIN中能夠看到"Select tables optimized away",表示優化器已經從執行計劃中移除了該表,並以一個常數取而代之。服務器

用IN()取代OR

在MySql中,IN()先將本身列表中的數據進行排序,而後經過二分查找的方式肯定列的值是否在IN()的列表中,這個時間複雜度是O(logn)。若是換成OR操做,則時間複雜度是O(n)。因此,對於IN()的列表中有大量取值的時候,用IN()替換OR操做將會更快。網絡

優化關聯查詢

在MySql中,任何一個查詢均可以當作是一個關聯查詢,即便只有一個表的查詢也是如此。
MySql對任何關聯都執行嵌套循環的關聯操做,例如對於下面的SQL語句:post

SELECT tbl1.col1,tbl2.col2
FROM tbl1 INNER JOIN tbl2 USING(col3)
WHERE tbl1.col1 IN(5,6);

下面的僞代碼表示MySql將如何執行這個查詢:性能

//先從第一個表中取出符合條件的全部行
out_iter = iterator over tbl1 where col1 IN(5,6)
outer_row = out_iter.next
//在while循環中遍歷第一個表結果集的每一行
while outer_row
    //對於第一個表結果集中的每一行,在第二個表中找出符合條件的全部行
    inner_iter = iterator over tbl2 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

對於單表查詢,那麼只須要完成上面外層的基本操做。
優化關聯查詢,要確保ON或者USING子句中的列上有索引,而且在創建索引時須要考慮到關聯的順序。一般來講,只須要在關聯順序中的第二個表的相應列上建立索引。例如,當表A和表B用列c關聯的時候,假設關聯的順序是B、A,那麼就不須要在B表的c列上創建索引。沒有用到的索引只會帶來額外的負擔。
此外,確保任何的GROUP BY和ORDER BY中的表達式只涉及到一個表中的列,這樣才能使用索引來優化這個過程。優化

臨時表的概念

上面提到在MySql中,任何一個查詢實質上都是一個關聯查詢。那麼對於子查詢或UNION查詢是如何實現關聯操做的呢。
對於UNION查詢,MySql先將每個單表查詢結果放到一個臨時表中,而後再從新讀出臨時表數據來完成UNION查詢。MySql讀取結果臨時表和普通表同樣,也是採用的關聯方式。
當遇到子查詢時,先執行子查詢並將結果放到一個臨時表中,而後再將這個臨時表當作一個普通表對待。
MySql的臨時表是沒有任何索引的,在編寫複雜的子查詢和關聯查詢的時候須要注意這一點。
臨時表也叫派生表。

排序優化

應該儘可能讓MySql使用索引進行排序。當不能使用索引生成排序結果的時候,MySql須要本身進行排序。若是數據量小於「排序緩衝區」的大小,則MySql使用內存進行「快速排序」操做。若是數據量太大超過「排序緩衝區」的大小,那麼MySql只能採用文件排序,而文件排序的算法很是複雜,會消耗不少資源。
不管如何排序都是一個成本很高的操做,因此從性能角度考慮,應儘量避免排序。因此讓MySql根據索引構造排序結果很是的重要。

子查詢優化

MySql的子查詢實現的很是糟糕。最糟糕的一類查詢是WHERE條件中包含IN()的子查詢語句。
應該儘量用關聯替換子查詢,能夠提升查詢效率。

優化COUNT()查詢

COUNT()有兩個不一樣的做用:

  1. 統計某個列值的數量,即統計某列值不爲NULL的個數。
  2. 統計行數。

當使用COUNT(*)時,統計的是行數,它會忽略全部的列而直接統計全部的行數。而在括號中指定了一個列的話,則統計的是這個列上值不爲NULL的個數。
能夠考慮使用索引覆蓋掃描或增長彙總表對COUNT()進行優化。

優化LIMIT分頁

處理分頁會使用到LIMIT,當翻頁到很是靠後的頁面的時候,偏移量會很是大,這時LIMIT的效率會很是差。例如對於LIMIT 10000,20這樣的查詢,MySql須要查詢10020條記錄,將前面10000條記錄拋棄,只返回最後的20條。這樣的代價很是高,若是全部的頁面被訪問的頻率都相同,那麼這樣的查詢平均須要訪問半個表的數據。
優化此類分頁查詢的一個最簡單的辦法就是儘量地使用索引覆蓋掃描,而不是查詢全部的列。而後根據須要與原表作一次關聯操做返回所需的列。對於偏移量很大的時候,這樣的效率會提高很是大。考慮下面的查詢:

SELECT film_id, description FROM sakila.film ORDER BY title LIMIT 50, 5;

若是這個表很是大,那麼這個查詢最好改寫成下面的這樣子:

SELECT film.film_id, film.description FROM sakila.film
INNER JOIN 
(SELECT film_id FROM sakila.film ORDER BY title LIMIT 50,5) AS lim
USING(film_id);

注意優化中關聯的子查詢,由於只查詢film_id一個列,數據量小,使得一個內存頁能夠容納更多的數據,這讓MySQL掃描儘量少的頁面。在獲取到所須要的全部行以後再與原表進行關聯以得到須要的所有列。
LIMIT的優化問題,實際上是OFFSET的問題,它會致使MySql掃描大量不須要的行而後再拋棄掉。能夠藉助書籤的思想記錄上次取數據的位置,那麼下次就能夠直接從該書籤記錄的位置開始掃描,這樣就避免了使用OFFSET。能夠把主鍵當作書籤使用,例以下面的查詢:

SELECT * FROM sakila.rental ORDER BY rental_id DESC LIMIT 20;

假設上面的查詢返回的是主鍵爲16049到16030的租借記錄,那麼下一頁查詢就能夠直接從16030這個點開始:

SELECT * FROM sakila.rental WHERE rental_id < 16030
ORDER BY rental_id DESC LIMIT 20;

該技術的好處是不管翻頁到多麼後面,其性能都會很好。
此外,也能夠用關聯到一個冗餘表的方式提升LIMIT的性能,冗餘表只包含主鍵列和須要作排序的數據列。

優化UNION查詢

除非確實須要服務器消除重複的行,不然必定要使用UNION ALL。若是沒有ALL關鍵字,MySql會給臨時表加上DISTINCT選項,這會致使對整個臨時表的數據作惟一性檢查。這樣作的代價很是高。

相關文章
相關標籤/搜索