查詢性能低下最基本的原因是訪問的數據太多。某些查詢不可避免的需要篩選大量數據,但這並不常見。大部分性能低下的查詢都可以通過減少訪問的數據量的方式進行優化 ...
查詢性能低下最基本的原因是訪問的數據太多。某些查詢不可避免的需要篩選大量數據,但這並不常見。大部分性能低下的查詢都可以通過減少訪問的數據量的方式進行優化
對於低效的查詢,可以通過下麵兩個步驟進行分析:
- 確認應用程式是否在檢索大量超過需要的數據。這通常意味著訪問了太多的行,但有時候也可能是訪問了太多的列
- 確認MySQL伺服器層是否在分析大量超過需要的數據行
是否向資料庫請求了不需要的數據
有些查詢會請求超過實際需要的數據,然後這些多餘的數據會被應用程式丟棄。這會給MySQL伺服器帶來額外的負擔,並增加網路開銷、應用伺服器的CPU以及記憶體資源。
讓我們來看一些典型案例:
查詢不需要的記錄
一個常見的錯誤就是人們會誤以為MySQL只會返回需要的數據,實際上MySQL確實先返回全部結果集然後再進行計算。
比如在新聞網站中取出100條記錄,但是只在頁面上顯示前10條。實際情況是MySQL會查詢出全部的結果集,客戶端的應用程式會在接收全部的結果集數據,然後拋棄其中大部分的數據。
解決這個問題最簡單有效的解決方案就是在這樣的查詢後面加上 LIMIT。
總是取出所有列
每次看到SELECT * 都應該用懷疑的眼光審視,是不是真的需要返回全部的列?
答案很可能不是必須的。取出所有的列,會讓優化器無法完成索引覆蓋掃描這類的優化,還會為伺服器帶來額外的I/O、記憶體和CPU的消耗。因此,一些DBA是嚴格禁止這樣的寫法的,這樣做有時候還可以避免某些列被修改帶來的問題。
當然,查詢返回超過需要的數據也不總是壞事。
使用這樣有點浪費資料庫資源的方式可以簡化開發,提高代碼片段的復用性。如果清楚這樣做的性能影響,那麼這種做法也是值得考慮的。
重覆查詢相同的數據
在編寫程式時有時候總會一不小心出現這樣的錯誤——不斷的重覆執行相同的查詢,然後每次都返回完全相同的數據。
比較好的方案是,當初次查詢的時候就應該把這個數據緩存起來,只有在需要的時候才從緩存中取出來,這樣子性能會更好。
MySQL是否在掃描額外的記錄
在確定查詢只返回需要的數據以後,接下來應該看看查詢為了返回結果是否掃描了過多的數據。
對於MySQL,最簡單的衡量查詢開銷的三個指標如下:
- 響應時間
- 掃描的行數
- 返回的行數
沒有哪個指標可以完美的衡量查詢的開銷,但是他們大致反映了MySQL在內部執行查詢時需要訪問多少數據,並且可以大致推算出查詢運行的時間。這三個指標都會記錄到MySQL的慢日誌中,所以檢查慢日誌記錄是找出掃描行數過多的查詢的好辦法。
響應時間
響應時間是服務時間和排隊時間的和。服務時間指的是資料庫處理這個查詢真正花了多長時間。排隊時間指的是伺服器因為等待某些資源而沒有真正執行查詢的時間——可能等待I/O操作完成,也可能是等待行鎖等等。但是我們無法將響應時間細分到上面這些部分,所以響應時間既可能是一個問題的結果也可能是一個問題的原因。
當我們看到一個查詢的響應時間時,首先應該判斷這個響應時間是否是一個合理的值。概括來講,我們可以通過瞭解這個查詢需要那些索引以及它的執行計劃是什麼,然後計算大概需要多少個順序和隨機I/O,再乘以在具體硬體條件下一次I/O的消耗時間。最後把這些消耗加在一起,就可以獲得一個大概的參考值來判斷當前響應時間是不是一個合理的值。
掃描的行數與返回的行數
分析查詢時,查看該查詢掃描的行數是非常有幫助的。這在一定程度上能夠搜名該查詢查找數據的效率高不高。
但是這個指標可能還不夠完美,因為並不是所有的行的訪問代價都是相同的。較短的行的訪問速度更快,記憶體中的行也比磁碟中的行的訪問速度要快得多。
理想情況下掃描的行數以及返回的行數應該是相同的。
但實際上這樣完美的情況並不多見,比如在做關聯查詢時,伺服器需要掃描多行才能生成結果集中的一行。
掃描的行數和訪問類型
在評估查詢開銷時,需要考慮以下從表中找到某一行數據的成本。MySQL有好幾種訪問方式可以查找並返回一行結果。有些訪問方式可能需要掃描很多行才能返回一行結果,有些訪問方式可能無需掃描就能返回結果。
在EXPLAIN語句中的type列反映了訪問的類型。訪問類型有很多種,從全表掃描到索引掃描、範圍掃描、唯一索引查詢、常數引用等。這裡羅列的這些,速度從慢到快,掃描的行數也是從小到大。
如果查詢無法找到合適的訪問類型,那麼解決的最好辦法通常就是增加一個合適的索引。索引讓MySQL以最高效,掃描行數最少的方式找到需要的記錄。
我們看這樣一個例子:
SELECT * FROM sakila.file_actor WHERE file_id = 1;
這個查詢將會返回10行數據,從EXPLAIN的結果可以看到,MySQL在索引 idx_fk_film_id 上使用了ref訪問類型來執行查詢:
EXPLAIN的結果也顯示MySQL預估需要訪問10行數據。也就是說,查詢優化器認為這種訪問類型可以高效的完成查詢。
我們可以嘗試刪除索引,這樣MySQL就不得不使用更糟糕的訪問類型:
刪除索引之後,訪問類型變成了全表掃描。這裡的“Using Where”表示MySQL會通過WHERE條件來篩選存儲引擎返回的記錄。
一般MySQL能夠使用如下三種方式應用WHERE條件,從好到壞依次為:
- 在索引中使用WHERE條件來過濾不匹配的記錄。這是在存儲引擎層完成的。
- 使用索引覆蓋掃描(在Extra列會出現Using index)來返回記錄,直接從索引中過濾其他不需要的記錄並返回命中的結果。這是在MySQL伺服器層完成的。但是無需再回到表中查詢記錄。
- 從數據表中返回數據,然後過濾不滿足條件的記錄(在Extra列中出現Using Where)。這在MySQL伺服器層完成,MySQL需要先從數據表讀出記錄然後過濾。
如果發現查詢需要掃描大量的數據但是只返回少數的行,那麼通常可以嘗試下麵的技巧去優化它:
- 使用索引覆蓋掃描,把所有需要用的列都放到索引中,這樣存儲引擎無需回表獲取對應的行就可以返回結果了。
- 改變庫表結構,比如說使用彙總表。
- 重寫這個複雜的查詢,讓MySQL優化器能夠以更優化的方式執行這個查詢