詳解在mysql查詢時,offset過大影響性能的原因與優(yōu)化方法

mysql查詢使用select命令,配合limit,offset參數(shù)可以讀取指定范圍的記錄。本文將介紹mysql查詢時,offset過大影響性能的原因及優(yōu)化方法。?

準(zhǔn)備測試數(shù)據(jù)表及數(shù)據(jù)

1.創(chuàng)建表

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-&gt;execute();} ?&gt;mysql&gt;?select?count(*)?from?member; +----------+|?count(*)?| +----------+|??1000000?| +----------+1?row?in?set?(0.23?sec)

?
3.當(dāng)前數(shù)據(jù)庫版本

mysql&gt;?select?version(); +-----------+|?version()?| +-----------+|?5.6.24????| +-----------+1?row?in?set?(0.01?sec)

分析offset過大影響性能的原因

1.offset較小的情況

mysql&gt;?select?*?from?member?where?gender=1?limit?10,1; +----+------------+--------+|?id?|?name???????|?gender?| +----+------------+--------+|?26?|?509e279687?|??????1?| +----+------------+--------+1?row?in?set?(0.00?sec)mysql&gt;?select?*?from?member?where?gender=1?limit?100,1; +-----+------------+--------+|?id??|?name???????|?gender?| +-----+------------+--------+|?211?|?07c4cbca3a?|??????1?| +-----+------------+--------+1?row?in?set?(0.00?sec)mysql&gt;?select?*?from?member?where?gender=1?limit?1000,1; +------+------------+--------+|?id???|?name???????|?gender?| +------+------------+--------+|?1975?|?e95b8b6ca1?|??????1?| +------+------------+--------+1?row?in?set?(0.00?sec)

當(dāng)offset較小時,查詢速度很快,效率較高。
?
2.offset較大的情況

mysql&gt;?select?*?from?member?where?gender=1?limit?100000,1; +--------+------------+--------+|?id?????|?name???????|?gender?| +--------+------------+--------+|?199798?|?540db8c5bc?|??????1?| +--------+------------+--------+1?row?in?set?(0.12?sec)mysql&gt;?select?*?from?member?where?gender=1?limit?200000,1; +--------+------------+--------+|?id?????|?name???????|?gender?| +--------+------------+--------+|?399649?|?0b21fec4c6?|??????1?| +--------+------------+--------+1?row?in?set?(0.23?sec)mysql&gt;?select?*?from?member?where?gender=1?limit?300000,1; +--------+------------+--------+|?id?????|?name???????|?gender?| +--------+------------+--------+|?599465?|?f48375bdb8?|??????1?| +--------+------------+--------+1?row?in?set?(0.31?sec)

當(dāng)offset很大時,會出現(xiàn)效率問題,隨著offset的增大,執(zhí)行效率下降。
?

分析影響性能原因

select?*?from?member?where?gender=1?limit?300000,1;

因為數(shù)據(jù)表是InnoDB,根據(jù)InnoDB索引的結(jié)構(gòu),查詢過程為:

  • 通過二級索引查到主鍵值(找出所有g(shù)ender=1的id)。

  • 再根據(jù)查到的主鍵值通過主鍵索引找到相應(yīng)的數(shù)據(jù)塊(根據(jù)id找出對應(yīng)的數(shù)據(jù)塊內(nèi)容)。

  • 根據(jù)offset的值,查詢300001次主鍵索引的數(shù)據(jù),最后將之前的300000條丟棄,取出最后1條。

不過既然二級索引已經(jīng)找到主鍵值,為什么還需要先用主鍵索引找到數(shù)據(jù)塊,再根據(jù)offset的值做偏移處理呢?

如果在找到主鍵索引后,先執(zhí)行offset偏移處理,跳過300000條,再通過第300001條記錄的主鍵索引去讀取數(shù)據(jù)塊,這樣就能提高效率了。

如果我們只查詢出主鍵,看看有什么不同

mysql&gt;?select?id?from?member?where?gender=1?limit?300000,1; +--------+|?id?????| +--------+|?599465?| +--------+1?row?in?set?(0.09?sec)

很明顯,如果只查詢主鍵,執(zhí)行效率對比查詢?nèi)孔侄?,有很大的提升?
?

推測

只查詢主鍵的情況
因為二級索引已經(jīng)找到主鍵值,而查詢只需要讀取主鍵,因此mysql會先執(zhí)行offset偏移操作,再根據(jù)后面的主鍵索引讀取數(shù)據(jù)塊。

需要查詢所有字段的情況
因為二級索引只找到主鍵值,但其他字段的值需要讀取數(shù)據(jù)塊才能獲取。因此mysql會先讀出數(shù)據(jù)塊內(nèi)容,再執(zhí)行offset偏移操作,最后丟棄前面需要跳過的數(shù)據(jù),返回后面的數(shù)據(jù)。
?

證實

InnoDB中有buffer pool,存放最近訪問過的數(shù)據(jù)頁,包括數(shù)據(jù)頁和索引頁。

為了測試,先把mysql重啟,重啟后查看buffer pool的內(nèi)容。

mysql&gt;?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)

可以看到,重啟后,沒有訪問過任何的數(shù)據(jù)頁。

查詢所有字段,再查看buffer pool的內(nèi)容

mysql&gt;?select?*?from?member?where?gender=1?limit?300000,1; +--------+------------+--------+|?id?????|?name???????|?gender?| +--------+------------+--------+|?599465?|?f48375bdb8?|??????1?| +--------+------------+--------+1?row?in?set?(0.38?sec)mysql&gt;?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)

可以看出,此時buffer pool中關(guān)于member表有1385個數(shù)據(jù)頁,261個索引頁。
?
重啟mysql清空buffer pool,繼續(xù)測試只查詢主鍵

mysql&gt;?select?id?from?member?where?gender=1?limit?300000,1; +--------+|?id?????| +--------+|?599465?| +--------+1?row?in?set?(0.08?sec)mysql&gt;?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)

可以看出,此時buffer pool中關(guān)于member表只有13個數(shù)據(jù)頁,263個索引頁。因此減少了多次通過主鍵索引訪問數(shù)據(jù)塊的I/O操作,提高執(zhí)行效率。

因此可以證實,mysql查詢時,offset過大影響性能的原因是多次通過主鍵索引訪問數(shù)據(jù)塊的I/O操作。(注意,只有InnoDB有這個問題,而MYISAM索引結(jié)構(gòu)與InnoDB不同,二級索引都是直接指向數(shù)據(jù)塊的,因此沒有此問題 )。
?
InnoDB與MyISAM引擎索引結(jié)構(gòu)對比圖

詳解在mysql查詢時,offset過大影響性能的原因與優(yōu)化方法

優(yōu)化方法

根據(jù)上面的分析,我們知道查詢所有字段會導(dǎo)致主鍵索引多次訪問數(shù)據(jù)塊造成的I/O操作。

因此我們先查出偏移后的主鍵,再根據(jù)主鍵索引查詢數(shù)據(jù)塊的所有內(nèi)容即可優(yōu)化。

mysql&gt;?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過大影響性能的原因與優(yōu)化方法?,更多相關(guān)內(nèi)容請關(guān)注php中文網(wǎng)。

相關(guān)推薦:

關(guān)于php使用正則去除寬高樣式的方法

關(guān)于php使用正則去除寬高樣式的方法

關(guān)于php使用正則去除寬高樣式的方法

以上就是詳解在

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點贊7 分享