溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

MySQL中為什么簡(jiǎn)單的一行查詢也會(huì)慢

發(fā)布時(shí)間:2021-12-08 14:21:55 來源:億速云 閱讀:143 作者:iii 欄目:大數(shù)據(jù)

這篇文章主要講解了“MySQL中為什么簡(jiǎn)單的一行查詢也會(huì)慢”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“MySQL中為什么簡(jiǎn)單的一行查詢也會(huì)慢”吧!

在MySQL中,有很多看上去邏輯相同,但性能卻差異巨大的SQL語句。對(duì)這些語句使用不當(dāng)?shù)脑?,就?huì)不經(jīng)意間導(dǎo)致整個(gè)數(shù)據(jù)庫的壓力變大。

案例一:條件字段函數(shù)操作

假設(shè)你現(xiàn)在維護(hù)了一個(gè)交易系統(tǒng),其中交易記錄表tradelog包含交易流水號(hào)(tradeid)、交易員id(operator)、交易時(shí)間(t_modified)等字段。為了便于描述,我們先忽略其他字段。這個(gè)表的建表語句如下:

mysql> CREATE TABLE `tradelog` (
  `id` int(11) NOT NULL,
  `tradeid` varchar(32) DEFAULT NULL,
  `operator` int(11) DEFAULT NULL,
  `t_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `tradeid` (`tradeid`),
  KEY `t_modified` (`t_modified`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

假設(shè),現(xiàn)在已經(jīng)記錄了從2016年初到2018年底的所有數(shù)據(jù),運(yùn)營(yíng)部門有一個(gè)需求是,要統(tǒng)計(jì)發(fā)生在所有年份中7月份的交易記錄總數(shù)。這個(gè)邏輯看上去并不復(fù)雜,你的SQL語句可能會(huì)這么寫:

mysql> select count(*) from tradelog where month(t_modified)=7;

由于t_modified字段上有索引,于是你就很放心地在生產(chǎn)庫中執(zhí)行了這條語句,但卻發(fā)現(xiàn)執(zhí)行了特別久,才返回了結(jié)果。

如果你問DBA同事為什么會(huì)出現(xiàn)這樣的情況,他大概會(huì)告訴你:如果對(duì)字段做了函數(shù)計(jì)算,就用不上索引了,這是MySQL的規(guī)定。

現(xiàn)在你已經(jīng)學(xué)過了InnoDB的索引結(jié)構(gòu)了,可以再追問一句為什么?為什么條件是where t_modified='2018-7-1’的時(shí)候可以用上索引,而改成where month(t_modified)=7的時(shí)候就不行了?

下面是這個(gè)t_modified索引的示意圖。方框上面的數(shù)字就是month()函數(shù)對(duì)應(yīng)的值。

MySQL中為什么簡(jiǎn)單的一行查詢也會(huì)慢

                                                                圖1 t_modified索引示意圖

如果你的SQL語句條件用的是where t_modified='2018-7-1’的話,引擎就會(huì)按照上面綠色箭頭的路線,快速定位到 t_modified='2018-7-1’需要的結(jié)果。

實(shí)際上,B+樹提供的這個(gè)快速定位能力,來源于同一層兄弟節(jié)點(diǎn)的有序性。

但是,如果計(jì)算month()函數(shù)的話,你會(huì)看到傳入7的時(shí)候,在樹的第一層就不知道該怎么辦了。

也就是說,對(duì)索引字段做函數(shù)操作,可能會(huì)破壞索引值的有序性,因此優(yōu)化器就決定放棄走樹搜索功能。

需要注意的是,優(yōu)化器并不是要放棄使用這個(gè)索引。

在這個(gè)例子里,放棄了樹搜索功能,優(yōu)化器可以選擇遍歷主鍵索引,也可以選擇遍歷索引t_modified,優(yōu)化器對(duì)比索引大小后發(fā)現(xiàn),索引t_modified更小,遍歷這個(gè)索引比遍歷主鍵索引來得更快。因此最終還是會(huì)選擇索引t_modified。

接下來,我們使用explain命令,查看一下這條SQL語句的執(zhí)行結(jié)果。

                                                                           圖2 explain 結(jié)果

key="t_modified"表示的是,使用了t_modified這個(gè)索引;我在測(cè)試表數(shù)據(jù)中插入了10萬行數(shù)據(jù),rows=100335,說明這條語句掃描了整個(gè)索引的所有值;Extra字段的Using index,表示的是使用了覆蓋索引。

也就是說,由于在t_modified字段加了month()函數(shù)操作,導(dǎo)致了全索引掃描。為了能夠用上索引的快速定位能力,我們就要把SQL語句改成基于字段本身的范圍查詢。按照下面這個(gè)寫法,優(yōu)化器就能按照我們預(yù)期的,用上t_modified索引的快速定位能力了。

mysql> select count(*) from tradelog where
    -> (t_modified >= '2016-7-1' and t_modified<'2016-8-1') or
    -> (t_modified >= '2017-7-1' and t_modified<'2017-8-1') or 
    -> (t_modified >= '2018-7-1' and t_modified<'2018-8-1');

當(dāng)然,如果你的系統(tǒng)上線時(shí)間更早,或者后面又插入了之后年份的數(shù)據(jù)的話,你就需要再把其他年份補(bǔ)齊。

到這里我給你說明了,由于加了month()函數(shù)操作,MySQL無法再使用索引快速定位功能,而只能使用全索引掃描。

不過優(yōu)化器在個(gè)問題上確實(shí)有“偷懶”行為,即使是對(duì)于不改變有序性的函數(shù),也不會(huì)考慮使用索引。比如,對(duì)于select * from tradelog where id + 1 = 10000這個(gè)SQL語句,這個(gè)加1操作并不會(huì)改變有序性,但是MySQL優(yōu)化器還是不能用id索引快速定位到9999這一行。所以,需要你在寫SQL語句的時(shí)候,手動(dòng)改寫成 where id = 10000 -1才可以。

案例二:隱式類型轉(zhuǎn)換

接下來我再跟你說一說,另一個(gè)經(jīng)常讓程序員掉坑里的例子。

我們一起看一下這條SQL語句:

mysql> select * from tradelog where tradeid=110717;

交易編號(hào)tradeid這個(gè)字段上,本來就有索引,但是explain的結(jié)果卻顯示,這條語句需要走全表掃描。你可能也發(fā)現(xiàn)了,tradeid的字段類型是varchar(32),而輸入的參數(shù)卻是整型,所以需要做類型轉(zhuǎn)換。

那么,現(xiàn)在這里就有兩個(gè)問題:

  1. 數(shù)據(jù)類型轉(zhuǎn)換的規(guī)則是什么?

  2. 為什么有數(shù)據(jù)類型轉(zhuǎn)換,就需要走全索引掃描?

先來看第一個(gè)問題,你可能會(huì)說,數(shù)據(jù)庫里面類型這么多,這種數(shù)據(jù)類型轉(zhuǎn)換規(guī)則更多,我記不住,應(yīng)該怎么辦呢?

這里有一個(gè)簡(jiǎn)單的方法,看 select “10” > 9的結(jié)果:

  1. 如果規(guī)則是“將字符串轉(zhuǎn)成數(shù)字”,那么就是做數(shù)字比較,結(jié)果應(yīng)該是1;

  2. 如果規(guī)則是“將數(shù)字轉(zhuǎn)成字符串”,那么就是做字符串比較,結(jié)果應(yīng)該是0。

驗(yàn)證結(jié)果如圖3所示。

MySQL中為什么簡(jiǎn)單的一行查詢也會(huì)慢

圖3 MySQL中字符串和數(shù)字轉(zhuǎn)換的效果示意圖

從圖中可知,select “10” > 9返回的是1,所以你就能確認(rèn)MySQL里的轉(zhuǎn)換規(guī)則了:在MySQL中,字符串和數(shù)字做比較的話,是將字符串轉(zhuǎn)換成數(shù)字。

這時(shí),你再看這個(gè)全表掃描的語句:

mysql> select * from tradelog where tradeid=110717;

就知道對(duì)于優(yōu)化器來說,這個(gè)語句相當(dāng)于:

mysql> select * from tradelog where  CAST(tradid AS signed int) = 110717;

也就是說,這條語句觸發(fā)了我們上面說到的規(guī)則:對(duì)索引字段做函數(shù)操作,優(yōu)化器會(huì)放棄走樹搜索功能。

現(xiàn)在,我留給你一個(gè)小問題,id的類型是int,如果執(zhí)行下面這個(gè)語句,是否會(huì)導(dǎo)致全表掃描呢?

select * from tradelog where id="83126";

你可以先自己分析一下,再到數(shù)據(jù)庫里面去驗(yàn)證確認(rèn)。

接下來,我們?cè)賮砜匆粋€(gè)稍微復(fù)雜點(diǎn)的例子。

案例三:隱式字符編碼轉(zhuǎn)換

假設(shè)系統(tǒng)里還有另外一個(gè)表trade_detail,用于記錄交易的操作細(xì)節(jié)。為了便于量化分析和復(fù)現(xiàn),我往交易日志表tradelog和交易詳情表trade_detail這兩個(gè)表里插入一些數(shù)據(jù)。

mysql> CREATE TABLE `trade_detail` (
  `id` int(11) NOT NULL,
  `tradeid` varchar(32) DEFAULT NULL,
  `trade_step` int(11) DEFAULT NULL, /*操作步驟*/
  `step_info` varchar(32) DEFAULT NULL, /*步驟信息*/
  PRIMARY KEY (`id`),
  KEY `tradeid` (`tradeid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into tradelog values(1, 'aaaaaaaa', 1000, now());
insert into tradelog values(2, 'aaaaaaab', 1000, now());
insert into tradelog values(3, 'aaaaaaac', 1000, now());

insert into trade_detail values(1, 'aaaaaaaa', 1, 'add');
insert into trade_detail values(2, 'aaaaaaaa', 2, 'update');
insert into trade_detail values(3, 'aaaaaaaa', 3, 'commit');
insert into trade_detail values(4, 'aaaaaaab', 1, 'add');
insert into trade_detail values(5, 'aaaaaaab', 2, 'update');
insert into trade_detail values(6, 'aaaaaaab', 3, 'update again');
insert into trade_detail values(7, 'aaaaaaab', 4, 'commit');
insert into trade_detail values(8, 'aaaaaaac', 1, 'add');
insert into trade_detail values(9, 'aaaaaaac', 2, 'update');
insert into trade_detail values(10, 'aaaaaaac', 3, 'update again');
insert into trade_detail values(11, 'aaaaaaac', 4, 'commit');

這時(shí)候,如果要查詢id=2的交易的所有操作步驟信息,SQL語句可以這么寫:

mysql> select d.* from tradelog l, trade_detail d where d.tradeid=l.tradeid and l.id=2; /*語句Q1*/

MySQL中為什么簡(jiǎn)單的一行查詢也會(huì)慢

                                                                             圖5 語句Q1的執(zhí)行過程

圖中:

  • 第1步,是根據(jù)id在tradelog表里找到L2這一行;

  • 第2步,是從L2中取出tradeid字段的值;

  • 第3步,是根據(jù)tradeid值到trade_detail表中查找條件匹配的行。explain的結(jié)果里面第二行的key=NULL表示的就是,這個(gè)過程是通過遍歷主鍵索引的方式,一個(gè)一個(gè)地判斷tradeid的值是否匹配。

進(jìn)行到這里,你會(huì)發(fā)現(xiàn)第3步不符合我們的預(yù)期。因?yàn)楸韙rade_detail里tradeid字段上是有索引的,我們本來是希望通過使用tradeid索引能夠快速定位到等值的行。但,這里并沒有。

如果你去問DBA同學(xué),他們可能會(huì)告訴你,因?yàn)檫@兩個(gè)表的字符集不同,一個(gè)是utf8,一個(gè)是utf8mb4,所以做表連接查詢的時(shí)候用不上關(guān)聯(lián)字段的索引。這個(gè)回答,也是通常你搜索這個(gè)問題時(shí)會(huì)得到的答案。

但是你應(yīng)該再追問一下,為什么字符集不同就用不上索引呢?

我們說問題是出在執(zhí)行步驟的第3步,如果單獨(dú)把這一步改成SQL語句的話,那就是:

mysql> select * from trade_detail where tradeid=$L2.tradeid.value;

其中,$L2.tradeid.value的字符集是utf8mb4。

參照前面的兩個(gè)例子,你肯定就想到了,字符集utf8mb4是utf8的超集,所以當(dāng)這兩個(gè)類型的字符串在做比較的時(shí)候,MySQL內(nèi)部的操作是,先把utf8字符串轉(zhuǎn)成utf8mb4字符集,再做比較。

這個(gè)設(shè)定很好理解,utf8mb4是utf8的超集。類似地,在程序設(shè)計(jì)語言里面,做自動(dòng)類型轉(zhuǎn)換的時(shí)候,為了避免數(shù)據(jù)在轉(zhuǎn)換過程中由于截?cái)鄬?dǎo)致數(shù)據(jù)錯(cuò)誤,也都是“按數(shù)據(jù)長(zhǎng)度增加的方向”進(jìn)行轉(zhuǎn)換的。

因此, 在執(zhí)行上面這個(gè)語句的時(shí)候,需要將被驅(qū)動(dòng)數(shù)據(jù)表里的字段一個(gè)個(gè)地轉(zhuǎn)換成utf8mb4,再跟L2做比較。

也就是說,實(shí)際上這個(gè)語句等同于下面這個(gè)寫法:

select * from trade_detail  where CONVERT(traideid USING utf8mb4)=$L2.tradeid.value;

CONVERT()函數(shù),在這里的意思是把輸入的字符串轉(zhuǎn)成utf8mb4字符集。

這就再次觸發(fā)了我們上面說到的原則:對(duì)索引字段做函數(shù)操作,優(yōu)化器會(huì)放棄走樹搜索功能。

到這里,你終于明確了,字符集不同只是條件之一,連接過程中要求在被驅(qū)動(dòng)表的索引字段上加函數(shù)操作,是直接導(dǎo)致對(duì)被驅(qū)動(dòng)表做全表掃描的原因。

作為對(duì)比驗(yàn)證,我給你提另外一個(gè)需求,“查找trade_detail表里id=4的操作,對(duì)應(yīng)的操作者是誰”,再來看下這個(gè)語句和它的執(zhí)行計(jì)劃。

mysql>select l.operator from tradelog l , trade_detail d where d.tradeid=l.tradeid and d.id=4;

MySQL中為什么簡(jiǎn)單的一行查詢也會(huì)慢

圖7 SQL語句優(yōu)化后的explain結(jié)果

這里,我主動(dòng)把 l.tradeid轉(zhuǎn)成utf8,就避免了被驅(qū)動(dòng)表上的字符編碼轉(zhuǎn)換,從explain結(jié)果可以看到,這次索引走對(duì)了。

小結(jié)

今天我給你舉了三個(gè)例子,其實(shí)是在說同一件事兒,即:對(duì)索引字段做函數(shù)操作,可能會(huì)破壞索引值的有序性,因此優(yōu)化器就決定放棄走樹搜索功能。

第二個(gè)例子是隱式類型轉(zhuǎn)換,第三個(gè)例子是隱式字符編碼轉(zhuǎn)換,它們都跟第一個(gè)例子一樣,因?yàn)橐笤谒饕侄紊献龊瘮?shù)操作而導(dǎo)致了全索引掃描。

MySQL的優(yōu)化器確實(shí)有“偷懶”的嫌疑,即使簡(jiǎn)單地把where id+1=1000改寫成where id=1000-1就能夠用上索引快速查找,也不會(huì)主動(dòng)做這個(gè)語句重寫。

因此,每次你的業(yè)務(wù)代碼升級(jí)時(shí),把可能出現(xiàn)的、新的SQL語句explain一下,是一個(gè)很好的習(xí)慣。

執(zhí)行一行也慢為什么?

為了便于描述,我還是構(gòu)造一個(gè)表,基于這個(gè)表來說明今天的問題。這個(gè)表有兩個(gè)字段id和c,并且我在里面插入了10萬行記錄。

mysql> CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `c` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

delimiter ;;
create procedure idata()
begin
  declare i int;
  set i=1;
  while(i<=100000)do
    insert into t values(i,i);
    set i=i+1;
  end while;
end;;
delimiter ;

call idata();

接下來,我會(huì)用幾個(gè)不同的場(chǎng)景來舉例,有些是前面的文章中我們已經(jīng)介紹過的知識(shí)點(diǎn),你看看能不能一眼看穿,來檢驗(yàn)一下吧。

第一類:查詢長(zhǎng)時(shí)間不返回

如圖1所示,在表t執(zhí)行下面的SQL語句:

mysql> select * from t where id=1;

查詢結(jié)果長(zhǎng)時(shí)間不返回。

MySQL中為什么簡(jiǎn)單的一行查詢也會(huì)慢

                                                        圖2 Waiting for table metadata lock狀態(tài)示意圖

出現(xiàn)這個(gè)狀態(tài)表示的是,現(xiàn)在有一個(gè)線程正在表t上請(qǐng)求或者持有MDL寫鎖,把select語句堵住了。

在MySQL 5.7版本下復(fù)現(xiàn)這個(gè)場(chǎng)景,也很容易。如圖3所示,我給出了簡(jiǎn)單的復(fù)現(xiàn)步驟。
MySQL中為什么簡(jiǎn)單的一行查詢也會(huì)慢

圖4 查獲加表鎖的線程id

等flush

接下來,我給你舉另外一種查詢被堵住的情況。

我在表t上,執(zhí)行下面的SQL語句:

mysql> select * from information_schema.processlist where id=1;

這里,我先賣個(gè)關(guān)子。

你可以看一下圖5。我查出來這個(gè)線程的狀態(tài)是Waiting for table flush,你可以設(shè)想一下這是什么原因。

圖5 Waiting for table flush狀態(tài)示意圖

這個(gè)狀態(tài)表示的是,現(xiàn)在有一個(gè)線程正要對(duì)表t做flush操作。MySQL里面對(duì)表做flush操作的用法,一般有以下兩個(gè):

flush tables t with read lock;

flush tables with read lock;

這兩個(gè)flush語句,如果指定表t的話,代表的是只關(guān)閉表t;如果沒有指定具體的表名,則表示關(guān)閉MySQL里所有打開的表。

但是正常這兩個(gè)語句執(zhí)行起來都很快,除非它們也被別的線程堵住了。

所以,出現(xiàn)Waiting for table flush狀態(tài)的可能情況是:有一個(gè)flush tables命令被別的語句堵住了,然后它又堵住了我們的select語句。

現(xiàn)在,我們一起來復(fù)現(xiàn)一下這種情況,復(fù)現(xiàn)步驟如圖6所示:

MySQL中為什么簡(jiǎn)單的一行查詢也會(huì)慢

                                                           圖6 Waiting for table flush的復(fù)現(xiàn)步驟

在session A中,我故意每行都調(diào)用一次sleep(1),這樣這個(gè)語句默認(rèn)要執(zhí)行10萬秒,在這期間表t一直是被session A“打開”著。然后,session B的flush tables t命令再要去關(guān)閉表t,就需要等session A的查詢結(jié)束。這樣,session C要再次查詢的話,就會(huì)被flush 命令堵住了。

圖7是這個(gè)復(fù)現(xiàn)步驟的show processlist結(jié)果。這個(gè)例子的排查也很簡(jiǎn)單,你看到這個(gè)show processlist的結(jié)果,肯定就知道應(yīng)該怎么做了。

MySQL中為什么簡(jiǎn)單的一行查詢也會(huì)慢

                                                                        圖 8 行鎖復(fù)現(xiàn)

MySQL中為什么簡(jiǎn)單的一行查詢也會(huì)慢

                                                                圖10 通過sys.innodb_lock_waits 查行鎖

可以看到,這個(gè)信息很全,4號(hào)線程是造成堵塞的罪魁禍?zhǔn)?。而干掉這個(gè)罪魁禍?zhǔn)椎姆绞剑褪荎ILL QUERY 4或KILL 4。

不過,這里不應(yīng)該顯示“KILL QUERY 4”。這個(gè)命令表示停止4號(hào)線程當(dāng)前正在執(zhí)行的語句,而這個(gè)方法其實(shí)是沒有用的。因?yàn)檎加行墟i的是update語句,這個(gè)語句已經(jīng)是之前執(zhí)行完成了的,現(xiàn)在執(zhí)行KILL QUERY,無法讓這個(gè)事務(wù)去掉id=1上的行鎖。

實(shí)際上,KILL 4才有效,也就是說直接斷開這個(gè)連接。這里隱含的一個(gè)邏輯就是,連接被斷開的時(shí)候,會(huì)自動(dòng)回滾這個(gè)連接里面正在執(zhí)行的線程,也就釋放了id=1上的行鎖。

第二類:查詢慢

經(jīng)過了重重封“鎖”,我們?cè)賮砜纯匆恍┎樵兟睦印?/p>

先來看一條你一定知道原因的SQL語句:

mysql> select * from t where c=50000 limit 1;

由于字段c上沒有索引,這個(gè)語句只能走id主鍵順序掃描,因此需要掃描5萬行。

作為確認(rèn),你可以看一下慢查詢?nèi)罩?。注意,這里為了把所有語句記錄到slow log里,我在連接后先執(zhí)行了 set long_query_time=0,將慢查詢?nèi)罩镜臅r(shí)間閾值設(shè)置為0。

MySQL中為什么簡(jiǎn)單的一行查詢也會(huì)慢

圖12 掃描一行卻執(zhí)行得很慢

是不是有點(diǎn)奇怪呢,這些時(shí)間都花在哪里了?

如果我把這個(gè)slow log的截圖再往下拉一點(diǎn),你可以看到下一個(gè)語句,select * from t where id=1 lock in share mode,執(zhí)行時(shí)掃描行數(shù)也是1行,執(zhí)行時(shí)間是0.2毫秒。

MySQL中為什么簡(jiǎn)單的一行查詢也會(huì)慢

圖14 兩個(gè)語句的輸出結(jié)果

第一個(gè)語句的查詢結(jié)果里c=1,帶lock in share mode的語句返回的是c=1000001??吹竭@里應(yīng)該有更多的同學(xué)知道原因了。如果你還是沒有頭緒的話,也別著急。我先跟你說明一下復(fù)現(xiàn)步驟,再分析原因。

MySQL中為什么簡(jiǎn)單的一行查詢也會(huì)慢

圖16 id=1的數(shù)據(jù)狀態(tài)

session B更新完100萬次,生成了100萬個(gè)回滾日志(undo log)。

帶lock in share mode的SQL語句,是當(dāng)前讀,因此會(huì)直接讀到1000001這個(gè)結(jié)果,所以速度很快;而select * from t where id=1這個(gè)語句,是一致性讀,因此需要從1000001開始,依次執(zhí)行undo log,執(zhí)行了100萬次以后,才將1這個(gè)結(jié)果返回。

注意,undo log里記錄的其實(shí)是“把2改成1”,“把3改成2”這樣的操作邏輯,畫成減1的目的是方便你看圖。

小結(jié)

今天我給你舉了在一個(gè)簡(jiǎn)單的表上,執(zhí)行“查一行”,可能會(huì)出現(xiàn)的被鎖住和執(zhí)行慢的例子。這其中涉及到了表鎖、行鎖和一致性讀的概念。

在實(shí)際使用中,碰到的場(chǎng)景會(huì)更復(fù)雜。但大同小異,你可以按照我在文章中介紹的定位方法,來定位并解決問題。

最后,我給你留一個(gè)問題吧。

我們?cè)谂e例加鎖讀的時(shí)候,用的是這個(gè)語句,select * from t where id=1 lock in share mode。由于id上有索引,所以可以直接定位到id=1這一行,因此讀鎖也是只加在了這一行上。

但如果是下面的SQL語句,

begin;
select * from t where c=5 for update;
commit;

這個(gè)語句序列是怎么加鎖的呢?加的鎖又是什么時(shí)候釋放呢?

上期問題時(shí)間

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

mysql> CREATE TABLE `table_a` (
  `id` int(11) NOT NULL,
  `b` varchar(10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `b` (`b`)
) ENGINE=InnoDB;

假設(shè)現(xiàn)在表里面,有100萬行數(shù)據(jù),其中有10萬行數(shù)據(jù)的b的值是’1234567890’, 假設(shè)現(xiàn)在執(zhí)行語句是這么寫的:

mysql> select * from table_a where b='1234567890abcd';

這時(shí)候,MySQL會(huì)怎么執(zhí)行呢?

最理想的情況是,MySQL看到字段b定義的是varchar(10),那肯定返回空呀??上?,MySQL并沒有這么做。

那要不,就是把’1234567890abcd’拿到索引里面去做匹配,肯定也沒能夠快速判斷出索引樹b上并沒有這個(gè)值,也很快就能返回空結(jié)果。

但實(shí)際上,MySQL也不是這么做的。

這條SQL語句的執(zhí)行很慢,流程是這樣的:

  1. 在傳給引擎執(zhí)行的時(shí)候,做了字符截?cái)唷R驗(yàn)橐胬锩孢@個(gè)行只定義了長(zhǎng)度是10,所以只截了前10個(gè)字節(jié),就是’1234567890’進(jìn)去做匹配;

  2. 這樣滿足條件的數(shù)據(jù)有10萬行;

  3. 因?yàn)槭莝elect *, 所以要做10萬次回表;

  4. 但是每次回表以后查出整行,到server層一判斷,b的值都不是’1234567890abcd’;

  5. 返回結(jié)果是空。

感謝各位的閱讀,以上就是“MySQL中為什么簡(jiǎn)單的一行查詢也會(huì)慢”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)MySQL中為什么簡(jiǎn)單的一行查詢也會(huì)慢這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI