mysql查詢(xún)時(shí)offset過(guò)大影響性能的原因和優(yōu)化詳解
前言
mysql查詢(xún)使用select命令,配合limit,offset參數(shù)可以讀取指定范圍的記錄。本文將介紹mysql查詢(xún)時(shí),offset過(guò)大影響性能的原因及優(yōu)化方法。
準(zhǔn)備測(cè)試數(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ù)庫(kù)版本
mysql> select version(); +-----------+ | version() | +-----------+ | 5.6.24 | +-----------+ 1 row in set (0.01 sec)
分析offset過(guò)大影響性能的原因
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較小時(shí),查詢(xún)速度很快,效率較高。
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很大時(shí),會(huì)出現(xiàn)效率問(wèn)題,隨著offset的增大,執(zhí)行效率下降。
分析影響性能原因
select * from member where gender=1 limit 300000,1;
因?yàn)閿?shù)據(jù)表是InnoDB,根據(jù)InnoDB索引的結(jié)構(gòu),查詢(xún)過(guò)程為:
- 通過(guò)二級(jí)索引查到主鍵值(找出所有g(shù)ender=1的id)。
- 再根據(jù)查到的主鍵值通過(guò)主鍵索引找到相應(yīng)的數(shù)據(jù)塊(根據(jù)id找出對(duì)應(yīng)的數(shù)據(jù)塊內(nèi)容)。
- 根據(jù)offset的值,查詢(xún)300001次主鍵索引的數(shù)據(jù),最后將之前的300000條丟棄,取出最后1條。
不過(guò)既然二級(jí)索引已經(jīng)找到主鍵值,為什么還需要先用主鍵索引找到數(shù)據(jù)塊,再根據(jù)offset的值做偏移處理呢?
如果在找到主鍵索引后,先執(zhí)行offset偏移處理,跳過(guò)300000條,再通過(guò)第300001條記錄的主鍵索引去讀取數(shù)據(jù)塊,這樣就能提高效率了。
如果我們只查詢(xún)出主鍵,看看有什么不同
mysql> select id from member where gender=1 limit 300000,1; +--------+ | id | +--------+ | 599465 | +--------+ 1 row in set (0.09 sec)
很明顯,如果只查詢(xún)主鍵,執(zhí)行效率對(duì)比查詢(xún)?nèi)孔侄危泻艽蟮奶嵘?nbsp;
推測(cè)
只查詢(xún)主鍵的情況
因?yàn)槎?jí)索引已經(jīng)找到主鍵值,而查詢(xún)只需要讀取主鍵,因此mysql會(huì)先執(zhí)行offset偏移操作,再根據(jù)后面的主鍵索引讀取數(shù)據(jù)塊。
需要查詢(xún)所有字段的情況
因?yàn)槎?jí)索引只找到主鍵值,但其他字段的值需要讀取數(shù)據(jù)塊才能獲取。因此mysql會(huì)先讀出數(shù)據(jù)塊內(nèi)容,再執(zhí)行offset偏移操作,最后丟棄前面需要跳過(guò)的數(shù)據(jù),返回后面的數(shù)據(jù)。
證實(shí)
InnoDB中有buffer pool,存放最近訪(fǎng)問(wèn)過(guò)的數(shù)據(jù)頁(yè),包括數(shù)據(jù)頁(yè)和索引頁(yè)。
為了測(cè)試,先把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)
可以看到,重啟后,沒(méi)有訪(fǎng)問(wèn)過(guò)任何的數(shù)據(jù)頁(yè)。
查詢(xún)所有字段,再查看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)
可以看出,此時(shí)buffer pool中關(guān)于member表有1385個(gè)數(shù)據(jù)頁(yè),261個(gè)索引頁(yè)。
重啟mysql清空buffer pool,繼續(xù)測(cè)試只查詢(xún)主鍵
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)
可以看出,此時(shí)buffer pool中關(guān)于member表只有13個(gè)數(shù)據(jù)頁(yè),263個(gè)索引頁(yè)。因此減少了多次通過(guò)主鍵索引訪(fǎng)問(wèn)數(shù)據(jù)塊的I/O操作,提高執(zhí)行效率。
因此可以證實(shí),mysql查詢(xún)時(shí),offset過(guò)大影響性能的原因是多次通過(guò)主鍵索引訪(fǎng)問(wèn)數(shù)據(jù)塊的I/O操作。(注意,只有InnoDB有這個(gè)問(wèn)題,而MYISAM索引結(jié)構(gòu)與InnoDB不同,二級(jí)索引都是直接指向數(shù)據(jù)塊的,因此沒(méi)有此問(wèn)題 )。
InnoDB與MyISAM引擎索引結(jié)構(gòu)對(duì)比圖
這里寫(xiě)圖片描述
優(yōu)化方法
根據(jù)上面的分析,我們知道查詢(xún)所有字段會(huì)導(dǎo)致主鍵索引多次訪(fǎng)問(wèn)數(shù)據(jù)塊造成的I/O操作。
因此我們先查出偏移后的主鍵,再根據(jù)主鍵索引查詢(xún)數(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 limit,offset 區(qū)別
SELECT keyword FROM keyword_rank WHERE advertiserid='59' order by keyword LIMIT 2 OFFSET 1;
比如這個(gè)SQL ,limit后面跟的是2條數(shù)據(jù),offset后面是從第1條開(kāi)始讀取
SELECT keyword FROM keyword_rank WHERE advertiserid='59' ORDER BY keyword LIMIT 2 ,1;
而這個(gè)SQL,limit后面是從第2條開(kāi)始讀,讀取1條信息。
這兩個(gè)千萬(wàn)別搞混哦。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
mysql學(xué)習(xí)筆記之?dāng)?shù)據(jù)引擎
插件式存儲(chǔ)引擎是MySQL數(shù)據(jù)庫(kù)最重要的特征之一,用戶(hù)可以根據(jù)應(yīng)用的需要尋找如何存儲(chǔ)和索引數(shù)據(jù)、是否使用事務(wù)等。MySQL默認(rèn)支持多種存儲(chǔ)引擎,以適用于不同領(lǐng)域的數(shù)據(jù)庫(kù)應(yīng)用需求,用戶(hù)可以通過(guò)選擇選擇不同的存儲(chǔ)引擎提供應(yīng)用的效率,提供靈活的存儲(chǔ)2017-02-02mysql如何處理varchar與nvarchar類(lèi)型中的特殊字符
這篇文章主要介紹了mysql如何處理varchar與nvarchar類(lèi)型中的特殊字符,需要的朋友可以參考下2014-12-12navicat連接mysql時(shí)出現(xiàn)1045錯(cuò)誤的解決方法
這篇文章主要為大家詳細(xì)介紹了navicat連接mysql時(shí)出現(xiàn)1045錯(cuò)誤的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02詳解MySQL的字段默認(rèn)null對(duì)唯一索引的影響
這篇文章主要為大家介紹了MySQL的字段默認(rèn)null對(duì)唯一索引的影響詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09Windows10下mysql 8.0.22 安裝配置方法圖文教程
這篇文章主要為大家詳細(xì)介紹了Windows10下mysql 8.0.22 安裝配置方法圖文教程,文中安裝步驟介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-11-11Mysql?InnoDB引擎中頁(yè)目錄和槽的查找過(guò)程
這篇文章主要為大家介紹了Mysql?InnoDB引擎中頁(yè)目錄和槽的查找記錄過(guò)程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05Mysql遷移到TiDB雙寫(xiě)數(shù)據(jù)庫(kù)兜底方案詳解
這篇文章主要為大家介紹了Mysql遷移到TiDB雙寫(xiě)數(shù)據(jù)庫(kù)兜底方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01