查詢性能優化
阿新 • • 發佈:2017-06-23
跳過 原因 找到 頁面 關聯 時間短 服務器 優化 容易
查詢性能優化
怎麽樣算查詢性能比較好?響應時間短(獲取查詢數據速度快)優化數據訪問
查詢性能低下最基本的原因是訪問的數據太多。大部分性能低下的查詢都可以通過減少訪問的數據量的方式進行優化。 對於低效的查詢,我們發現通過下面兩個步驟來分析總是很有效:- 確認應用程序是否在檢索大量超過需要的數據。這通常意味著訪問了太多行,但有時候也可能是訪問了太多的列。
- 確認MySQL服務器層是否在分析大量超過需要的數據行。
是否向數據庫請求了不需要的數據
比較容易犯的錯誤情景:- 查詢不需要的記錄。一個常見的錯誤是誤以為MySQL會只返回需要的數據,實際上MySQL卻是先返回全部結果集再進行計算。最簡單的解決方法是在查詢後面加上LIMIT。(假分頁
- 多表關聯時返回全部列。(SELECT * FROM a join b on ... 這種情況下會查詢出表a 和表b的全部列數據.)(查詢數據多余)
- 總是取出全部列。(SELECT * ...)(查詢數據多余)
- 重復查詢相同的數據。(一般是初次查詢時把數據緩存起來,這種數據特點一般是:頻率高,數據量小)(查詢次數多余)
MySQL是否在掃描額外的記錄
一般MySQL能夠使用如下三種方式應用WHERE 條件,從好到壞依次為:- 在索引中使用WHERE條件來過濾不匹配的記錄。這是在存儲引擎層完成的。
- 使用索引覆蓋掃描(在EXTRA 列中出現了Using index)來返回記錄,直接從索引中過濾不需要的記錄並返回命中的結果。這是在MySQL服務器層完成的,但無須再回表查詢記錄。
- 從數據表中返回數據,然後過濾不滿足條件的記錄(在EXTRA 列中出現Using where)。這在MySQL服務器層完成,MySQL需要先從數據表讀取記錄然後過濾。
- 使用索引覆蓋掃描,把所有需要用到的列都放到索引中
- 改變庫表結構。例如使用單獨的匯總表
- 重寫這個復雜的查詢,讓MySQL優化器能夠以更優化的方式執行這個查詢。
優化特定類型的查詢
優化count查詢
COUNT()作用:它可以統計某個列值的數量,也可以統計行數。如果希望知道的是結果集的行數,最好使用COUNT(*),它會忽略所有列而直接統計行 數。 有時候可以使用MyISAM在COUNT(*)全表非常快的這個特性,來加速一些特定條件的COUNT()的查詢。 例如,查詢ID大於5的城市。SELECT COUNT(*) FROM City WHERE ID > 5;利用MyISAM在COUNT(*)全表非常快的特性,可以先查找ID小於5的城市數,然後用總城市數減去小於5的城市數得到同樣的結果。即:
SELECT (SELECT COUNT(*) FROM City) - COUNT(*) FROM City WHERE ID <=5;
使用近似值
有時候有些業務並不要求完全精確的COUNT值,此時可以使用近似值來代替。EXPALIN出來的優化器估算的行數就是一個不錯的近似值,執行EXPLAIN並不需要真正的去執行查詢,所以成本很低。優化關聯查詢
- 確保ON或者USING子句中的列上有索引。當表A和表B用列c關聯的時候,如果優化器的關聯順序是B、A,(如何確認優化器的關聯順序),那麽就不需要在B表的對應列上建索引。例如:查詢語句為:SELECT .. FROM B JOIN A USING(c),關聯順序為B、A,此時查詢應該是這樣的:b_rows = SELECT ... FROM B; SELECT ... FROM A WHERE A.c = b_rows.c; 一般來說:除非有其他理由,否則只需要在關聯順序中的第二個表的相應列上創建索引。
- 確保任何的GROUP BY和ORDER BY中的表達式只涉及到一個表中的列。這樣MYSQL才有可能使用索引來優化這個過程。
優化子查詢
盡可能的使用關聯查詢代替。(這種是說IN()裏面是一個表達式而不是常量)優化limit分頁(索引章節有提到)
SELECT film_id,description FROM film ORDER BY title limit 10000,5如果表很大,使用“延遲關聯”可以大大提升查詢效率
SELECT film_id, film.description FROM film JOIN ( SELECT film_id FROM film ORDER BY title LIMIT 10000,5 ) AS lim USING (film_id);它可以讓MYSQL掃描盡可能少的頁面,獲取需要訪問的記錄後再根據關聯列回原表查詢需要的所有列。 有時候也可以將LIMIT查詢轉換為已知位置的查詢,讓MYSQL通過範圍掃描獲得到對應的結果。如果在一個位置列上有索引,並且預先計算出了邊界值,上面的查詢可以改寫為:
SELECT film_id,description FROM film WHERE position BETWEEN 10000 AND 10004 ORDER BY position;-- 確認BETWEEN的邊界[]
優化UNION查詢
除非確實需要服務器消除重復的行,否則就一定要使用UNION ALL,如果沒有ALL關鍵字,MySQL會給臨時表加上SIATINCT 選項,這會導致對整個臨時表的數據做唯一性檢查。這樣做的代價非常高。使用用戶自定義變量
使用自定義變量可以更加清晰的梳理sql語句。可以用來存儲一些復雜查詢的中間變量值。 具體語法:SET @min_actor := (SELECT MIN(act_id) FROM actor); SELECT .. FROM actor WHERE act_id > @min_actor;使用自定義變量的限制:
- 無法查詢緩存(無關緊要)
- 不能在使用常量或者標識符的地方使用自定義變量,例如 表名、列名和LIMIT子句中
- 用戶自定義變量的生命周期是在一個連接中有效,不能用它們來作連接間的通信
- 不能顯示地聲明自定義變量的類型。如果希望變量是整數類型,那麽初始化的時候就賦值為0,如果希望是浮點類型則賦值為0.0,如果希望是字符處則賦值為‘’,用戶自定義變量的類型在賦值的時候會改變。
- 賦值符號 := 的優先級非常低,所以需要註意,賦值表達式應該使用明確的括號。
- 使用未定義變量不會產生任何語法錯誤。
優化排名順序
使用自定義變量的一個重要特性就是可以在給一個變量賦值的同時使用這個變量。用戶自定義的變量具有"左值"特性。 例子1:行號功能(在SQL SERVER 中會使用)SET @rownum := 0; SELECT actor_id,@rownum := @rownum + 1 AS rownum FROM actor LIMIT 3;例子2:獲取演過最多電影的前10位演員,然後根據他們的出演電影次數做一個排名,如果出演的電影數量一樣,則排名相同。(這種有邏輯處理的查詢,一般會使用到自定義變量,也可以放在應用程序中處理邏輯。)
SELECT actor_id, COUNT(*) AS cnt FROM film_actor GROUP BY actor_id ORDER BY cnt DESC LIMIT 10;現在把排名加上去,如果演員參演電影相同,則排名應該相同。這裏使用三個變量:一個用來記錄當前排名(rank),一個用來記錄前一個演員的排名(prev_cnt),一個用來記錄當前演員參演的電影數量(curr_cnt)。
SET @curr_cnt := 0 ,@prev_cnt := 0 ,@rank := 0; SELECT actor_id, @curr_cnt := COUNT(*) AS cnt, @rank := IF (prev_cnt <> @curr_cnt,@rank + 1 ,@rank) AS rank, @prev_cnt := @curr_cnt AS dummy FROM ( SELECT actor_id, COUNT(*) AS cnt FROM film_actor GROUP BY actor_id ORDER BY cnt DESC LIMIT 10 ) AS der;查詢結果:
避免重復查詢剛剛更新的數據
例子1:在更新行的同時獲取該行的數據 一種方式是用兩條SQL語句,如下:UPDATE t1 SET lastUpdated = NOW() WHERE id = 1; SELECT lastUpdated FROM t1 WHERE id = 1;使用自定義變量查詢,如下:
UPDATE t1 SET lastUpdated = NOW() WHERE id = 1 AND @now := NOW(); SELECT @now;這裏第二條查詢不涉及任何數據表,速度會比較快
編寫偷懶的UNION
例子:假設需要編寫一個UNION查詢,其第一個子查詢作為分支條件先執行,如果找到了匹配的行,則跳過第二個分支。(比如查詢的時候會在兩個地方查找同一個用戶——一個主用戶表,一個長時間不活躍的用戶表。)SELECT id FROM users WHERE id = 123 UNION ALL SELECT id FROM users_archived WHERE id = 123;這條語句雖然可以正常工作,但是不管在users表中是否查到了數據,都會在users_archived中再查找一次。改進後的語句如下:
SELECT GREATEST(@found := -1,id) AS id,‘users‘ AS which_tbl FROM users WHERE id = 1 UNION ALL SELECT id,‘users_archived‘ FROM users_archived WHERE id = 1 AND @found IS NULL UNION ALL SELECT 1,‘reset‘ FROM DUAL WHERE (@found := NULL) IS NOT NULL;
查詢性能優化