mysql查詢使用select指令,配合limit,offset參數可以讀取指定範圍的記錄。本文將介紹mysql查詢時,offset過大影響效能的原因及最佳化方法。
1.建立表格
#CREATE TABLE `member` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(10) NOT NULL COMMENT '姓名', `gender` tinyint(3) unsigned NOT NULL COMMENT '性别', PRIMARY KEY (`id`), KEY `gender` (`gender`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2.插入1000000筆記錄
<?php $pdo = new PDO("mysql:host=localhost;dbname=user","root",'');for($i=0; $i<1000000; $i++){ $name = substr(md5(time().mt_rand(000,999)),0,10); $gender = mt_rand(1,2); $sqlstr = "insert into member(name,gender) values('".$name."','".$gender."')"; $stmt = $pdo->prepare($sqlstr); $stmt->execute();} ?>mysql> select count(*) from member; +----------+| count(*) | +----------+| 1000000 | +----------+1 row in set (0.23 sec)
#3.目前資料庫版本##
mysql> select version(); +-----------+| version() | +-----------+| 5.6.24 | +-----------+1 row in set (0.01 sec)
1.offset較小的情況
mysql> select * from member where gender=1 limit 10,1; +----+------------+--------+| id | name | gender | +----+------------+--------+| 26 | 509e279687 | 1 | +----+------------+--------+1 row in set (0.00 sec)mysql> select * from member where gender=1 limit 100,1; +-----+------------+--------+| id | name | gender | +-----+------------+--------+| 211 | 07c4cbca3a | 1 | +-----+------------+--------+1 row in set (0.00 sec)mysql> select * from member where gender=1 limit 1000,1; +------+------------+--------+| id | name | gender | +------+------------+--------+| 1975 | e95b8b6ca1 | 1 | +------+------------+--------+1 row in set (0.00 sec)
2.offset較大的狀況
mysql> select * from member where gender=1 limit 100000,1; +--------+------------+--------+| id | name | gender | +--------+------------+--------+| 199798 | 540db8c5bc | 1 | +--------+------------+--------+1 row in set (0.12 sec)mysql> select * from member where gender=1 limit 200000,1; +--------+------------+--------+| id | name | gender | +--------+------------+--------+| 399649 | 0b21fec4c6 | 1 | +--------+------------+--------+1 row in set (0.23 sec)mysql> select * from member where gender=1 limit 300000,1; +--------+------------+--------+| id | name | gender | +--------+------------+--------+| 599465 | f48375bdb8 | 1 | +--------+------------+--------+1 row in set (0.31 sec)
select * from member where gender=1 limit 300000,1;
InnoDB,根據InnoDB索引的結構,查詢過程為:
不過既然二級索引已經找到主鍵值,為什麼還需要先用主鍵索引找到資料塊,再根據offset的值做偏移處理呢?
如果在找到主鍵索引後,先執行offset偏移處理,跳過300000條,再透過第300001條記錄的主鍵索引去讀取資料塊,這樣就能提高效率了。
如果我們只查詢出主鍵,看看有什麼不同mysql> select id from member where gender=1 limit 300000,1; +--------+| id | +--------+| 599465 | +--------+1 row in set (0.09 sec)
#只查詢主鍵的情況 因為二級索引已經找到主鍵值,而查詢只需要讀取主鍵,因此mysql會先執行offset偏移操作,再根據後面的主鍵索引讀取資料塊。
需要查詢所有欄位的情況 因為二級索引只找到主鍵值,但其他欄位的值需要讀取資料區塊才能取得。因此mysql會先讀出資料塊內容,再執行offset偏移操作,最後丟棄前面需要跳過的數據,回傳後面的數據。
buffer pool,存放最近造訪過的資料頁,包括數據頁和索引頁。
為了測試,先把mysql重啟,重啟後再查看buffer pool的內容。mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in('primary','gender') and TABLE_NAME like '%member%' group by index_name; Empty set (0.04 sec)
查詢所有字段,再查看buffer pool的內容
mysql> select * from member where gender=1 limit 300000,1; +--------+------------+--------+| id | name | gender | +--------+------------+--------+| 599465 | f48375bdb8 | 1 | +--------+------------+--------+1 row in set (0.38 sec)mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in('primary','gender') and TABLE_NAME like '%member%' group by index_name; +------------+----------+| index_name | count(*) | +------------+----------+| gender | 261 || PRIMARY | 1385 | +------------+----------+2 rows in set (0.06 sec)
1385個資料頁,261個索引頁。
重啟mysql清空buffer pool,繼續測試只查詢主鍵
mysql> select id from member where gender=1 limit 300000,1; +--------+| id | +--------+| 599465 | +--------+1 row in set (0.08 sec)mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in('primary','gender') and TABLE_NAME like '%member%' group by index_name; +------------+----------+| index_name | count(*) | +------------+----------+| gender | 263 || PRIMARY | 13 | +------------+----------+2 rows in set (0.04 sec)
13個資料頁,263個索引頁。因此減少了多次透過主鍵索引存取資料塊的I/O操作,提高執行效率。
因此可以證實,mysql查詢時,offset過大影響效能的原因是多次透過主鍵索引存取資料區塊的I/O操作。 (注意,只有InnoDB有這個問題,而MYISAM索引結構與InnoDB不同,二級索引都是直接指向資料塊的,因此沒有此問題 )。
InnoDB與MyISAM引擎索引結構比較圖
因此我們先查出偏移後的主鍵,再根據主鍵索引查詢資料區塊的所有內容即可優化。
mysql> select a.* from member as a inner join (select id from member where gender=1 limit 300000,1) as b on a.id=b.id; +--------+------------+--------+| id | name | gender | +--------+------------+--------+| 599465 | f48375bdb8 | 1 | +--------+------------+--------+1 row in set (0.08 sec)
以上是詳解在mysql查詢時,offset過大影響效能的原因與最佳化方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!