色综合图-色综合图片-色综合图片二区150p-色综合图区-玖玖国产精品视频-玖玖香蕉视频

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

一次SQL查詢(xún)優(yōu)化原理分析(900W+數(shù)據(jù)從17s到300ms)

瀏覽:233日期:2023-03-06 14:25:18
目錄
  • 前言
  • 證實(shí)
  • 參考資料:

有一張財(cái)務(wù)流水表,未分庫(kù)分表,目前的數(shù)據(jù)量為9555695,分頁(yè)查詢(xún)使用到了limit,優(yōu)化之前的查詢(xún)耗時(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);

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

原理:1、減少回表操作;
2、可參考《阿里巴巴Java開(kāi)發(fā)手冊(cè)(泰山版)》第五章-MySQL數(shù)據(jù)庫(kù)、(二)索引規(guī)約、第7條:
【推薦】利用延遲關(guān)聯(lián)或者子查詢(xún)優(yōu)化超多分頁(yè)場(chǎng)景。
說(shuō)明: MySQL并不是挑過(guò)offeset行,而是取offset+N行,然后返回放棄前offset行,返回N行,那當(dāng)offset特別大的時(shí)候,效率就非常的底下,要么控制返回的總頁(yè)數(shù),要么對(duì)超過(guò)特定閾值的頁(yè)數(shù)進(jìn)行SQL改寫(xiě)。
正例: 先快速定位需要獲取的id段,然后再關(guān)聯(lián):
SELECT a.* FROM 表1 a,(select id from 表1 where 條件 LIMIT 100000,20) b where a.id = b.id;

-- 優(yōu)化前SQLSELECT  各種字段FROM `table_name`WHERE 各種條件LIMIT 0,10;
-- 優(yōu)化后SQLSELECT  各種字段FROM `table_name` main_taleRIGHT JOIN (SELECT  子查詢(xún)只查主鍵FROM `table_name`WHERE 各種條件LIMIT 0,10;) temp_table ON temp_table.主鍵 = main_table.主鍵

前言

首先說(shuō)明一下MySQL的版本:

mysql> select version();+-----------+| version() |+-----------+| 5.7.17    |+-----------+1 row in set (0.00 sec)

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

mysql> desc test;+--------+---------------------+------+-----+---------+----------------+| 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       ||+--------+---------------------+------+-----+---------+----------------+3 rows in set (0.00 sec)

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

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

mysql> select count(*) from test;+----------+| count(*) |+----------+|  5242882 |+----------+1 row in set (4.25 sec)

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

mysql> select * from test where val=4 limit 300000,5;+---------+-----+--------+| id      | val | source |+---------+-----+--------+| 3327622 |   4 |      4 || 3327632 |   4 |      4 || 3327642 |   4 |      4 || 3327652 |   4 |      4 || 3327662 |   4 |      4 |+---------+-----+--------+5 rows in set (15.98 sec)

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

mysql> select * from test a inner join (select id from test where val=4 limit 300000,5) b on a.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 |+---------+-----+--------+---------+5 rows in set (0.38 sec)

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

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

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

類(lèi)似于下面這張圖:

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

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

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

證實(shí)

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

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

我只能通過(guò)間接的方式來(lái)證實(shí):

InnoDB中有buffer pool。里面存有最近訪(fǎng)問(wèn)過(guò)的數(shù)據(jù)頁(yè),包括數(shù)據(jù)頁(yè)和索引頁(yè)。所以我們需要運(yùn)行兩個(gè)sql,來(lái)比較buffer pool中的數(shù)據(jù)頁(yè)的數(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ù)頁(yè)的數(shù)量遠(yuǎn)遠(yuǎn)少于select * from test where val=4 limit 300000,5;對(duì)應(yīng)的數(shù)量,因?yàn)榍耙粋€(gè)sql只訪(fǎng)問(wèn)5次數(shù)據(jù)頁(yè),而后一個(gè)sql訪(fǎng)問(wèn)300005次數(shù)據(jù)頁(yè)。

select * from test where val=4 limit 300000,5
mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in("val","primary") and TABLE_NAME like "%test%" group by index_name;Empty set (0.04 sec)

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

mysql> select * from test where val=4 limit 300000,5;+---------+-----+--------+| id      | val | source |+---------+-----+--------+| 3327622 |   4 |      4 || 3327632 |   4 |      4 || 3327642 |   4 |      4 || 3327652 |   4 |      4 || 3327662 |   4 |      4 |+---------+-----+--------+5 rows in set (26.19 sec)mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in("val","primary") and TABLE_NAME like "%test%" group by index_name;+------------+----------+| index_name | count(*) |+------------+----------+| PRIMARY    |     4098 || val|      208 |+------------+----------+2 rows in set (0.04 sec)

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

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

mysqladmin shutdown/usr/local/bin/mysqld_safe &
mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in("val","primary") and TABLE_NAME like "%test%" group by index_name;Empty set (0.03 sec)

運(yùn)行sql:

mysql> select * from test a inner join (select id from test where val=4 limit 300000,5) b on a.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 |+---------+-----+--------+---------+5 rows in set (0.09 sec)mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in("val","primary") and TABLE_NAME like "%test%" group by index_name;+------------+----------+| index_name | count(*) |+------------+----------+| PRIMARY    |5 || val|      390 |+------------+----------+2 rows in set (0.03 sec)

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

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

參考資料:

1.https://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/

2.https://dev.mysql.com/doc/refman/5.7/en/innodb-information-schema-buffer-pool-tables.html

到此這篇關(guān)于一次SQL查詢(xún)優(yōu)化原理分析(900W+數(shù)據(jù)從17s到300ms)的文章就介紹到這了,更多相關(guān)SQL查詢(xún)優(yōu)化內(nèi)容請(qǐng)搜索以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持!

標(biāo)簽: MsSQL
相關(guān)文章:
主站蜘蛛池模板: 亚欧免费视频 | 窝窝女人体国产午夜视频 | 久久99精品久久久久久青青91 | 在线观看亚洲成人 | 精品久久久日韩精品成人 | 久久久精品久久视频只有精品 | 日韩精品三级 | 亚洲国产精品日韩在线观看 | 怡红院免费的全部视频 | 国产v综合v亚洲欧美大另类 | 边接电话边做国语高清对白 | 久久香蕉精品视频 | 男女乱淫免费视频 | 久久99国产亚洲高清观看首页 | 欧美精品 日韩 | 国产精品自在自线亚洲 | 久久精品国产精品亚洲人人 | 日本一级级特黄特色大片 | 久久国产影视 | 99精品久久秒播无毒不卡 | 日日摸日日碰夜夜爽久久 | 日日狠狠久久偷偷四色综合免费 | 九九精品成人免费国产片 | 亚洲精品一区二区在线观看 | 成人在线视频国产 | 日韩免费a级在线观看 | 中文字幕一二三区乱码老 | 国产区二区| 亚洲欧美视频一区二区三区 | 亚洲精品国产手机 | 在线视频区 | 日本三级一区二区三区 | 北条麻妃在线一区二区 | 国产日韩在线看 | 亚欧美图片自偷自拍另类 | 久久久精品国产免费观看同学 | 日本中文字幕不卡免费视频 | 免费看男女做好爽好硬视频 | 日韩制服诱惑 | 欧美日韩a级片 | 泰国情欲片寂寞的寡妇在线观看 |