高性能Mysql-查詢性能優化

1、爲什麼查詢會變慢

在嘗試編寫快速的查詢之前,首先我們需要明白,真正重要的是響應時間。如果把查詢看做是一個任務,那麼他就是由多個子任務組成的,優化查詢也就是需要優化這些子任務。通常來說,查詢的生命週期大致可以按照順序來看:從客戶端到服務器,然後再服務器上進行解析,生成執行計劃,執行,並返回結果給客戶端。在完成這些任務的時候,查詢需要在不同的地方花費時間,包括網絡,CPU計算,生成統計信息和執行計劃,鎖等待等操作。尤其是向底層存儲引擎檢索數據的調用操作,這些調用需要在內存操作,CPU操作,和內存不足時導致的I/O操作上耗費時間。

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

查詢性能低下最基本的原因是訪問的數據太多。某些查詢可能不可避免地需要篩選大量 數據, 但這並不常見。大部分性能低下的查詢都可以通過械少訪問的數據量的方式進行 優化。對於低效的查詢, 我們發現通過下面兩個步驟來分析總是很有效:

①    確認應用程序是否在檢索大量超過需要的數據。這通常意味着訪問了太多的行, 但有時候也可能是訪問了太多的列。
②   確認MySQL服務器層是否在分析大量超過需要的數據行。

2.1 是否向數據庫請求了不需要的數據

有些查詢會請求超過實際需要的數據, 然後這些多餘的數據會被應用程序丟棄。這會給MySQL服務器帶來額外的負擔, 井增加網絡開銷,另外也會消耗應用服務器的CPU 和內存資源。這裏有一些典型案例:

① 查詢不需要的記錄:一個常見的錯誤是常常會誤以爲MySQL會只返回需要的數據, 實際上MySQL卻是先返回全部結果集再進行計算。

② 多表關聯時返回全部列。

③ 總是取出全部列:每次看到 SELECT *的時候都需要用懷疑的眼光審視, 是不是真的需要返回全部的列?很可能不是必需的。 取出全部列, 會讓優化器無怯完成索引覆蓋掃描這類優化,還會爲服務器帶來額外的I/O、 內存和CPU的消耗。

④ 重複查詢相同的數據。

2.2 MySQL是否在掃描額外的記錄

在確定查詢只返回需要的數據以 後, 接下來應該看看查詢爲了返回結果是否掃描了過多 的數據。對於MySQL, 最簡單的衡量查詢開銷的三個指標如下:晌應時間、掃描的行數、返回的行數。

響應時間:響應時間是兩個部分之和:服務時間和排隊時間。 服務時間是指數據庫處理這個查詢真正花了多長時間。 排隊時間是指服務器因爲等待某些資源而沒有真正執行查詢的時間一一可能是等I/O操作完成, 也可能是等待行鎖, 等等。

掃描的行數和返回的行數:理想情況下掃描的行數和返回的行數應該是相同的,但在實際情況中,當我們在做 關聯查詢時,服務器必須要掃描多行才能生成結果集的一行。

掃描的行數和訪問類型:在評估查詢開銷時,需要考慮一下從表中找到某一行數據的成本。Mysql有好幾種訪問方式可以查找並返回一行結果。有些訪問方式需要掃描很多行纔會返回一行。在explain語句中,type列反映了訪問類型。如果查詢沒有找到合適分訪問類型,那麼解決的最好辦法就是添加索引。

一般mysql能夠使用如下三種方式應用WHERE條件,從好帶壞一次爲:

•     在索引中使用WHERE條件來過濾不匹配的記錄。 這是在存儲引擎層完成的。
•     使用索引覆蓋掃描(在Extra列中出現了Using index) 來返回記錄, 直接從索引中過濾不需要的記錄並返回命中的結果。 這是在MySQL服務器層完成的, 但無須再回表查詢記錄。
•     從數據表中返回數據,然後過施不滿足條件的記錄(在Extra列中出現Using Where)。這在MySQL服務器層完成,MySQL需要先從數據表讀出記錄然後過濾。

3、重構查詢方式

有時候, 可以將查詢轉換一種寫法讓 其返回一樣的結果,但是性能更好。但也可以通過修改應用代碼,用另一種方式完成查詢,最終達到一樣的目的。

3.1 一個複雜查詢還是多個簡單查詢

MySQL從設計上讓連接和斷開連接都很輕量級,再返回一個小的查詢結果方面很高效。不過, 在應用設計的時候, 如果一個查詢能夠勝任時還寫成多個獨立查詢是不明智的。

3.2 切分查詢

有時候對於一個大查詢我們需要 “分而治之”,將大查詢切分成小查詢, 每個查詢功能完全一樣, 只完成一小部分, 每次只返回一小部分查詢結果。刪除舊的數據就是一個很好的例子。 定期地清除大量數據時, 如果用一個大的語句一次性完成的話, 則可能需要一次鎖住很多數據、 佔滿整個事務日誌、 豔盡系統資源、 阻塞 很多小的但重要的查詢。 將一個大的 DEL盯E語句切分成多個較小的查詢可以儘可能小地 影響MySQL性能, 同時還可以減少MySQL複製的延遲。

3.3 分解關聯查詢

很多高性能的應用都會對關聯查詢進行分解。 簡單地, 可以對每一個表進行一次單表查詢, 然後將結果在應用程序中進行關聯。 用分解關聯查詢的方式重構查詢有如下 的優勢:

•    讓緩存的效率更高。 許多應用程序可以方便地緩存單表查詢對應的結果對象。 
•    將查詢分解後,執行單個查詢可以減少鎖的競爭。
•    在應用層做關聯, 可以更容易對數據庫進行拆分, 更容易做到高性能和可擴展。
•    查詢本身效率也可能會有所提升。 
•    可以減少冗餘記錄的查詢。 在應用層做關聯查詢, 意味着對於某條記錄應用只需要查詢一次,而在數據庫中做關聯查詢,則可能需要重複地訪問一部分數據。 從這點看, 這樣的重構還可能會減少網絡和內存的消耗。
•    更進一步, 這樣做相當於在應用中實現了哈希關聯, 而不是使用MySQL的嵌套循環關聯。 

4、查詢執行的基礎

當希望MySQL能夠以更高的性能運行查詢時, 最好的辦法就是弄清楚MySQL是優化和執行查詢的。如下圖:

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

5、mysql查詢優化器的侷限性

MySQL的萬能 “嵌套循環” 並不是對每種查詢都是最優的。 不過還好,MySQL查詢優化器只對少部分查詢不適用, 而且我們往往可以通過改寫查詢讓MySQL高效地完成工 作。

5.1 關聯子查詢

MySQL的子查詢實現得非常糟騰。 最糟糕的一類查詢是WHERE條件中包含IN()的子查詢語句。那如何用好關聯子查詢呢?

例如:當返回結果中只有一個表的某些列的時候。但是具體情況還需要我們在測試完成後選擇具體的解決方案。

5.2 union的限制

有時,MySQL無法將限制條件從外層“下推”到內層,這使得原本能夠限制部分返回結果的條件無怯應用到內層查詢的優化上。
如果希望UNION的各個子句能夠根據LIMIT只取部分結果集,或者希望能夠先排好序再合併結果集的話,就需要在UNION的各個子句中分別使用這些子句。例如:兩個表合併去20條數據,可以每個表都limit20條,在合併。而不是先合併,在limit20。

5.3 索引合併優化

當WHERE子句中包含多個複雜條件的 時候,MySQL能夠訪問單個表的多個索引以合井和交叉過濾的方式來定位需要查找的行。

5.4 等值傳遞

某些時候,等值傳遞會帶來一些意想不到的額外消耗。 例如,有一個非常大的IN()列表,而MySQL優化器發現存在WHERE、 ON或者USING的子句, 將這個列表的值和另一個表的某個列相關聯。那麼優化器會將IN()列表都複製應用到關聯的各個表中。 

5.5 並行執行

MySQL無法利用多核特性來並行執行查詢。 很多其他的關係型數據庫能夠提供這個特性,但是MySQL做不到。 

5.6 哈希關聯

MySQL的所有關聯都是嵌套循環關聯。 不過, 可以通過建立一個哈希索引來曲線地實現哈希關聯。如果使用的是Memory存儲引擎, 則索引都是哈希索引, 所以關聯的時候也類似於哈希關聯。

5.7 鬆散索引掃描

由於歷史原因,MySQL並不支持鬆散索引掃描, 也就無法按照不連續的方式掃描一個索引。 通常,MySQL的索引掃描需要先定義一個起點和終點, 即使需要的數據只是這段索引中很少數的幾個,MySQL仍需要掃描這段索引中每一個條目。

5.8 最大值和最小值優化

對於MIN() 和MAX() 查詢,MySQL的優化做得並不好。

5.9 在同一個表上查詢和更新

mysql不允許對同一張表進行同時更新和查詢。

6、優化特定類型的查詢

6.1 優化count()查詢

COUNT ()是一個特殊的函數, 有兩種非常不同的作用,它可以統計某個列值的數量, 也可以統計行數。在統計列值時要求列值是非空的(不統計 NULL)。 如果在 COUNT()的括號中指定了列或者列的表達式, 則統計的就是這個表達式有值的結果數 。

簡單的優化:有時候可以使用 MyISAM 在 COUNT(*)全表非常快的這個特性, 來加速一些特定條件的 COUNT()的查詢。

使用近似值:有時候某些業務場景並不要求完全精確的 cou陽值,此時 可以用近似值來代替。

更復雜的優化:除了前面的方法,在MySQL層面還能做的就只有索引覆蓋掃描了。

6.2 優化關聯查詢

•    確保ON或者 USING子句中的列上有索引。在創建索引的時候就要考慮到關聯的順序。當表A 和表B用列c關聯的時候, 如果優化器的關聯順序是B、A, 那麼就不需要在B表的對應列上建上索引。 沒有用到的索引只會帶來額外的負擔。 一般來說, 除非 有其他理由, 否則只需要在關聯順序中的第二個表的相應列上創建索引。
•    確保任何的GROUP BY和ORDER BY中的表達式只涉及到一個表中的列, 這樣MySQL纔有可能使用索引來優化這個過程。
•    當升級MySQL的時候需要注意:關聯語法、 運算符優先級等其他可能會發生變化 的地方。 因爲以前是普通關聯的地方可能會變成笛卡兒積, 不同類型的關聯可能會 生成不同的結果等。

6.3 優化子查詢

關於子查詢優化我們給出的最重要的優化建議就是儘可能使用關聯查詢代替, 至少當前的MySQL版本需要這樣。 

6.4 優化GROUP BY和DISTINCT

在很多場景下, MySQL都使用同樣的辦法優化這兩種查詢, 事實上, MySQL優化器會在內部處理的時候相互轉化這兩類查詢。 它們都可以使用索引來優化, 這也是最有效的優化辦法。

在MySQL中, 當無戰使用索引的時候, GROUP 即使用兩種策略來完成:使用臨時表或者文件排序來做分組。如果需要對關聯查詢做分組(GROU P BY),井且是按照查找表中的某個列進行分組, 那麼通常採用查找表的標識列(例如主鍵)分組的效率會比其他列更高。

6.5 優化LIMIT分頁

在系統中需要進行分頁操作的時候,我們通常會使用 LIMIT 加上偏移量的辦法實現,同時加上合適的 ORDER BY 子句.如果有對應的索引,通常效率會不錯,否則,MySQL 需 要做大量的文件排序操作。一個非常常見又令人頭離的問題就是,在偏移量非常大的時候,例如可能是 LIMIT 1000 到20這樣的查詢,這時 MySQL 需要查詢 10 020 條記錄然後只返回最後 20 條,前面10 000 條記錄都將被拋棄,這樣的代價非常高。

優化此類分頁查詢的一個最簡單的辦格就是儘可能地使用索引覆蓋掃描,而不是查詢所 有的列。比如:SELECT film_id, description From sakila.film ORDER BY title LIMIT 50,5;此查詢可以修改爲:SELECT film.film_id, film.description FROM sakila.film INNER JON(SELECT film.id FROM sakila.film ORDER BY title LIMIT 50,5 ) AS lim USING(film_id); 這裏的延遲關聯將大大提升效率。

6.6 優化SQL_CALC_FOUND_ROWS

分頁的時候,另一個常用的技巧是在LIMIT語句中加上SOL_ CALC_ FOUND_ ROWS提示(hint),這樣就可以獲得去掉 LIMIT以後滿足條件的行數,因此可以作爲分頁的總數。

6.7 優化UNION查詢


除非確實需要服務器消除重複的行,否則就 定要使用UNION ALL,這 一點很重要。如果沒有ALL關鍵字,MySQL會給臨時表加上 DISTINCT選項,這會導致對整個臨時表的數據唯一性檢查。
 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章