詳細(xì)說明在mysql查詢時,offset過大影響性能的原因與優(yōu)化方法
發(fā)表時間:2023-07-11 來源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]mysql查詢使用select命令,配合limit,offset參數(shù)可以讀取指定范圍的記錄。本文將介紹mysql查詢時,offset過大影響性能的原因及優(yōu)化方法。 準(zhǔn)備測試數(shù)據(jù)表及數(shù)據(jù)1.創(chuàng)建表CR...
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",'');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.當(dāng)前數(shù)據(jù)庫版本
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)
當(dāng)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)
當(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> 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> 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> 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)
可以看出,此時buffer pool中關(guān)于member表有1385個數(shù)據(jù)頁,261個索引頁。
重啟mysql清空buffer pool,繼續(xù)測試只查詢主鍵
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)
可以看出,此時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)對比圖
優(yōu)化方法
根據(jù)上面的分析,我們知道查詢所有字段會導(dǎo)致主鍵索引多次訪問數(shù)據(jù)塊造成的I/O操作。
因此我們先查出偏移后的主鍵,再根據(jù)主鍵索引查詢數(shù)據(jù)塊的所有內(nèi)容即可優(yōu)化。
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過大影響性能的原因與優(yōu)化方法 ,更多相關(guān)內(nèi)容請關(guān)注php中文網(wǎng)。
相關(guān)推薦:
關(guān)于php使用正則去除寬高樣式的方法
詳解文件內(nèi)容去重及排序 的相關(guān)內(nèi)容
解讀mysql大小寫敏感配置問題
以上就是詳解在mysql查詢時,offset過大影響性能的原因與優(yōu)化方法的詳細(xì)內(nèi)容,更多請關(guān)注php中文網(wǎng)其它相關(guān)文章!
學(xué)習(xí)教程快速掌握從入門到精通的SQL知識。