0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

MySQL用limit為什么會(huì)影響性能

Linux愛好者 ? 來源:簡(jiǎn)書 ? 作者:Muscleape ? 2022-06-20 16:31 ? 次閱讀

有一張財(cái)務(wù)流水表,未分庫分表,目前的數(shù)據(jù)量為9555695,分頁查詢使用到了limit,優(yōu)化之前的查詢耗時(shí)16 s 938 ms(execution: 16 s 831 ms, fetching: 107 ms),按照下文的方式調(diào)整SQL后,耗時(shí)347 ms(execution: 163 ms, fetching: 184 ms);

操作:查詢條件放到子查詢中,子查詢只查主鍵ID,然后使用子查詢中確定的主鍵關(guān)聯(lián)查詢其他的屬性字段;

原理:減少回表操作,利用延遲關(guān)聯(lián)或者子查詢優(yōu)化超多分頁場(chǎng)景。

--優(yōu)化前SQL
SELECT各種字段
FROM`table_name`
WHERE各種條件
LIMIT0,10;
--優(yōu)化后SQL
SELECT各種字段
FROM`table_name`main_tale
RIGHTJOIN
(
SELECT子查詢只查主鍵
FROM`table_name`
WHERE各種條件
LIMIT0,10;
)temp_tableONtemp_table.主鍵=main_table.主鍵

找到的原理分析:MySQL 用 limit 為什么會(huì)影響性能?

前言

首先說明一下MySQL的版本:

mysql>selectversion();
+-----------+
|version()|
+-----------+
|5.7.17|
+-----------+
1rowinset(0.00sec)

表結(jié)構(gòu):

mysql>desctest;
+--------+---------------------+------+-----+---------+----------------+
|Field|Type|Null|Key|Default|Extra|
+--------+---------------------+------+-----+---------+----------------+
|id|bigint(20)unsigned|NO|PRI|NULL|auto_increment|
|val|int(10)unsigned|NO|MUL|0||
|source|int(10)unsigned|NO||0||
+--------+---------------------+------+-----+---------+----------------+
3rowsinset(0.00sec)

id為自增主鍵,val為非唯一索引。

灌入大量數(shù)據(jù),共500萬:

mysql>selectcount(*)fromtest;
+----------+
|count(*)|
+----------+
|5242882|
+----------+
1rowinset(4.25sec)

我們知道,當(dāng)limit offset rows中的offset很大時(shí),會(huì)出現(xiàn)效率問題:

mysql>select*fromtestwhereval=4limit300000,5;
+---------+-----+--------+
|id|val|source|
+---------+-----+--------+
|3327622|4|4|
|3327632|4|4|
|3327642|4|4|
|3327652|4|4|
|3327662|4|4|
+---------+-----+--------+
5rowsinset(15.98sec)

為了達(dá)到相同的目的,我們一般會(huì)改寫成如下語句:

mysql>select*fromtestainnerjoin(selectidfromtestwhereval=4limit300000,5)bona.id=b.id;
+---------+-----+--------+---------+
|id|val|source|id|
+---------+-----+--------+---------+
|3327622|4|4|3327622|
|3327632|4|4|3327632|
|3327642|4|4|3327642|
|3327652|4|4|3327652|
|3327662|4|4|3327662|
+---------+-----+--------+---------+
5rowsinset(0.38sec)

時(shí)間相差很明顯。

為什么會(huì)出現(xiàn)上面的結(jié)果?我們看一下select * from test where val=4 limit 300000,5;的查詢過程:

查詢到索引葉子節(jié)點(diǎn)數(shù)據(jù)。根據(jù)葉子節(jié)點(diǎn)上的主鍵值去聚簇索引上查詢需要的全部字段值。

類似于下面這張圖:fdbcabee-efc7-11ec-ba43-dac502259ad0.jpg

像上面這樣,需要查詢300005次索引節(jié)點(diǎn),查詢300005次聚簇索引的數(shù)據(jù),最后再將結(jié)果過濾掉前300000條,取出最后5條。MySQL耗費(fèi)了大量隨機(jī)I/O在查詢聚簇索引的數(shù)據(jù)上,而有300000次隨機(jī)I/O查詢到的數(shù)據(jù)是不會(huì)出現(xiàn)在結(jié)果集當(dāng)中的。

肯定會(huì)有人問:既然一開始是利用索引的,為什么不先沿著索引葉子節(jié)點(diǎn)查詢到最后需要的5個(gè)節(jié)點(diǎn),然后再去聚簇索引中查詢實(shí)際數(shù)據(jù)。這樣只需要5次隨機(jī)I/O,類似于下面圖片的過程:

fdd667fa-efc7-11ec-ba43-dac502259ad0.jpg

其實(shí)我也想問這個(gè)問題。

證實(shí)

下面我們實(shí)際操作一下來證實(shí)上述的推論:

為了證實(shí)select * from test where val=4 limit 300000,5是掃描300005個(gè)索引節(jié)點(diǎn)和300005個(gè)聚簇索引上的數(shù)據(jù)節(jié)點(diǎn),我們需要知道MySQL有沒有辦法統(tǒng)計(jì)在一個(gè)sql中通過索引節(jié)點(diǎn)查詢數(shù)據(jù)節(jié)點(diǎn)的次數(shù)。我先試了Handler_read_*系列,很遺憾沒有一個(gè)變量能滿足條件。

我只能通過間接的方式來證實(shí):

InnoDB中有buffer pool。里面存有最近訪問過的數(shù)據(jù)頁,包括數(shù)據(jù)頁和索引頁。所以我們需要運(yùn)行兩個(gè)sql,來比較buffer pool中的數(shù)據(jù)頁的數(shù)量。

預(yù)測(cè)結(jié)果是運(yùn)行select * from test a inner join (select id from test where val=4 limit 300000,5);之后,buffer pool中的數(shù)據(jù)頁的數(shù)量遠(yuǎn)遠(yuǎn)少于select * from test where val=4 limit 300000,5;對(duì)應(yīng)的數(shù)量,因?yàn)榍耙粋€(gè)sql只訪問5次數(shù)據(jù)頁,而后一個(gè)sql訪問300005次數(shù)據(jù)頁。

select*fromtestwhereval=4limit300000,5
mysql>selectindex_name,count(*)frominformation_schema.INNODB_BUFFER_PAGEwhereINDEX_NAMEin('val','primary')andTABLE_NAMElike'%test%'groupbyindex_name;Emptyset(0.04sec)

可以看出,目前buffer pool中沒有關(guān)于test表的數(shù)據(jù)頁。

mysql>select*fromtestwhereval=4limit300000,5;
+---------+-----+--------+
|id|val|source|
+---------+-----+--------+|
3327622|4|4|
|3327632|4|4|
|3327642|4|4|
|3327652|4|4|
|3327662|4|4|
+---------+-----+--------+
5rowsinset(26.19sec)

mysql>selectindex_name,count(*)frominformation_schema.INNODB_BUFFER_PAGEwhereINDEX_NAMEin('val','primary')andTABLE_NAMElike'%test%'groupbyindex_name;
+------------+----------+
|index_name|count(*)|
+------------+----------+
|PRIMARY|4098|
|val|208|
+------------+----------+2rowsinset(0.04sec)

可以看出,此時(shí)buffer pool中關(guān)于test表有4098個(gè)數(shù)據(jù)頁,208個(gè)索引頁。

select * from test a inner join (select id from test where val=4 limit 300000,5) ;為了防止上次試驗(yàn)的影響,我們需要清空buffer pool,重啟mysql。

mysqladminshutdown
/usr/local/bin/mysqld_safe&
mysql>selectindex_name,count(*)frominformation_schema.INNODB_BUFFER_PAGEwhereINDEX_NAMEin('val','primary')andTABLE_NAMElike'%test%'groupbyindex_name;

Emptyset(0.03sec)

運(yùn)行sql:

mysql>select*fromtestainnerjoin(selectidfromtestwhereval=4limit300000,5)bona.id=b.id;
+---------+-----+--------+---------+
|id|val|source|id|
+---------+-----+--------+---------+
|3327622|4|4|3327622|
|3327632|4|4|3327632|
|3327642|4|4|3327642|
|3327652|4|4|3327652|
|3327662|4|4|3327662|
+---------+-----+--------+---------+
5rowsinset(0.09sec)

mysql>selectindex_name,count(*)frominformation_schema.INNODB_BUFFER_PAGEwhereINDEX_NAMEin('val','primary')andTABLE_NAMElike'%test%'groupbyindex_name;
+------------+----------+
|index_name|count(*)|
+------------+----------+
|PRIMARY|5|
|val|390|
+------------+----------+
2rowsinset(0.03sec)

我們可以看明顯的看出兩者的差別:第一個(gè)sql加載了4098個(gè)數(shù)據(jù)頁到buffer pool,而第二個(gè)sql只加載了5個(gè)數(shù)據(jù)頁到buffer pool。符合我們的預(yù)測(cè)。也證實(shí)了為什么第一個(gè)sql會(huì)慢:讀取大量的無用數(shù)據(jù)行(300000),最后卻拋棄掉。而且這會(huì)造成一個(gè)問題:加載了很多熱點(diǎn)不是很高的數(shù)據(jù)頁到buffer pool,會(huì)造成buffer pool的污染,占用buffer pool的空間。

遇到的問題

為了在每次重啟時(shí)確保清空buffer pool,我們需要關(guān)閉innodb_buffer_pool_dump_at_shutdown和innodb_buffer_pool_load_at_startup,這兩個(gè)選項(xiàng)能夠控制數(shù)據(jù)庫關(guān)閉時(shí)dump出buffer pool中的數(shù)據(jù)和在數(shù)據(jù)庫開啟時(shí)載入在磁盤上備份buffer pool的數(shù)據(jù)。

原文標(biāo)題:一次 SQL 查詢優(yōu)化原理分析:900W+ 數(shù)據(jù),從 17s 到 300ms

文章出處:【微信公眾號(hào):Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

審核編輯:湯梓紅
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 數(shù)據(jù)
    +關(guān)注

    關(guān)注

    8

    文章

    6767

    瀏覽量

    88642
  • SQL
    SQL
    +關(guān)注

    關(guān)注

    1

    文章

    751

    瀏覽量

    43994
  • MySQL
    +關(guān)注

    關(guān)注

    1

    文章

    794

    瀏覽量

    26359

原文標(biāo)題:一次 SQL 查詢優(yōu)化原理分析:900W+ 數(shù)據(jù),從 17s 到 300ms

文章出處:【微信號(hào):LinuxHub,微信公眾號(hào):Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    MySQL的整體邏輯架構(gòu)

    支持多種存儲(chǔ)引擎是眾所周知的MySQL特性,也是MySQL架構(gòu)的關(guān)鍵優(yōu)勢(shì)之一。如果能夠理解MySQL Server與存儲(chǔ)引擎之間是怎樣通過API交互的,將大大有利于理解MySQL的核心
    的頭像 發(fā)表于 04-30 11:14 ?388次閱讀
    <b class='flag-5'>MySQL</b>的整體邏輯架構(gòu)

    Redis與MySQL協(xié)同升級(jí)企業(yè)緩存

    傳統(tǒng)的MySQL數(shù)據(jù)庫在處理大規(guī)模應(yīng)用時(shí)已經(jīng)到了瓶頸,RedisEnterprise怎樣助力突破這一瓶頸?RedisEnterprise與MYSQL共同用作企業(yè)級(jí)緩存或副本數(shù)據(jù)庫,會(huì)產(chǎn)生什么樣的火花
    的頭像 發(fā)表于 02-19 13:18 ?318次閱讀
    Redis與<b class='flag-5'>MySQL</b>協(xié)同升級(jí)企業(yè)緩存

    mysql密碼忘了怎么重置

    mysql密碼忘了怎么重置? MySQL是一種開源的關(guān)系型數(shù)據(jù)庫管理系統(tǒng),密碼用于保護(hù)數(shù)據(jù)庫的安全性和保密性。如果你忘記了MySQL的密碼,可以通過以下幾種方法進(jìn)行重置。 方法一:使用MySQ
    的頭像 發(fā)表于 12-27 16:51 ?5434次閱讀

    MySQL的執(zhí)行過程 SQL語句性能優(yōu)化常用策略

    回顧 MySQL 的執(zhí)行過程,幫助介紹如何進(jìn)行 sql 優(yōu)化。
    的頭像 發(fā)表于 12-12 10:26 ?582次閱讀
    <b class='flag-5'>MySQL</b>的執(zhí)行過程 SQL語句<b class='flag-5'>性能</b>優(yōu)化常用策略

    mysql數(shù)據(jù)庫基礎(chǔ)命令

    使用以下命令: mysql -u -p 其中,username是您的MySQL用戶名。執(zhí)行此命令后,系統(tǒng)會(huì)提示您輸入密碼。成功輸入密碼后,您將登錄到MySQL命令行界面。 創(chuàng)建數(shù)據(jù)庫
    的頭像 發(fā)表于 12-06 10:56 ?512次閱讀

    oracle數(shù)據(jù)庫limit怎么

    在Oracle數(shù)據(jù)庫中,可以使用ROWNUM來實(shí)現(xiàn)類似LIMIT的功能。ROWNUM是Oracle數(shù)據(jù)庫提供的一個(gè)偽列,它在查詢結(jié)果集中為每一行分配一個(gè)唯一的數(shù)字。 要使用ROWNUM進(jìn)行分頁查詢
    的頭像 發(fā)表于 12-06 10:05 ?2006次閱讀

    oracle的limit語句

    Oracle數(shù)據(jù)庫中并沒有像MySQL或其他數(shù)據(jù)庫那樣直接使用"LIMIT"關(guān)鍵字來限制查詢結(jié)果的行數(shù)。在Oracle中,可以使用不同的方法來實(shí)現(xiàn)類似的功能。下面將詳細(xì)介紹這些方法。 1.
    的頭像 發(fā)表于 12-06 10:03 ?2717次閱讀

    php的mysql無法啟動(dòng)

    ,以便幫助讀者快速解決相關(guān)問題。 一、安裝環(huán)境配置檢查 1.1 PHP版本檢查 在使用PHP連接MySQL之前,首先要確保PHP版本的兼容性。查看所使用的PHP版本是否與MySQL版本兼容,如果不兼容,可能會(huì)導(dǎo)致
    的頭像 發(fā)表于 12-04 15:59 ?1283次閱讀

    docker部署mysql的壞處

    的問題和挑戰(zhàn)。在這篇文章中,我們將探討 Docker 部署 MySQL 的壞處。 首先,Docker 部署 MySQL 可能會(huì)面臨性能問題。盡管 Docker 提供了輕量級(jí)的容器化解決方案,但容器化的應(yīng)用程序在
    的頭像 發(fā)表于 11-23 09:29 ?1297次閱讀

    MySQL性能優(yōu)化方法

    MySQL 性能優(yōu)化是一項(xiàng)關(guān)鍵的任務(wù),可以提高數(shù)據(jù)庫的運(yùn)行速度和效率。以下是一些優(yōu)化方法,包括具體代碼和詳細(xì)優(yōu)化方案。
    的頭像 發(fā)表于 11-22 09:59 ?504次閱讀

    mysql和sql server區(qū)別

    MySQL和SQL Server是兩種常見的關(guān)系型數(shù)據(jù)庫管理系統(tǒng)(RDBMS),用于存儲(chǔ)和管理數(shù)據(jù)庫。雖然它們都支持SQL語言,但在其他方面存在一些顯著的區(qū)別。以下是MySQL和SQL Server
    的頭像 發(fā)表于 11-21 11:07 ?1398次閱讀

    MySQL忘記root密碼解決方案

    的密碼,可能會(huì)導(dǎo)致無法正常管理MySQL數(shù)據(jù)庫。 這篇文章將提供詳盡、詳實(shí)、細(xì)致的解決方案,幫助解決MySQL忘記root密碼的問題。 解決方案分為以下幾個(gè)步驟: 停止MySQL服務(wù):
    的頭像 發(fā)表于 11-21 11:04 ?571次閱讀

    MySQL導(dǎo)出的步驟

    MySQL是一種常用的關(guān)系型數(shù)據(jù)庫管理系統(tǒng),用于存儲(chǔ)和管理大量的結(jié)構(gòu)化數(shù)據(jù)。在實(shí)際應(yīng)用中,我們經(jīng)常需要將MySQL數(shù)據(jù)庫中的數(shù)據(jù)導(dǎo)出到其他地方,如備份數(shù)據(jù)、數(shù)據(jù)遷移、數(shù)據(jù)分析等。下面是使用MySQL
    的頭像 發(fā)表于 11-21 10:58 ?710次閱讀

    mysql是一個(gè)什么類型的數(shù)據(jù)庫

    MySQL是一種關(guān)系型數(shù)據(jù)庫管理系統(tǒng)(RDBMS),用于存儲(chǔ)和管理大量結(jié)構(gòu)化數(shù)據(jù)。它被廣泛用于各種應(yīng)用程序和網(wǎng)站的后端,包括電子商務(wù)平臺(tái)、社交媒體網(wǎng)站、金融系統(tǒng)等等。MySQL的特點(diǎn)是性能高、可靠性
    的頭像 發(fā)表于 11-16 14:43 ?1573次閱讀

    如何提高Mysql數(shù)據(jù)庫的訪問瓶頸

    在學(xué)習(xí)Mysql的時(shí)候,我們都有這個(gè)常識(shí):對(duì)于DB的操作,其實(shí)本質(zhì)上是對(duì)于磁盤的操作,如果對(duì)于DB的訪問次數(shù)過多,其實(shí)就是涉及了大量的磁盤IO,這就會(huì)導(dǎo)致MYsql出現(xiàn)性能上的瓶頸。 項(xiàng)目背景
    的頭像 發(fā)表于 11-08 16:22 ?978次閱讀
    如何提高<b class='flag-5'>Mysql</b>數(shù)據(jù)庫的訪問瓶頸