mysql中分頁查詢有兩種方式, 一種是使用COUNT(*)的方式,具體代碼如下
SELECT COUNT(*) FROM foo WHERE b = 1; SELECT a FROM foo WHERE b = 1 LIMIT 100,10;
另外一種是使用SQL_CALC_FOUND_ROWS
SELECT SQL_CALC_FOUND_ROWS a FROM foo WHERE b = 1 LIMIT 100, 10; SELECT FOUND_ROWS();
第二種方式調用SQL_CALC_FOUND_ROWS之後會將WHERE語句查詢的行數放在FOUND_ROWS()之中,第二次只需要查詢FOUND_ROWS()就可以查出有多少行了。
首先原子性講,第二種肯定比第一種好。第二種能保證查詢語句的原子性,第一種當兩個請求之間有額外的操作修改了表的時候,結果就自然是不准確的了。而第二種則不會。但是非常可惜,一般頁面需要進行分頁顯示的時候,往往並不要求分頁的結果非常准確。即分頁返回的total總數大1或者小1都是無所謂的。所以其實原子性不是我們分頁關注的重點。
下面看效率。這個非常重要,分頁操作在每個網站上的使用都是非常大的,查詢量自然也很大。由於無論哪種,分頁操作必然會有兩次sql查詢,於是就有很多很多關於兩種查詢性能的比較:
SQL_CALC_FOUND_ROWS真的很慢麼?
http://hi.baidu.com/thinkinginlamp/item/b122fdaea5ba23f614329b14
To SQL_CALC_FOUND_ROWS or not to SQL_CALC_FOUND_ROWS?
http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/
老王這篇文章裡面有提到一個covering index的概念,簡單來說就是怎樣才能只讓查詢根據索引返回結果,而不進行表查詢
具體看他的另外一篇文章:
MySQL之Covering Index
http://hi.baidu.com/thinkinginlamp/item/1b9aaf09014acce0f45ba6d3
結合這幾篇文章,做的實驗:
表:
CREATE TABLE IF NOT EXISTS `foo` ( `a` int(10) unsigned NOT NULL AUTO_INCREMENT, `b` int(10) unsigned NOT NULL, `c` varchar(100) NOT NULL, PRIMARY KEY (`a`), KEY `bar` (`b`,`a`) ) ENGINE=MyISAM;
注意下這裡是使用b,a做了一個索引,所以查詢select * 的時候是不會用到covering index的,select a才會使用到covering index
<?php $host = '192.168.100.166'; $dbName = 'test'; $user = 'root'; $password = ''; $db = mysql_connect($host, $user, $password) or die('DB connect failed'); mysql_select_db($dbName, $db); echo '==========================================' . "\r\n"; $start = microtime(true); for ($i =0; $i<1000; $i++) { mysql_query("SELECT SQL_NO_CACHE COUNT(*) FROM foo WHERE b = 1"); mysql_query("SELECT SQL_NO_CACHE a FROM foo WHERE b = 1 LIMIT 100,10"); } $end = microtime(true); echo $end - $start . "\r\n"; echo '==========================================' . "\r\n"; $start = microtime(true); for ($i =0; $i<1000; $i++) { mysql_query("SELECT SQL_NO_CACHE SQL_CALC_FOUND_ROWS a FROM foo WHERE b = 1 LIMIT 100, 10"); mysql_query("SELECT FOUND_ROWS()"); } $end = microtime(true); echo $end - $start . "\r\n"; echo '==========================================' . "\r\n"; $start = microtime(true); for ($i =0; $i<1000; $i++) { mysql_query("SELECT SQL_NO_CACHE COUNT(*) FROM foo WHERE b = 1"); mysql_query("SELECT SQL_NO_CACHE * FROM foo WHERE b = 1 LIMIT 100,10"); } $end = microtime(true); echo $end - $start . "\r\n"; echo '==========================================' . "\r\n"; $start = microtime(true); for ($i =0; $i<1000; $i++) { mysql_query("SELECT SQL_NO_CACHE SQL_CALC_FOUND_ROWS * FROM foo WHERE b = 1 LIMIT 100, 10"); mysql_query("SELECT FOUND_ROWS()"); } $end = microtime(true); echo $end - $start . "\r\n";
返回的結果:
和老王裡面文章說的是一樣的。第四次查詢SQL_CALC_FOUND_ROWS由於不僅是沒有使用到covering index,也需要進行全表查詢,而第三次查詢COUNT(*),且select * 有使用到index,並沒進行全表查詢,所以有這麼大的差別。
PS: 另外提醒下,這裡是使用MyISAM會出現三和四的查詢差別這麼大,但是如果是使用InnoDB的話,就不會有這麼大差別了。
所以我得出的結論是如果數據庫是InnoDB的話,我還是傾向於使用SQL_CALC_FOUND_ROWS
結論:SQL_CALC_FOUND_ROWS和COUNT(*)的性能在都使用covering index的情況下前者高,在沒使用covering index情況下後者性能高。所以使用的時候要注意這個。