MySQL分頁查詢優化

以前搬磚的時候遇到對行數大的表進行分頁的操做,性能好差。最近在讀《高性能MySQL》,正好講到這個方面的,記錄一下(基本上都是原文)。mysql

優化LIMIT分頁

在系統中須要進行分頁才作的時候,咱們一般會使用LIMIT加上偏移量的辦法實現,同時加上合適的ORDER BY字句。若是有對應的索引,一般效率會不錯,不然,MySQL須要作大量的文件排序操做。sql

一個很是常見又使人頭疼的問題就是,在偏移量很是大的時候(翻頁到很是靠後的頁面),例如多是LIMIT 10000,20這樣的查詢,這時MySQL須要查詢10020條記錄而後只返回最後20條,前面10000條記錄都被拋棄,這樣的代價很是高。若是素所的頁面被訪問的頻率都相同,那麼這樣的查詢平均須要訪問半個表的數據,要優化這種查詢,要麼在頁面中限制分頁的數量,要麼是優化大偏移量的性能。緩存

優化此類分頁查詢的一個最簡單的辦法就是儘量地使用索引覆蓋查詢,而不是查詢全部的列。而後根據須要作一個關聯操做再返回所需的列。對於偏移量很大的時候,這樣作的效率會提高很大。考慮下面的查詢。性能

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

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

mysql> 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);

這裏的「延遲關聯」將大大提高查詢效率,它讓MySQL掃描儘量的頁面,獲取須要訪問的記錄後再根據關聯列回原表查詢須要的全部列。這項技術也能夠用於優化關聯查詢的LIMIT字句。設計

有時候也能夠將LIMIT查詢轉換爲已知位置的查詢,讓MySQL經過範圍掃描得到到對應的結果。例如,若是在一個位置列上有索引,而且預先計算出了邊界值,上面的查詢就能夠改寫爲:code

mysql> SELECT film_id, description FROM sakila.film
    -> WHERE position BETWEEN 50 AND 54 ORDER BY position

對數據進行排名的問題也與此相似,但每每還會和GROUP BY混合使用。在這種狀況下一般須要預先計算並儲存排名信息。排序

LIMIT和OFFSET的問題,它會致使MySQL掃描大量不須要的行而後再拋棄掉。若是可使用書籤記錄上一次取數據的位置,那麼下一次就能夠直接從該書籤的位置開始掃描,這樣就能夠避免使用OFFSET。例如,若須要按照租借記錄作翻頁,那麼能夠根據最新一條租借記錄向後追溯,這種作法可行是由於租借記錄的逐漸是單調增加的。首先使用下面的查詢獲取第一組結果:索引

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

該技術的好處是不管翻頁到多麼後面,其性能都會很好。ip

優化SQL_CALC_FOUND_ROWS

分頁的時候,另外一個經常使用的技巧是在LIMIT語句中加上SQL_CALC_FOUND_ROWS提示(hint),這樣作能夠得到去掉LIMIT之後知足條件的行數,所以能夠做爲分頁的總數。看起來,MySQL作了一些很是高深的優化,像是經過某種方法預測了總行數。但實際上MySQL只有在掃描了全部知足條件的行,而後再拋棄掉不須要的行,而不是在知足LIMIT的行數後就終止掃描。全部該提示的代價可能很是高。(幾年前本菜鳥在大學作項目的時候,就是用這個查詢優化器的提示,覺得這樣會減小查詢次數和掃描行數,後來我在工做後操做幾百萬行數的表的時候,這種方法性能不好)

一個更好的設計是將具體的頁面換成「下一頁」按鈕,假設煤業顯示20條記錄,那麼咱們每次查詢都是用LIMIT返回21條記錄並只顯示20條,若是第21條存在,那麼咱們就顯示「下一頁」按鈕,不然就說明沒有更多的數據,也就無需顯示「下一頁」按鈕了。

一種作法是先獲取並緩存較多的數據————例如,緩存1000條————而後每次分頁都從這個緩存中獲取。這樣作可讓應用程序根據結果集的大小採起不一樣的策略,例如結果集少於1000,就能夠在頁面上顯示全部的頁面連接,由於數據都在緩存中,因此這樣作性能不會有問題。若是結果集大於1000,則能夠在頁面上設計一個額外的「找到的結果多餘1000條」之類的按鈕。這兩種策略都比每次生成所有結果集再拋棄掉不須要的數據的效率高不少。

有時候能夠考慮使用EXPLAIN的結果集中的rows列的值做爲結果集總數的近似值(實際上Google的搜索結果總數也是個近似值)。當須要精確結果的時候,在單獨使用COUNT(*)來知足需求,這時若是可以使用索引覆蓋掃描則一般會比SQL_CALC_FOUND_ROWS快得多。

相關文章
相關標籤/搜索