詳解在mysql查詢時,offset過大影響效能的原因與最佳化方法

jacklove
發布: 2018-06-08 17:17:01
原創
2102 人瀏覽過

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",&#39;&#39;);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(&#39;".$name."&#39;,&#39;".$gender."&#39;)";    $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)
登入後複製


#分析offset過大影響效能的原因

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)
登入後複製

當offset較小時,查詢速度很快,效率較高。

 

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)
登入後複製

當offset很大時,會出現效率問題,隨著offset的增加,執行效率下降。

 

分析影響效能原因
select * from member where gender=1 limit 300000,1;
登入後複製

因為資料表是

InnoDB,根據InnoDB索引的結構,查詢過程為:

  • 透過二級索引查到主鍵值(找出所有gender=1的id)。

  • 再根據查到的主鍵值透過主鍵索引找到對應的資料區塊(根據id找出對應的資料區塊內容)。

  • 根據offset的值,查詢300001次主鍵索引的數據,最後將先前的300000條丟棄,取出最後1條。

不過既然二級索引已經找到主鍵值,為什麼還需要先用主鍵索引找到資料塊,再根據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偏移操作,最後丟棄前面需要跳過的數據,回傳後面的數據。
 

確認

InnoDB中有

buffer pool,存放最近造訪過的資料頁,包括數據頁和索引頁。

為了測試,先把mysql重啟,重啟後再查看buffer pool的內容。

mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in(&#39;primary&#39;,&#39;gender&#39;) and TABLE_NAME like &#39;%member%&#39; 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(&#39;primary&#39;,&#39;gender&#39;) and TABLE_NAME like &#39;%member%&#39; group by index_name;
+------------+----------+| index_name | count(*) |
+------------+----------+| gender     |      261 || PRIMARY    |     1385 |
+------------+----------+2 rows in set (0.06 sec)
登入後複製

可以看出,此時buffer pool中關於member表有

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(&#39;primary&#39;,&#39;gender&#39;) and TABLE_NAME like &#39;%member%&#39; group by index_name;
+------------+----------+| index_name | count(*) |
+------------+----------+| gender     |      263 || PRIMARY    |       13 |
+------------+----------+2 rows in set (0.04 sec)
登入後複製

可以看出,此時buffer pool中關於member表只有

13個資料頁,263個索引頁。因此減少了多次透過主鍵索引存取資料塊的I/O操作,提高執行效率。

因此可以證實,

mysql查詢時,offset過大影響效能的原因是多次透過主鍵索引存取資料區塊的I/O操作。 (注意,只有InnoDB有這個問題,而MYISAM索引結構與InnoDB不同,二級索引都是直接指向資料塊的,因此沒有此問題 )。  

InnoDB與MyISAM引擎索引結構比較圖

詳解在mysql查詢時,offset過大影響效能的原因與最佳化方法


最佳化方法

根據上面的分析,我們知道查詢所有欄位會導致主鍵索引多次存取資料塊造成的I/O操作。

因此我們先查出偏移後的主鍵,再根據主鍵索引查詢資料區塊的所有內容即可優化。

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中文網。

相關推薦:

關於php使用正規去除寬高樣式的方法

#詳解檔案內容去重及排序的相關內容

#解讀mysql大小寫敏感配置問題

#

以上是詳解在mysql查詢時,offset過大影響效能的原因與最佳化方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板