1. 程式人生 > >MySQL查詢性能優化---高性能(二)

MySQL查詢性能優化---高性能(二)

多次 union all 關聯 join mys end max tro 減少

轉載地址:https://segmentfault.com/a/1190000011330649

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

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

查詢數據的方式

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

分解大的查詢

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

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語句:

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選項,這會導致對整個臨時表的數據做唯一性檢查。這樣做的代價非常高。

MySQL查詢性能優化---高性能(二)