昨天CSDN突然抽風(fēng)?我一個ctrl+z把整篇文章給撤掉了還不能復(fù)原?直接心態(tài)崩了不想寫了?不過這部分果然還是很重要,還是寫出來吧
流程圖?
這里面總共有兩層結(jié)構(gòu)Server層?儲存引擎
- Server 層負(fù)責(zé)建立連接、分析和執(zhí)行 SQL。MySQL 大多數(shù)的核心功能模塊都在這實現(xiàn),主要包括連接器,查詢緩存、解析器、預(yù)處理器、優(yōu)化器、執(zhí)行器等。另外,所有的內(nèi)置函數(shù)(如日期、時間、數(shù)學(xué)和加密函數(shù)等)和所有跨存儲引擎的功能(如存儲過程、觸發(fā)器、視圖等。)都在 Server 層實現(xiàn)。
- 存儲引擎層負(fù)責(zé)數(shù)據(jù)的存儲和提取。支持 InnoDB、MyISAM、Memory 等多個存儲引擎,不同的存儲引擎共用一個 Server 層。現(xiàn)在最常用的存儲引擎是 InnoDB,從 MySQL 5.5 版本開始, InnoDB 成為了 MySQL 的默認(rèn)存儲引擎。我們常說的索引數(shù)據(jù)結(jié)構(gòu),就是由存儲引擎層實現(xiàn)的,不同的存儲引擎支持的索引類型也不相同,比如 InnoDB 支持索引類型是 B+樹 ,且是默認(rèn)使用,也就是說在數(shù)據(jù)表中創(chuàng)建的主鍵索引和二級索引默認(rèn)使用的是 B+ 樹索引。
第一步:連接器?
就是用戶和MYSQL相連接的過程,會發(fā)生三次握手和四次揮手,因為連接是基于TCP協(xié)議進(jìn)行傳輸?shù)?/p>
# -h 指定 MySQL 服務(wù)得 IP 地址,如果是連接本地的 MySQL服務(wù),可以不用這個參數(shù); # -u 指定用戶名,管理員角色名為 root; # -p 指定密碼,如果命令行中不填寫密碼(為了密碼安全,建議不要在命令行寫密碼),就需要在交互對話里面輸入密碼 mysql -h$ip -u$user -p
?如果 MySQL 服務(wù)正常運行,完成 TCP 連接的建立后,連接器就要開始驗證你的用戶名和密碼,如果用戶名或密碼不對,就收到一個"Access denied for user"的錯誤,然后客戶端程序結(jié)束執(zhí)行。
如果用戶密碼都沒有問題,連接器就會獲取該用戶的權(quán)限,然后保存起來,后續(xù)該用戶在此連接里的任何操作,都會基于連接開始時讀到的權(quán)限進(jìn)行權(quán)限邏輯的判斷。
所以,如果一個用戶已經(jīng)建立了連接,即使管理員中途修改了該用戶的權(quán)限,也不會影響已經(jīng)存在連接的權(quán)限。修改完成后,只有再新建的連接才會使用新的權(quán)限設(shè)置。
可以通過
SHOW PROCESSLIST;
查看有多少用戶連接了MYSQL
比如上圖的顯示結(jié)果,共有兩個用戶名為 root 的用戶連接了 MySQL 服務(wù),其中 id 為 6 的用戶的 Command 列的狀態(tài)為?
Sleep
?,這意味著該用戶連接完 MySQL 服務(wù)就沒有再執(zhí)行過任何命令,也就是說這是一個空閑的連接,并且空閑的時長是 736 秒( Time 列)。空閑連接會一直占用著嗎?
當(dāng)然不是了,MySQL 定義了空閑連接的最大空閑時長,由?
wait_timeout
?參數(shù)控制的,默認(rèn)值是 8 小時(28880秒),如果空閑連接超過了這個時間,連接器就會自動將它斷開。mysql> show variables like 'wait_timeout'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | wait_timeout | 28800 | +---------------+-------+ 1 row in set (0.00 sec)
當(dāng)然,我們自己也可以手動斷開空閑的連接,使用的是 kill connection + id 的命令。
長鏈接和短連接的區(qū)別
短連接 連接 mysql 服務(wù)(TCP 三次握手) 執(zhí)行sql 斷開 mysql 服務(wù)(TCP 四次揮手) // 長連接 連接 mysql 服務(wù)(TCP 三次握手) 執(zhí)行sql 執(zhí)行sql 執(zhí)行sql .... 斷開 mysql 服務(wù)(TCP 四次揮手)
所以就是短連接只有一條SQL語句?長連接有多條SQL語句
同樣一個任務(wù)我們使用長連接要比使用短連接節(jié)省了多次連接和斷開MYSQL的過程
但是長連接就一點缺點沒有嗎?非也
MySQL 在執(zhí)行查詢過程中臨時使用內(nèi)存管理連接對象,這些連接對象資源只有在連接斷開時才會釋放。如果長連接累計很多,將導(dǎo)致 MySQL 服務(wù)占用內(nèi)存太大,有可能會被系統(tǒng)強(qiáng)制殺掉,這樣會發(fā)生 MySQL 服務(wù)異常重啟的現(xiàn)象。
那我們?nèi)绾谓鉀Q這種問題嗎
斷開連接不就完事了嘛
1.定期手動斷開連接
2.客戶端主動重置連接。MySQL 5.7 版本實現(xiàn)了?
mysql_reset_connection()
?函數(shù)的接口,注意這是接口函數(shù)不是命令,那么當(dāng)客戶端執(zhí)行了一個很大的操作后,在代碼里調(diào)用 mysql_reset_connection 函數(shù)來重置連接,達(dá)到釋放內(nèi)存的效果。這個過程不需要重連和重新做權(quán)限驗證,但是會將連接恢復(fù)到剛剛創(chuàng)建完時的狀態(tài)。每次學(xué)XX底層原理的時候都感覺,底層運行在替我負(fù)重前行
第二步:查詢緩存?
連接工作結(jié)束之后,客戶端就可以向MYSQL發(fā)送SQL語句了?它首先會解析語句的第一個字段?查看這是一條什么語句 假如說發(fā)送的是一個SELECT語句?那么它就會去去查詢緩存( Query Cache )里查找緩存數(shù)據(jù),看看之前有沒有執(zhí)行過這一條命令,這個查詢緩存是以 key-value 形式保存在內(nèi)存中的,key 為 SQL 查詢語句,value 為 SQL 語句查詢的結(jié)果。
如果查詢的語句命中查詢緩存,那么就會直接返回 value 給客戶端。如果查詢的語句沒有命中查詢緩存中,那么就要往下繼續(xù)執(zhí)行,等執(zhí)行完后,查詢的結(jié)果就會被存入查詢緩存中。
這么一看?這太牛了?這個查詢緩存?這不就像dp數(shù)組一樣可以大量優(yōu)化運行了嗎
其實不然?因為每次對這張表更新數(shù)據(jù)的時候?這張表的緩存就會被全部清除....啊?也就是說?如果是更新后的數(shù)據(jù)查不到?只能是未更新的查詢過一次的數(shù)據(jù)?
?那我查它干啥?我直接把上一次的查詢結(jié)果拿出來用多好
很好?MYSQL也是這么想的?所以MYSQL8.0直接把這個東西給刪了
第三步:解析SQL
第一件事:詞法分析,MYSQL會分析你這個SQL語句中的關(guān)鍵子?然后形成SQL語法樹 這樣方便后面模塊獲取 SQL 類型、表名、字段名、 where 條件等等。?(給MYSQL來一把長難句分析)
第二件事:句法分析?就是看有沒有語法錯誤
這里加粗了語法錯誤?字段,表,數(shù)據(jù)不存在那不是語法錯誤哦
就像JVM中的元數(shù)據(jù)驗證?字節(jié)碼驗證一樣
第四步:執(zhí)行 SQL
對應(yīng)圖中的四塊
- prepare 階段,也就是預(yù)處理階段;
- optimize 階段,也就是優(yōu)化階段;
- execute 階段,也就是執(zhí)行階段;
預(yù)處理階段?
?那肯定在預(yù)處理器中,于SELECT而言?預(yù)處理就做倆事
1.查看字段,表等數(shù)據(jù)是否存在
2.把*?替換成全部列
優(yōu)化階段
對應(yīng)的當(dāng)然是優(yōu)化器
我們知道MYSQL執(zhí)行語句的時候賊麻煩?又要判斷這個索引又判斷那個長度的?就是這一步?要確定一個執(zhí)行的具體方案
我們可以在查詢語句最前面加個?
explain
?命令,這樣就會輸出這條 SQL 語句的執(zhí)行計劃,然后執(zhí)行計劃中的 key 就表示執(zhí)行過程中使用了哪個索引?若果這個key參數(shù)是null的話?就說明沒有使用索引?也就是說?使用的是效率最差的全表查詢具體來講?優(yōu)化器會選擇什么方案呢?
SELECT id FROM product WHERE id > 1 AND name LIKE 'i%';
這條查詢語句的結(jié)果既可以使用主鍵索引,也可以使用普通索引,但是執(zhí)行的效率會不同。這時,就需要優(yōu)化器來決定使用哪個索引了。
這里使用的是覆蓋索引,也就是說?同時使用這兩個索引 這兩個索引上的信息足夠滿足查詢請求,不需要再回到主鍵索引上去取數(shù)據(jù)。(查詢主鍵索引的 B+ 樹的成本會比查詢二級索引的 B+ 的成本大,優(yōu)化器基于查詢成本的考慮,會選擇查詢代價小的普通索引。)
就是個啥流程呢?首先先在二級索引(name)中?找到匹配的數(shù)據(jù)?然后二級索引存的數(shù)據(jù)就是主鍵id?然后記錄id>1的數(shù)據(jù)?所以就不用再去主鍵索引了?直接就出結(jié)果了
執(zhí)行階段?
執(zhí)行階段?那肯定是執(zhí)行器
經(jīng)歷過優(yōu)化器了?我們已經(jīng)得到了執(zhí)行方案?那現(xiàn)在就是正式的執(zhí)行SQL的時機(jī)了
執(zhí)行的過程中,執(zhí)行器就會和存儲引擎交互了,交互是以記錄為單位的。
攏共有三種執(zhí)行方式:
- 主鍵索引查詢
- 全表掃描
- 索引下推
主鍵索引查詢
從語文的角度分析?他肯定是通過主鍵索引查詢的對吧(?)
我們看這行代碼
select * from product where id = 1;
?首先id為主鍵?肯定為1?再有這個篩選條件是等值?所以一定只會查詢一次
執(zhí)行器第一次查詢?調(diào)用read_first_record 函數(shù)指針指向的函數(shù)
這個函數(shù)指針被指向為 InnoDB 引擎索引查詢的接口(優(yōu)化器參數(shù)為const?有這個參數(shù)?儲存引擎才能選擇對應(yīng)的執(zhí)行方式),把條件?
id = 1
?交給存儲引擎,讓存儲引擎定位符合條件的第一條記錄。然后呢儲存引擎通過主鍵索引(b+樹)找到id為1的數(shù)據(jù)判斷1:如果記錄是不存在的,就會向執(zhí)行器上報記錄找不到的錯誤,然后查詢結(jié)束。如果記錄是存在的,就會將記錄返回給執(zhí)行器;
然后如果存在的話?判斷2
查看是否符合篩選條件?如果符合發(fā)送給客戶端,如果不符合則跳過該記錄。
執(zhí)行器查詢的過程是一個 while 循環(huán),所以還會再查一次,但是這次因為不是第一次查詢了,所以這次會調(diào)用 read_record 函數(shù)指針指向的函數(shù)(區(qū)別于上面的read_first_record),因為優(yōu)化器選擇的訪問類型為 const(前面提到的這個參數(shù)再次出現(xiàn)),這個函數(shù)指針被指向為一個永遠(yuǎn)返回 - 1 的函數(shù),所以當(dāng)調(diào)用該函數(shù)的時候,執(zhí)行器就退出循環(huán),也就是結(jié)束查詢了
全表掃描
select * from product where name = 'iphone';
第一步不變?還是調(diào)用 read_first_record 函數(shù)指針指向的函數(shù)?但是這次因為是全表掃描(優(yōu)化器參數(shù)為all)
第二步?從第一條記錄開始讀取,看它是否滿足WHERE條件(name =?iPhone)?如果不滿足跳過,滿足把這條記錄發(fā)給客戶端
執(zhí)行器查詢的過程是一個 while 循環(huán),所以還會再查一次,會調(diào)用 read_record 函數(shù)指針指向的函數(shù),因為優(yōu)化器選擇的訪問類型為 all,read_record 函數(shù)指針指向的還是 InnoDB 引擎全掃描的接口,所以接著向存儲引擎層要求繼續(xù)讀剛才那條記錄的下一條記錄,存儲引擎把下一條記錄取出后就將其返回給執(zhí)行器(Server層),執(zhí)行器繼續(xù)判斷條件,不符合查詢條件即跳過該記錄,否則發(fā)送到客戶端;一直重復(fù)上述過程,直到存儲引擎把表中的所有記錄讀完,然后向執(zhí)行器(Server層) 返回了讀取完畢的信息; 執(zhí)行器收到存儲引擎報告的查詢完畢的信息,退出循環(huán),停止查詢。?
索引下推
它是MySQL 5.6 推出的查詢優(yōu)化策略
select * from t_user where age > 20 and reward = 100000
假如說我們創(chuàng)建了?對于age和reward的索引
聯(lián)合索引當(dāng)遇到范圍查詢 (>、<) 就會停止匹配,也就是?age 字段能用到聯(lián)合索引,但是 reward 字段則無法利用到索引(硬記就行?后面可能會更新索引失效的部分)
如果不使用索引下推
第一步還是:?server層調(diào)用儲存引擎?找到滿足age>20的第一條記錄
第二步:存儲引擎根據(jù)二級索引的 B+ 樹快速定位到這條記錄后,獲取主鍵值,然后進(jìn)行回表操作,將完整的記錄返回給 Server 層
第三步:
server再判斷reward是否等于100000 如果符合?返回給用戶?不符合忽略
第四步:接著拿下一條記錄(不需要再次定位記錄了?記錄之間由鏈表連接)?存儲引擎在二級索引定位到記錄后,獲取主鍵值,然后回表操作拿到全部記錄數(shù)據(jù) 判斷是否符合
然后三四步循環(huán)?直到age>20的記錄查詢完畢
沒有索引下推的時候,每查詢到一條二級索引記錄,都要進(jìn)行回表操作,然后將記錄返回給 Server,接著 Server 再判斷該記錄的 reward 是否等于 100000
那么有索引下推的時候呢?
Server 層首先調(diào)用存儲引擎的接口定位到滿足查詢條件的第一條二級索引記錄,也就是定位到 age > 20 的第一條記錄;
存儲引擎定位到二級索引后,先不執(zhí)行回表操作,而是先判斷一下該索引中包含的列(reward列)的條件(reward 是否等于 100000)是否成立。如果條件不成立,則直接跳過該二級索引。如果成立,則執(zhí)行回表操作,將完成記錄返回給 Server 層。
Server 層在判斷其他的查詢條件(本次查詢沒有其他條件)是否成立(為什么只有reward條件可以用儲存引擎判斷呢??因為它雖然沒用上聯(lián)合索引?但是索引的數(shù)據(jù)還是有的?在聯(lián)合索引中對著id找reward還是很方便的(一對一對應(yīng)的)),如果成立則將其發(fā)送給客戶端;否則跳過該記錄,然后向存儲引擎索要下一條記錄。 如此往復(fù),直到存儲引擎把表中的所有記錄讀完。
就相當(dāng)于把判斷reward是否等于100000這件事外包給儲存引擎了文章來源:http://www.zghlxwxcb.cn/news/detail-521910.html
索引下推能夠減少二級索引在查詢時的回表操作,提高查詢的效率,因為它將 Server 層部分負(fù)責(zé)的事情,交給存儲引擎層去處理了文章來源地址http://www.zghlxwxcb.cn/news/detail-521910.html
到了這里,關(guān)于MYSQL執(zhí)行一條SELECT語句的具體流程的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!