(二)全解MySQL:一條SQL語句從誕生至結束的多姿多彩歷程!

引言

? ?在上篇文章中,我們以《MySQL架構篇》拉開了MySQL數據庫的的序幕,上篇文章中將MySQL分層架構中的每一層都進行了詳細闡述。而在本篇中,則會進一步站在一條SQL的角度,從SQL的誕生開始,到SQL執行、數據返回等全鏈路進行分析。

? ?在此之前呢,其實也寫過兩篇類似的姊妹篇,第一篇講的是《一個網絡請求的神奇之旅!》,在其中化身一個網絡請求,切身感受了從瀏覽器發出后,到響應數據的返回。而在更早的時候,《JVM系列》中也有一篇文章,講的是《一個Java對象從誕生到死亡的歷程》,其中聊到了Java對象是如何誕生的,運行時會經歷什么過程?使用結束后又是如何回收的。

在本篇中,則會在站在數據庫的視角,再次感受“一條SQL多姿多彩的歷程”!你如果認真的看完了“一個請求、一個對象、一條SQL”這三部曲后,相信你對于程序開發又會有一個全新的深刻認知。

一、一條SQL是如何誕生的?

? ?SQL語句都誕生于客戶端,主要有兩種方式產生一條SQL,一種是由開發者自己手動編寫,另一種則是相關的ORM框架自動生成,一般情況下,MySQL運行過程中收到的大部分SQL都是由ORM框架生成的,比如Java中的MyBatis、Hibernate框架等。

? ?同時,SQL生成的時機一般都與用戶的請求有關,當用戶在系統中進行了某項操作,一般都會產生一條SQL,例如我們在瀏覽器上輸入如下網址:

https://juejin.cn/user/862486453028888/posts

此時,就會先請求掘金的服務器,然后由掘金內部實現中的ORM框架,根據請求參數生成一條SQL,類似于下述的偽SQL

select * from juejin_article where userid = 862486453028888;

這條SQL大致描述的意思就是:根據用戶請求的「作者ID」,在掘金數據庫的文章表中,查詢該作者的所有文章信息。

? ?從上述這個案例中可以明顯感受出來,用戶瀏覽器上看到的數據一般都來自于數據庫,而數據庫執行的SQL則源自于用戶操作,兩者是相輔相成的關系,也包括任何寫操作(增、刪、改),本質上也會被轉換一條條SQL,也舉個簡單的例子:

# 請求網址(Request URL)
https://www.xxx.com/user/register

# 請求參數(Request Param)
{
    user_name : "竹子愛熊貓",
    user_pwd : "123456",
    user_sex : "男",
    user_phone : "18888888888",
    ......
}
# 這里對于用戶密碼的處理不夠嚴謹,沒有做加密操作不要在意~

比如上述這個用戶注冊的案例,當用戶在網頁上點擊「注冊」按鈕時,會向目標網站的服務器發送一個post請求,緊接著同樣會根據請求參數,生成一條SQL,如下:

insert into table_user (user_name,user_pwd,user_sex,user_phone,....)
    VALUES ("竹子愛熊貓", "123456", "男", "18888888888", ....);

也就是說,一條SQL的誕生都源自于一個用戶請求,在開發程序時,SQL的大體邏輯我們都會由業務層的編碼決定,具體的SQL語句則是根據用戶的請求參數,以及提前定制好的“SQL骨架”拼揍而成。當然,在Java程序或其他語言編寫的程序中,只能生成SQL,而SQL真正的執行工作是需要交給數據庫去完成的。

二、一條SQL執行前會經歷的過程

? ?經過上一步之后,一條完整的SQL就誕生了,為了SQL能夠正常執行,首先會先去獲取一個數據庫連接對象,上篇關于MySQL的架構篇曾聊到過,MySQL連接層中會維護著一個名為「連接池」的玩意兒,但相信大家也都接觸過「數據庫連接池」這個東西,比如Java中的C3P0、Druid、DBCP....等各類連接池。

那此時在這里可以思考一個問題,為什么數據庫自己維護了連接池的情況下,在MySQL客戶端中還需要再次維護一個數據庫連接池呢?接下來一起聊一聊。

2.1、數據庫連接池的必要性

? ?眾所周知,當要在Java中創建一個數據庫連接時,首先會去讀取配置文件中的連接地址、賬號密碼等信息,然后根據配置的地址信息,發起網絡請求獲取數據庫連接對象。在這個過程中,由于涉及到了網絡請求,那此時必然會先經歷TCP三次握手的過程,同時獲取到連接對象完成SQL操作后,又要釋放這個數據庫連接,此時又需要經歷TCP四次揮手過程。

從上面的描述中可以明顯感知出,在Java中創建、關閉數據庫連接的過程,過程開銷其實比較大,而在程序上線后,又需要頻繁進行數據庫操作。因此如果每次操作數據庫時,都獲取新的連接對象,那整個Java程序至少會有四分之一的時間內在做TCP三次握手/四次揮手工作,這對整個系統造成的后果可想而知....

也正是由于上述原因,因此大名鼎鼎的「數據庫連接池」登場了,「數據庫連接池」和「線程池」的思想相同,會將數據庫連接這種較為珍貴的資源,利用池化技術對這種資源進行維護。也就代表著之后需要進行數據庫操作時,不需要自己去建立連接了,而是直接從「數據庫連接池」中獲取,用完之后再歸還給連接池,以此達到復用的效果。

當然,連接池中維護的連接對象也不會一直都在,當長時間未進行SQL操作時,連接池也會銷毀這些連接對象,而后當需要時再次創建,不過何時創建、何時銷毀、連接數限制等等這些工作,都交給了連接池去完成,無需開發者自身再去關注。

在Java中,目前最常用的數據庫連接池就是阿里的Druid,一般咱們都會用它作為生產環境中的連接池:
[圖片上傳失敗...(image-7aa740-1670309104421)]

目前Druid已經被阿里貢獻給Apache軟件基金會維護了~

OK~,回到前面拋出的問題,有了MySQL連接池為何還需要在客戶端維護一個連接池?

對于這個問題,相信大家在心里多少都有點答案了,原因很簡單,兩者都是利用池化技術去達到復用資源、節省開銷、提升性能的目的,只不過針對的方向不同。

MySQL的連接池主要是為了實現復用線程的目的,因為每個數據庫連接在MySQL中都會使用一條線程維護,而每次為客戶端分配連接對象時,都需要經歷創建線程、分配棧空間....這些繁重的工作,這個過程需要時間,同時資源開銷也不小,所以MySQL利用池化技術解決了這些問題。

而客戶端的連接池,主要是為了實現復用數據庫連接的目的,因為每次SQL操作都需要經過TCP三次握手/四次揮手的過程,過程同樣耗時且占用資源,因此也利用池化技術解決了這個問題。

其實也可以這樣理解,MySQL連接池維護的是工作線程,客戶端連接池則維護的是網絡連接。

2.2、SQL執行前會發生的事情

? ?回歸本文主題,當完整的SQL生成后,會先去連接池中嘗試獲取一個連接對象,那接下來會發生什么事情呢?如下圖:
[圖片上傳失敗...(image-85f48b-1670309104421)]
當嘗試從連接池中獲取連接時,如果此時連接池中有空閑連接,可以直接拿到復用,但如果沒有,則要先判斷一下當前池中的連接數是否已達到最大連接數,如果連接數已經滿了,當前線程則需要等待其他線程釋放連接對象,沒滿則可以直接再創建一個新的數據庫連接使用。

假設此時連接池中沒有空閑連接,需要再次創建一個新連接,那么就會先發起網絡請求建立連接。

首先會經過《TCP的三次握手過程》,對于這塊就不再細聊了,畢竟之前聊過很多次了。當網絡連接建立成功后,也就等價于在MySQL中創建了一個客戶端會話,然后會發生下圖一系列工作:
[圖片上傳失敗...(image-2d7987-1670309104421)]

  • ①首先會驗證客戶端的用戶名和密碼是否正確:
    • 如果用戶名不存在或密碼錯誤,則拋出1045的錯誤碼及錯誤信息。
    • 如果用戶名和密碼驗證通過,則進入第②步。
  • ②判斷MySQL連接池中是否存在空閑線程:
    • 存在:直接從連接池中分配一條空閑線程維護當前客戶端的連接。
    • 不存在:創建一條新的工作線程(映射內核線程、分配棧空間....)。
  • ③工作線程會先查詢MySQL自身的用戶權限表,獲取當前登錄用戶的權限信息并授權。

到這里為止,執行SQL前的準備工作就完成了,已經打通了執行SQL的通道,下一步則是準備執行SQL語句,工作線程會等待客戶端將SQL傳遞過來。

三、一條SQL語句在數據庫中是如何執行的?

? ?經過連接層的一系列工作后,接著客戶端會將要執行的SQL語句通過連接發送過來,然后會進行MySQL服務層進行處理,不過根據用戶的操作不同,MySQL執行SQL語句時也會存在些許差異,這里是指讀操作和寫操作,兩者SQL的執行過程并不相同,下面先來看看select語句的執行過程。

3.1、一條查詢SQL的執行過程

在分析查詢SQL的執行流程之前,咱們先模擬一個案例,以便于后續分析:

-- SQL語句
SELECT user_id FROM `zz_user` WHERE user_sex = "男" AND user_name = "竹子④號";

-- 表數據
+---------+--------------+----------+-------------+
| user_id | user_name    | user_sex | user_phone  |
+---------+--------------+----------+-------------+
|       1 | 竹子①號      | 男       | 18888888888 |
|       2 | 竹子②號      | 男       | 13588888888 |
|       3 | 竹子③號      | 男       | 15688888888 |
|       4 | 熊貓①號      | 女       | 13488888888 |
|       5 | 熊貓②號      | 女       | 18588888888 |
|       6 | 竹子④號      | 男       | 17777777777 |
|       7 | 熊貓③號      | 女       | 16666666666 |
+---------+--------------+----------+-------------+

先上個SQL執行的完整流程圖,后續再逐步分析每個過程:
[圖片上傳失敗...(image-41304b-1670309104421)]

  • ①先將SQL發送給SQL接口,SQL接口會對SQL語句進行哈希處理。
  • SQL接口在緩存中根據哈希值檢索數據,如果緩存中有則直接返回數據。
  • ③緩存中未命中時會將SQL交給解析器,解析器會判斷SQL語句是否正確:
    • 錯誤:拋出1064錯誤碼及相關的語法錯誤信息。
    • 正確:將SQL語句交給優化器處理,進入第④步。
  • ④優化器根據SQL制定出不同的執行方案,并擇選出最優的執行計劃。
  • ⑤工作線程根據執行計劃,調用存儲引擎所提供的API獲取數據。
  • ⑥存儲引擎根據API調用方的操作,去磁盤中檢索數據(索引、表數據....)。
  • ⑦發送磁盤IO后,對于磁盤中符合要求的數據逐條返回給SQL接口。
  • SQL接口會對所有的結果集進行處理(剔除列、合并數據....)并返回。

上述是一個簡單的流程概述,一般情況下查詢SQL的執行都會經過這些步驟,下面再將每一步拆開詳細聊一聊。

SQL接口會干的工作

? ?當客戶端將SQL發送過來之后,SQL緊接著會交給SQL接口處理,首先會對SQL做哈希處理,也就是根據SQL語句計算出一個哈希值,然后去「查詢緩存」中比對,如果緩存中存在相同的哈希值,則代表著之前緩存過相同SQL語句的結果,那此時則直接從緩存中獲取結果并響應給客戶端。

在這里,如果沒有從緩存中查詢到數據,緊接著會將SQL語句交給解析器去處理。

SQL接口除開對SQL進行上述的處理外,后續還會負責處理結果集(稍后分析)。

解析器中會干的工作

? ?解析器收到SQL后,會開始檢測SQL是否正確,也就是做詞法分析、語義分析等工作,在這一步,解析器會根據SQL語言的語法規則,判斷客戶端傳遞的SQL語句是否合規,如果不合規就會返回1064錯誤碼及錯誤信息:

ERROR 1064 (42000): You have an error in your SQL syntax; check....

? ?但如果SQL語句沒有問題,此時就會對SQL語句進行關鍵字分析,也就是根據SQL中的SELECT、UPDATE、DELETE等關鍵字,先判斷SQL語句的操作類型,是讀操作還是寫操作,然后再根據FROM關鍵字來確定本次SQL語句要操作的是哪張表,也會根據WHERE關鍵字后面的內容,確定本次SQL的一些結果篩選條件.....。

總之,經過關鍵字分析后,一條SQL語句要干的具體工作就會被解析出來。

解析了SQL語句中的關鍵字之后,解析器會根據分析出的關鍵字信息,生成對應的語法樹,然后交給優化器處理。

在這一步也就相當于Java中的.java源代碼變為.class字節碼的過程,目的就是將SQL語句翻譯成數據庫可以看懂的指令。

優化器中會干的工作

? ?經過解析器的工作后會得到一個SQL語法樹,也就是知道了客戶端的SQL大體要干什么事情了,接著優化器會對于這條SQL,給出一個最優的執行方案,也就是告訴工作線程怎么執行效率最高、最節省資源以及時間。

? ?優化器最開始會根據語法樹制定出多個執行計劃,然后從多個執行計劃中選擇出一個最好的計劃,交給工作線程去執行,但這里究竟是如何選擇最優執行計劃的,相信大家也比較好奇,那此時我們結合前面給出的案例分析一下。

SELECT user_id FROM `zz_user` WHERE user_sex = "男" AND user_name = "竹子④號";

先來看看,對于這條SQL而言,總共有幾種執行方案呢?答案是兩種。

  • ①先從表中將所有user_sex="男"的數據查出來,再從結果中獲取user_name="竹子④號"的數據。
  • ②先從表中尋找user_name="竹子④號"的數據,再從結果中獲得user_sex="男"的數據。

再結合前面給出的表數據,暫且分析一下上述兩種執行計劃哪個更好呢?

+---------+--------------+----------+-------------+
| user_id | user_name    | user_sex | user_phone  |
+---------+--------------+----------+-------------+
|       1 | 竹子①號      | 男       | 18888888888 |
|       2 | 竹子②號      | 男       | 13588888888 |
|       3 | 竹子③號      | 男       | 15688888888 |
|       4 | 熊貓①號      | 女       | 13488888888 |
|       5 | 熊貓②號      | 女       | 18588888888 |
|       6 | 竹子④號      | 男       | 17777777777 |
|       7 | 熊貓③號      | 女       | 16666666666 |
+---------+--------------+----------+-------------+

如果按照第①種方案執行,此時會先得到四條user_sex="男"的數據,然后再從四條數據中查找user_name="竹子④號"的數據。

如果按照第②中方案執行,此時會直接得到一條user_name="竹子④號"的數據,然后再判斷一下user_sex是否為"男",是則直接返回,否則返回空。

相較于兩種執行方案的過程,前者需要掃一次全表,然后再對結果集逐條判斷。而第二種方案掃一次全表后,只需要再判斷一次就可以了,很明顯可以感知出:第②種執行計劃是最優的,因此優化器會給出第②種執行計劃。

? ?經過上述案例的講解后,大家應該能夠對優化器的工作進一步理解。但上述案例僅是為了幫助大家理解,實際的SQL優化過程會更加復雜,例如多表join查詢時,怎么查更合適?單表復雜SQL查詢時,有多條索引可以走,走哪條速度最快....,因此一條SQL的最優執行計劃,需要結合多方面的優化策略來生成,例如MySQL優化器的一些優化準則如下:

  • ?多條件查詢時,重排條件先后順序,將效率更好的字段條件放在前面。
  • ?當表中存在多個索引時,選擇效率最高的索引作為本次查詢的目標索引。
  • ?使用分頁Limit關鍵字時,查詢到對應的數據條數后終止掃表。
  • ?多表join聯查時,對查詢表的順序重新定義,同樣以效率為準。
  • ?對于SQL中使用函數時,如count()、max()、min()...,根據情況選擇最優方案。
    • max()函數:走B+樹最右側的節點查詢(大的在右,小的在左)。
    • min()函數:走B+樹最左側的節點查詢。
    • count()函數:如果是MyISAM引擎,直接獲取引擎統計的總行數。
    • ......
  • ?對于group by分組排序,會先查詢所有數據后再統一排序,而不是一開始就排序。
  • ?......

總之,根據SQL不同,優化器也會基于不同的優化準則選擇出最佳的執行計劃。但需要牢記的一點是:MySQL雖然有優化器,但對于效率影響最大的還是SQL本身,因此編寫出一條優秀的SQL,才是提升效率的最大要素

存儲引擎中會干的工作

? ?經過優化器后,會得到一個最優的執行計劃,緊接著工作線程會根據最優計劃,去依次調用存儲引擎提供的API,在上篇文章中提到過,存儲引擎主要就是負責在磁盤讀寫數據的,不同的存儲引擎,存儲在本地磁盤中的數據結構也并不相同,但這些底層實現并不需要MySQL的上層服務關心,因為上層服務只需要負責調用對應的API即可,存儲引擎的API功能都是相同的。

? ?工作線程根據執行計劃調用存儲引擎的API查詢指定的表,最終也就是會發生磁盤IO,從磁盤中檢索數據,當然,檢索的數據有可能是磁盤中的索引文件,也有可能是磁盤中的表數據文件,這點要根據執行計劃來決定,我們只需要記住,經過這一步之后總能夠得到執行結果即可。

但有個小細節,還記得最開始創建數據庫連接時,對登錄用戶的授權步驟嘛?當工作線程去嘗試查詢某張表時,會首先判斷一下線程自身維護的客戶端連接,其登錄的用戶是否具備這張表的操作權限,如果不具備則會直接返回權限不足的錯誤信息。

? ?不過存儲引擎從磁盤中檢索出目標數據后,并不會將所有數據全部得到后再返回,而是會逐條返回給SQL接口,然后會由SQL接口完成最后的數據聚合工作,對于這點稍后會詳細分析。

下來再來看看寫入SQL的執行過程,因為讀取和寫入操作之間,也會存在些許差異。

3.2、一條寫入SQL的執行過程

? ?假設此時要執行下述這一條寫入類型的SQL語句(還是基于之前的表數據):

UPDATE `zz_user` SET user_sex = "女" WHERE user_id = 6;

上面這條SQL是一條典型的修改SQL,但除開修改操作外,新增、刪除等操作也屬于寫操作,寫操作的意思是指會對表中的數據進行更改。同樣先上一個完整的流程圖:
[圖片上傳失敗...(image-c5b383-1670309104421)]
從上圖來看,相較于查詢SQL,寫操作的SQL執行流程明顯會更復雜一些,這里也先簡單總結一下每一步流程,然后再詳細分析一下其中一些與查詢SQL中不同的步驟:

  • ①先將SQL發送給SQL接口,SQL接口會對SQL語句進行哈希處理。
  • ②在緩存中根據哈希值檢索數據,如果緩存中有則將對應表的所有緩存全部刪除。
  • ③經過緩存后會將SQL交給解析器,解析器會判斷SQL語句是否正確:
    • 錯誤:拋出1064錯誤碼及相關的語法錯誤信息。
    • 正確:將SQL語句交給優化器處理,進入第④步。
  • ④優化器根據SQL制定出不同的執行方案,并擇選出最優的執行計劃。
  • ⑤在執行開始之前,先記錄一下undo-log日志和redo-log(prepare狀態)日志。
  • ⑥在緩沖區中查找是否存在當前要操作的行記錄或表數據(內存中):
    • 存在:
      • ⑦直接對緩沖區中的數據進行寫操作。
      • ⑧然后利用Checkpoint機制刷寫到磁盤。
    • 不存在:
      • ⑦根據執行計劃,調用存儲引擎的API
      • ⑧發生磁盤IO,對磁盤中的數據做寫操作。
  • ⑨寫操作完成后,記錄bin-log日志,同時將redo-log日志中的記錄改為commit狀態。
  • ⑩將SQL執行耗時及操作成功的結果返回給SQL接口,再由SQL接口返回給客戶端。

整個寫SQL的執行過程,前面的一些步驟與查SQL執行的過程沒太大差異,唯一一點不同的在于緩存哪里,原本查詢時是從緩存中嘗試獲取數據。而寫操作時,由于要對表數據發生更改,因此如果在緩存中發現了要操作的表存在緩存,則需要將整個表的所有緩存清空,確保緩存的強一致性。

OK~,除開上述這點區別外,另外多出了唯一性判斷、一個緩沖區寫入,以及幾個寫入日志的步驟,接下來一起來聊聊這些。

唯一性判斷主要是針對插入、修改語句來說的,因為如果表中的某個字段建立了唯一約束或唯一索引后,當插入/修改一條數據時,就會先檢測一下目前插入/修改的值,是否與表中的唯一字段存在沖突,如果表中已經存在相同的值,則會直接拋出異常,反之會繼續執行。

很簡單哈~,接著再來聊聊緩沖區和日志!

其實在上篇中聊到過,由于CPU和磁盤之間的性能差距實在過大,因此MySQL中會在內存中設計一個「緩沖區」的概念,主要目的是在于彌補CPU與磁盤之間的性能差距。

緩沖區中會做的工作

??在真正調用存儲引擎的API操作磁盤之前,首先會在「緩沖區」中查找有沒有要操作的目標數據/目標表,如果存在則直接對緩沖區中的數據進行操作,然后MySQL會在后臺以一種名為Checkpoint的機制,將緩沖區中更新的數據刷回到磁盤。只有當緩沖區沒有找到目標數據時,才會去真正調用存儲引擎的API,然后發生磁盤IO,去對應磁盤中的表數據進行修改。

不過值得注意的一點是:雖然緩沖區中有數據時會先操作緩沖區,然后再通過Checkpoint機制刷寫磁盤,但這兩個過程不是連續的!也就是說,當線程對緩沖區中的數據操作完成后,會直接往下走,數據落盤的工作則會交給后臺線程。
不過雖然兩者之間是異步的,但對于人而言,這個過程不會有太大的感知,畢竟CPU在運行的時候,都是按納秒、微秒級作為單位。

??但不管數據是在緩沖區還是磁盤,本質上數據更改的動作都是發生在內存的,就算是修改磁盤數據,也是將數據讀到內存中操作,然后再將數據寫回磁盤。不過在「寫SQL」執行的前后都會記錄日志,這點下面詳細聊聊,這也是寫SQL與讀SQL最大的區別。

寫操作時的日志

??執行「讀SQL」一般都不會有狀態,也就是說:MySQL執行一條select語句,幾乎不會留下什么痕跡。但這里為什么用幾乎這個詞呢?因為查詢時也有些特殊情況會留下“痕跡”,就是慢查詢SQL

慢查詢SQL:查詢執行過程耗時較長的SQL記錄。
在執行查詢SQL時,大多數的普通查詢MySQL并不關心,但慢查詢SQL除外,這類SQL一般是引起響應緩慢問題的“始作俑者”,所以當一條查詢SQL的執行時長超過規定的時間限制,就會被“記錄在案”,也就是會記錄到慢查詢日志中。

??與「查詢SQL」恰恰相反,任何一條寫入類型的SQL都是有狀態的,也就代表著只要是會對數據庫發生更改的SQL,執行時都會被記錄在日志中。首先所有的寫SQL在執行之前都會生成對應的撤銷SQL,撤銷SQL也就是相反的操作,比如現在執行的是insert語句,那這里就生成對應的delete語句....,然后記錄在undo-log撤銷/回滾日志中。但除此之外,還會記錄redo-log日志。

??redo-log日志是InnoDB引擎專屬的,主要是為了保證事務的原子性和持久性,這里會將寫SQL的事務過程記錄在案,如果服務器或者MySQL宕機,重啟時就可以通過redo_log日志恢復更新的數據。在「寫SQL」正式執行之前,就會先記錄一條prepare狀態的日志,表示當前「寫SQL」準備執行,然后當執行完成并且事務提交后,這條日志記錄的狀態才會更改為commit狀態。

除開上述的redo-log、undo-log日志外,同時還會記錄bin-log日志,這個日志和redo-log日志很像,都是記錄對數據庫發生更改的SQL,只不過redo-logInnoDB引擎專屬的,而bin-log日志則是MySQL自帶的日志。

不過無論是什么日志,都需要在磁盤中存儲,而本身「寫SQL」在磁盤中寫表數據效率就較低了,此時還需寫入多種日志,效率定然會更低。對于這個問題MySQL以及存儲引擎的設計者自然也想到了,所以大部分日志記錄也是采用先寫到緩沖區中,然后再異步刷寫到磁盤中。

比如redo-log日志在內存中會有一個redo_log緩沖區中,bin-log日志也同理,當需要記錄日志時,都是先寫到內存中的緩沖區。

那內存中的日志數據何時會刷寫到磁盤呢?對于這點則是由刷盤策略來決定的,redo-log日志的刷盤策略由innodb_flush_log_at_trx_commit參數控制,而bin-log日志的刷盤策略則可以通過sync_binlog參數控制:

  • innodb_flush_log_at_trx_commit
    • 0:間隔一段時間,然后再刷寫一次日志到磁盤(性能最佳)。
    • 1:每次提交事務時,都刷寫一次日志到磁盤(性能最差,最安全,默認策略)。
    • 2:有事務提交的情況下,每間隔一秒時間刷寫一次日志到磁盤。
  • sync_binlog
    • 0:同上述innodb_flush_log_at_trx_commit參數的2
    • 1:同上述innodb_flush_log_at_trx_commit參數的1,每次提交事務都會刷盤,默認策略。

到這里就大致闡述了一下「寫SQL」執行時,會寫的一些日志記錄,這些日志在后續做數據恢復、遷移、線下排查時都較為重要,因此后續也會單開一篇詳細講解。

四、一條SQL執行完成后是如何返回的?

? ?一條「讀SQL」或「寫SQL」執行完成后,由于SQL操作的屬性不同,兩者之間也會存在差異性,

4.1、讀類型的SQL返回

? ?前面聊到過,MySQL執行一條查詢SQL時,數據是逐條返回的模式,因為如果等待所有數據全部查出來之后再一次性返回,必然會導致撐滿內存。

不過這里的返回,并不是指返回客戶端,而是指返回SQL接口,因為從磁盤中檢索出目標數據時,一般還需要對這些數據進行再次處理,舉個例子理解一下。

SELECT user_id FROM `zz_user` WHERE user_sex = "男" AND user_name = "竹子④號";

還是之前那條查詢SQL,這條SQL要求返回的結果字段僅有一個user_id,但在磁盤中檢索數據時,會直接將這個字段單獨查詢出來嗎?并不是的,而是會將整條行數據全部查詢出來,如下:

+---------+--------------+----------+-------------+
| user_id | user_name    | user_sex | user_phone  |
+---------+--------------+----------+-------------+
|    6    |   竹子④號    |    男    | 17777777777 |
+---------+--------------+----------+-------------+

從行記錄中篩選出最終所需的結果字段,這個工作是在SQL接口中完成的,也包括多表聯查時,數據的合并工作,同樣也是在SQL接口完成,其他SQL亦是同理。

還有一點需要牢記:就算沒有查詢到數據,也會將執行狀態、執行耗時這些信息返回給SQL接口,然后由SQL接口向客戶端返回NULL

不過當查詢到數據后,在正式向客戶端返回之前,還會順手將結果集放入到緩存中。

4.2、寫類型的SQL返回

? ?寫SQL執行的過程會比讀SQL復雜,但寫SQL的結果返回卻很簡單,寫類型的操作執行完成之后,僅會返回執行狀態、受影響的行數以及執行耗時,比如:

UPDATE `zz_user` SET user_sex = "女" WHERE user_id = 6;

這條SQL執行成功后,會返回Query OK, 1 row affected (0.00 sec)這組結果,最終返回給客戶端的則只有「受影響的行數」,如果寫SQL執行成功,這個值一般都會大于0,反之則會小于0

4.3、執行結果是如何返回給客戶端的?

? ?對于這個問題的答案其實很簡單,由于執行當前SQL的工作線程,本身也維護著一個數據庫連接,這個數據庫連接實際上也維持著客戶端的網絡連接,如下:
[圖片上傳失敗...(image-960bac-1670309104421)]
當結果集處理好了之后,直接通過Host中記錄的地址,將結果集封裝成TCP數據報,然后返回即可。

數據返回給客戶端之后,除非客戶端主動輸入exit等退出連接的命令,否則連接不會立馬斷開。

如果要斷開客戶端連接時,又會經過TCP四次揮手的過程。

不過就算與客戶端斷開了連接,MySQL中創建的線程并不會銷毀,而是會放入到MySQL的連接池中,等待其他客戶端復用當前連接。一般情況下,一條線程在八小時內未被復用,才會觸發MySQL的銷毀工作。

五、SQL執行篇總結

? ?看到這里,SQL執行原理篇也走進了尾聲,其實SQL語句的執行過程,實際上也就是MySQL的架構中一層層對其進行處理,理解了MySQL架構篇的內容后,相信看SQL執行篇也不會太難,經過這篇文章的學習后,相信大家對數據庫的原理知識也能夠進一步掌握,那我們下篇再見~

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,156評論 6 529
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 97,866評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 174,880評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,398評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,202評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,743評論 1 320
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,822評論 3 438
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 41,962評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,476評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,444評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,579評論 1 365
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,129評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,840評論 3 344
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,231評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,487評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,177評論 3 388
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,568評論 2 370

推薦閱讀更多精彩內容