2009年3月15日 星期日

增進mysql查詢效能:上一頁下一頁,比分頁好太多了!(limit offset performance)

mysql在limit的offset效能會非常的差,在web實作的應用,常常是分頁會用到的。

實作的問題

假設資料庫的結構是這樣子...

postid, userid, user_posttime, post_title, post_content postid是primary key index建在 userid + userposttime (combine的)

列出使用者文章的sql是(最新的文章排前面,每頁10筆)

第一頁是... select * from table where userid='girvan' order by user_posttime desc limit 0,10

第二頁是...select * from table where userid='girvan' order by user_posttime desc limit 10,10

但是如果... 使用者的文章很多,然後第951頁的sql是...

select * from table where userid='girvan' order by user_posttime desc limit 9500,10

你將會在你的slow query裡面看到這一筆。

而且... 可怕的不是使用者會點選,而是搜尋引擎的機器人,機器人很有心... 會幫你每一頁都執行過。

我曾經去查過這項問題,結論是... 不要使用!那我們該怎麼做分頁?

後來由幾個地方得到靈感...

  • blogger的部落格只有上一頁、下一頁
  • Google的搜尋結果,只讓你看前20頁的結果
  • Gmail裡面只有上一頁和下一頁
  • 無名小站會限制使用者文章上限、留言總數

可能是offset的問題造成這類的限制。

 

解法

第一頁的查詢跟原本一樣。然而第一頁裡的第二頁的連結可能會改成...

index.php?time=155815154&p=2

time的時間是第1頁裡第10筆的時間

然而第二頁的sql查詢就會變成這樣子...

select * from table where userid='girvan' and user_posttime < 155815154 order by user_posttime desc limit 10;

因為index是建combined的,所以這類的查詢非常的快。以此類推,到第951頁的查詢,也會跟第二頁一樣快。

常見問題

在第3頁裡的第二頁連結的sql會變成這樣子... select * from table where userid='girvan' and user_posttime > 153514825 order by user_posttime asc limit 0,10; 然後搜尋的結果記得倒過來!(php可以用array_reverse() )

如果第10篇和第11篇的時間是一樣的,從第1頁到第2頁裡,第11篇會不見。(Google Blogger)就有這個問題。

其它的不多說,實作就會知道了。