使用子查詢優(yōu)化大數(shù)據(jù)量分頁(yè)查詢
創(chuàng)新互聯(lián)公司專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于網(wǎng)站制作、成都網(wǎng)站設(shè)計(jì)、富拉爾基網(wǎng)絡(luò)推廣、成都微信小程序、富拉爾基網(wǎng)絡(luò)營(yíng)銷、富拉爾基企業(yè)策劃、富拉爾基品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運(yùn)營(yíng)等,從售前售中售后,我們都將竭誠(chéng)為您服務(wù),您的肯定,是我們最大的嘉獎(jiǎng);創(chuàng)新互聯(lián)公司為所有大學(xué)生創(chuàng)業(yè)者提供富拉爾基建站搭建服務(wù),24小時(shí)服務(wù)熱線:18982081108,官方網(wǎng)址:www.cdcxhl.com
這種方式的做法是先定位偏移位置的id,然后再往后查詢,適用于id遞增的情況。
使用id限定優(yōu)化大數(shù)據(jù)量分頁(yè)查詢
使用這種方式需要先假設(shè)數(shù)據(jù)表的id是連續(xù)遞增的,我們根據(jù)查詢的頁(yè)數(shù)和查詢的記錄數(shù)可以算出查詢的id的范圍,可以使用 id between and 來(lái)查詢:
當(dāng)然了,也可以使用in的方式來(lái)進(jìn)行查詢,這種方式經(jīng)常用在多表關(guān)聯(lián)的情況下,使用其他表查詢的id集合來(lái)進(jìn)行查詢:
但是使用這種in查詢方式的時(shí)候要注意的是,某些MySQL版本并不支持在in子句中使用limit子句。
參考 sql優(yōu)化之大數(shù)據(jù)量分頁(yè)查詢(mysql) - yanggb - 博客園 (cnblogs.com)
準(zhǔn)備數(shù)據(jù)是20000000條數(shù)據(jù)
在分頁(yè)場(chǎng)景下,使用limit start end,我們分別看下從10000, 100000, 1000000開(kāi)始分頁(yè)的執(zhí)行時(shí)間(每頁(yè)取10條),如下圖
當(dāng)start較小時(shí),查詢沒(méi)有性能問(wèn)題,但是如上圖查詢時(shí)間所示,隨著start增大,查詢消耗時(shí)間也在遞增,在start=10000000時(shí),分頁(yè)竟然消耗了2秒多,這是不能忍受的。
由此引出對(duì)limit分頁(yè)的優(yōu)化,首先來(lái)explain該語(yǔ)句,看到查詢沒(méi)有使用到任何的索引,進(jìn)行的是全表掃描,假如limit分頁(yè)用到了索引是不是會(huì)快很多呢!
explain分析一下,第一行是select * from user_innodb形成的臨時(shí)表使用的是全表掃描,第二行是 (SELECT id FROM user_innodb LIMIT 10000000, 10)形成的,使用的是eq_ref,第三行是全表掃描a和bjoin形成的派生表,使用到的是index,所以速度也會(huì)快很多
一般剛開(kāi)始學(xué)SQL的時(shí)候,會(huì)這樣寫
代碼如下:
SELECT * FROM table ORDER BY id LIMIT 1000, 10;
但在數(shù)據(jù)達(dá)到百萬(wàn)級(jí)的時(shí)候,這樣寫會(huì)慢死
代碼如下:
SELECT * FROM table ORDER BY id LIMIT 1000000, 10;
也許耗費(fèi)幾十秒
網(wǎng)上很多優(yōu)化的方法是這樣的
代碼如下:
SELECT * FROM table WHERE id = (SELECT id FROM table LIMIT 1000000, 1) LIMIT 10;
是的,速度提升到0.x秒了,看樣子還行了
可是,還不是完美的!
以下這句才是完美的!
代碼如下:
SELECT * FROM table WHERE id BETWEEN 1000000 AND 1000010;
比上面那句,還要再快5至10倍
另外,如果需要查詢 id 不是連續(xù)的一段,最佳的方法就是先找出 id ,然后用 in 查詢
代碼如下:
SELECT * FROM table WHERE id IN(10000, 100000, 1000000...);
再分享一點(diǎn)
查詢字段一較長(zhǎng)字符串的時(shí)候,表設(shè)計(jì)時(shí)要為該字段多加一個(gè)字段,如,存儲(chǔ)網(wǎng)址的字段
查詢的時(shí)候,不要直接查詢字符串,效率低下,應(yīng)該查看該字串的crc32或md5
如何優(yōu)化Mysql千萬(wàn)級(jí)快速分頁(yè)
Limit 1,111 數(shù)據(jù)大了確實(shí)有些性能上的問(wèn)題,而通過(guò)各種方法給用上where id = XX,這樣用上索引的id號(hào)可能速度上快點(diǎn)兒。By:jack
Mysql limit分頁(yè)慢的解決辦法(Mysql limit 優(yōu)化,百萬(wàn)至千萬(wàn)條記錄實(shí)現(xiàn)快速分頁(yè))
MySql 性能到底能有多高?用了php半年多,真正如此深入地去思考這個(gè)問(wèn)題還是從前天開(kāi)始。有過(guò)痛苦有過(guò)絕望,到現(xiàn)在充滿信心!MySql 這個(gè)數(shù)據(jù)庫(kù)絕對(duì)是適合dba級(jí)的高手去玩的,一般做一點(diǎn)1萬(wàn)篇新聞的小型系統(tǒng)怎么寫都可以,用xx框架可以實(shí)現(xiàn)快速開(kāi)發(fā)??墒菙?shù)據(jù)量到了10萬(wàn),百萬(wàn)至千 萬(wàn),它的性能還能那么高嗎?一點(diǎn)小小的失誤,可能造成整個(gè)系統(tǒng)的改寫,甚至更本系統(tǒng)無(wú)法正常運(yùn)行!好了,不那么多廢話了。用事實(shí)說(shuō)話,看例子:
數(shù) 據(jù)表 collect ( id, title ,info ,vtype) 就這4個(gè)字段,其中 title 用定長(zhǎng),info 用text, id 是逐漸,vtype是tinyint,vtype是索引。這是一個(gè)基本的新聞系統(tǒng)的簡(jiǎn)單模型。現(xiàn)在往里面填充數(shù)據(jù),填充10萬(wàn)篇新聞。
最后collect 為 10萬(wàn)條記錄,數(shù)據(jù)庫(kù)表占用硬盤1.6G。OK ,看下面這條sql語(yǔ)句:
select id,title from collect limit 1000,10; 很快;基本上0.01秒就OK,再看下面的
select id,title from collect limit 90000,10; 從9萬(wàn)條開(kāi)始分頁(yè),結(jié)果?
8-9秒完成,my god 哪出問(wèn)題了????其實(shí)要優(yōu)化這條數(shù)據(jù),網(wǎng)上找得到答案??聪旅嬉粭l語(yǔ)句:
select id from collect order by id limit 90000,10; 很快,0.04秒就OK。 為什么?因?yàn)橛昧薸d主鍵做索引當(dāng)然快。網(wǎng)上的改法是:
select id,title from collect where id=(select id from collect order by id limit 90000,1) limit 10;
這就是用了id做索引的結(jié)果。可是問(wèn)題復(fù)雜那么一點(diǎn)點(diǎn),就完了??聪旅娴恼Z(yǔ)句
select id from collect where vtype=1 order by id limit 90000,10; 很慢,用了8-9秒!
到 了這里我相信很多人會(huì)和我一樣,有崩潰感覺(jué)!vtype 做了索引了???怎么會(huì)慢呢?vtype做了索引是不錯(cuò),你直接 select id from collect where vtype=1 limit 1000,10; 是很快的,基本上0.05秒,可是提高90倍,從9萬(wàn)開(kāi)始,那就是0.05*90=4.5秒的速度了。和測(cè)試結(jié)果8-9秒到了一個(gè)數(shù)量級(jí)。從這里開(kāi)始有人 提出了分表的思路,這個(gè)和dis #cuz 論壇是一樣的思路。思路如下:
建一個(gè)索引表: t (id,title,vtype) 并設(shè)置成定長(zhǎng),然后做分頁(yè),分頁(yè)出結(jié)果再到 collect 里面去找info 。 是否可行呢?實(shí)驗(yàn)下就知道了。
10萬(wàn)條記錄到 t(id,title,vtype) 里,數(shù)據(jù)表大小20M左右。用
select id from t where vtype=1 order by id limit 90000,10; 很快了?;旧?.1-0.2秒可以跑完。為什么會(huì)這樣呢?我猜想是因?yàn)閏ollect 數(shù)據(jù)太多,所以分頁(yè)要跑很長(zhǎng)的路。limit 完全和數(shù)據(jù)表的大小有關(guān)的。其實(shí)這樣做還是全表掃描,只是因?yàn)閿?shù)據(jù)量小,只有10萬(wàn)才快。OK, 來(lái)個(gè)瘋狂的實(shí)驗(yàn),加到100萬(wàn)條,測(cè)試性能。
加了10倍的數(shù)據(jù),馬上t表就到了200多M,而且是定長(zhǎng)。還是剛才的查詢語(yǔ)句,時(shí)間是0.1-0.2秒完成!分表性能沒(méi)問(wèn)題?錯(cuò)!因?yàn)槲覀兊膌imit還是9萬(wàn),所以快。給個(gè)大的,90萬(wàn)開(kāi)始
select id from t where vtype=1 order by id limit 900000,10; 看看結(jié)果,時(shí)間是1-2秒!
why ?? 分表了時(shí)間還是這么長(zhǎng),非常之郁悶!有人說(shuō)定長(zhǎng)會(huì)提高limit的性能,開(kāi)始我也以為,因?yàn)橐粭l記錄的長(zhǎng)度是固定的,mysql 應(yīng)該可以算出90萬(wàn)的位置才對(duì)??? 可是我們高估了mysql 的智能,他不是商務(wù)數(shù)據(jù)庫(kù),事實(shí)證明定長(zhǎng)和非定長(zhǎng)對(duì)limit影響不大? 怪不得有人說(shuō) discuz到了100萬(wàn)條記錄就會(huì)很慢,我相信這是真的,這個(gè)和數(shù)據(jù)庫(kù)設(shè)計(jì)有關(guān)!
難道MySQL 無(wú)法突破100萬(wàn)的限制嗎???到了100萬(wàn)的分頁(yè)就真的到了極限???
答案是: NO !!!! 為什么突破不了100萬(wàn)是因?yàn)椴粫?huì)設(shè)計(jì)mysql造成的。下面介紹非分表法,來(lái)個(gè)瘋狂的測(cè)試!一張表搞定100萬(wàn)記錄,并且10G 數(shù)據(jù)庫(kù),如何快速分頁(yè)!
好了,我們的測(cè)試又回到 collect表,開(kāi)始測(cè)試結(jié)論是: 30萬(wàn)數(shù)據(jù),用分表法可行,超過(guò)30萬(wàn)他的速度會(huì)慢到你無(wú)法忍受!當(dāng)然如果用分表+我這種方法,那是絕對(duì)完美的。但是用了我這種方法后,不用分表也可以完美解決!
答 案就是:復(fù)合索引! 有一次設(shè)計(jì)mysql索引的時(shí)候,無(wú)意中發(fā)現(xiàn)索引名字可以任取,可以選擇幾個(gè)字段進(jìn)來(lái),這有什么用呢?開(kāi)始的select id from collect order by id limit 90000,10; 這么快就是因?yàn)樽吡怂饕?,可是如果加了where 就不走索引了。抱著試試看的想法加了 search(vtype,id) 這樣的索引。然后測(cè)試
select id from collect where vtype=1 limit 90000,10; 非???!0.04秒完成!
再測(cè)試: select id ,title from collect where vtype=1 limit 90000,10; 非常遺憾,8-9秒,沒(méi)走search索引!
再測(cè)試:search(id,vtype),還是select id 這個(gè)語(yǔ)句,也非常遺憾,0.5秒。
綜上:如果對(duì)于有where 條件,又想走索引用limit的,必須設(shè)計(jì)一個(gè)索引,將where 放第一位,limit用到的主鍵放第2位,而且只能select 主鍵!
完美解決了分頁(yè)問(wèn)題了??梢钥焖俜祷豬d就有希望優(yōu)化limit , 按這樣的邏輯,百萬(wàn)級(jí)的limit 應(yīng)該在0.0x秒就可以分完??磥?lái)mysql 語(yǔ)句的優(yōu)化和索引是非常重要的!
好了,回到原題,如何將上面的研究成功快速應(yīng)用于開(kāi)發(fā)呢?如果用復(fù)合查詢,我的輕量級(jí)框架就沒(méi)得用了。分頁(yè)字符串還得自己寫,那多麻煩?這里再看一個(gè)例子,思路就出來(lái)了:
select * from collect where id in (9000,12,50,7000); 竟然 0秒就可以查完!
mygod ,mysql 的索引竟然對(duì)于in語(yǔ)句同樣有效!看來(lái)網(wǎng)上說(shuō)in無(wú)法用索引是錯(cuò)誤的!
有了這個(gè)結(jié)論,就可以很簡(jiǎn)單的應(yīng)用于輕量級(jí)框架了:
代碼如下:
復(fù)制代碼代碼如下:
$db=dblink();
$db-pagesize=20;
$sql=”select id from collect where vtype=$vtype”;
$db-execute($sql);
$strpage=$db-strpage(); //將分頁(yè)字符串保存在臨時(shí)變量,方便輸出
while($rs=$db-fetch_array()){
$strid.=$rs['id'].',';
}
$strid=substr($strid,0,strlen($strid)-1); //構(gòu)造出id字符串
$db-pagesize=0; //很關(guān)鍵,在不注銷類的情況下,將分頁(yè)清空,這樣只需要用一次數(shù)據(jù)庫(kù)連接,不需要再開(kāi);
$db-execute(“select id,title,url,sTime,gTime,vtype,tag from collect where id in ($strid)”);
?php while($rs=$db-fetch_array()): ?
?php echo $rs['id'];?
?php echo $rs['url'];?
?php echo $rs['sTime'];?
?php echo $rs['gTime'];?
?php echo $rs['vtype'];?
” target=”_blank”?php echo $rs['title'];?
?php echo $rs['tag'];?
?php endwhile; ?
?php
echo $strpage;
offset+limit方式的分頁(yè)查詢,當(dāng)數(shù)據(jù)表超過(guò)100w條記錄,性能會(huì)很差。
主要原因是offset limit的分頁(yè)方式是從頭開(kāi)始查詢,然后舍棄前offset個(gè)記錄,所以offset偏移量越大,查詢速度越慢。
比如: 讀第10000到10019行元素(pk是主鍵/唯一鍵).
使用order by id可以在查詢時(shí)使用主鍵索引。
但是這種方式在id為uuid的時(shí)候就會(huì)出現(xiàn)問(wèn)題??梢允褂脀here in的方式解決:
帶條件的查詢:
如果在分頁(yè)查詢中添加了where條件例如 type = 'a’這樣的條件,sql變成 :
這種情況因?yàn)閠ype沒(méi)有使用索引也會(huì)導(dǎo)致查詢速度變慢。但是只添加type為索引查詢速度還是很慢,是因?yàn)椴樵兊臄?shù)據(jù)量太多了。這個(gè)時(shí)候考慮添加組合索引,組合索引的順序要where條件字段在前,id在后,如 (type,id),因?yàn)榻M合索引查詢時(shí)用到了type索引,而type跟id是組合索引的關(guān)系,如果只select id ,那么直接就可以按組合索引返回id,而不需要再進(jìn)行一次查詢?nèi)シ祷豬d
使用uuid作為主鍵不僅會(huì)帶來(lái)性能上的問(wèn)題,在查詢時(shí)也會(huì)遇到問(wèn)題。
因?yàn)樵谑褂胹elect id from table limit 10000,10 查詢id數(shù)據(jù)時(shí),默認(rèn)是對(duì)id進(jìn)行排序,返回的是排序后的id結(jié)果,如果我們想按插入順序查詢結(jié)果,這樣查詢出來(lái)的結(jié)果就與我們的需求不相符。
聚集索引跟非聚集索引:聚集索引類似與新華字典的拼音,根據(jù)拼音搜索到的信息都是連續(xù)的,可以很快獲取到它前后的信息。非聚集索引類似于部首查詢,信息存放的位置可能不在一個(gè)區(qū)域。對(duì)經(jīng)常使用范圍查詢的字段考慮使用聚集索引。
InnoDB中索引分為聚簇索引(主鍵索引)和非聚簇索引(非主鍵索引),聚簇索引的葉子節(jié)點(diǎn)中保存的是整行記錄,而非聚簇索引的葉子節(jié)點(diǎn)中保存的是該行記錄的主鍵的值。
如果您的表上定義有主鍵,該主鍵索引是聚集索引。
如果你不定義為您的表的主鍵時(shí),MySQL取第一個(gè)唯一索引(unique)而且只含非空列(NOT NULL)作為主鍵,InnoDB使用它作為聚集索引。
如果沒(méi)有這樣的列,InnoDB就自己產(chǎn)生一個(gè)這樣的ID值,
優(yōu)先選index key_len小的索引進(jìn)行count(*),盡量不使用聚簇索引
在沒(méi)有where條件的情況下,count(*)和count(常量),如果有非聚簇索引,mysql會(huì)自動(dòng)選擇非聚簇索引,因?yàn)榉蔷鄞厮饕嫉目臻g小,如果沒(méi)有非聚簇索引會(huì)使用聚集索引。count(primary key)主鍵id為聚集索引,使用聚集索引。有where條件的情況下,是否使用索引會(huì)根據(jù)where條件判斷。