我准備從mysql的實現出發,將mysql好好理解一下,從他的邏輯結構一層一層出發,感受一下,所以再學第一層之前,要先對mysql整體的邏輯結構有一個初步認識
mysql邏輯架構
整體來說,MySql的邏輯架構分成三個部分,這個之前我以為提到過
1)客戶端:主要是建立連接的過程,交互的過程
2)核心服務
3)存儲引擎
這個可能比較抽象,我們結合MySql的查詢過程,結合着進行學習
MySql查詢過程
這個圖其實就是在第一個的基礎上,進行的更加細致的划分,因為上面只是大致畫出了邏輯架構,但是這個就展示了一整個過程。
我說一下大致的過程:
1)客戶端向服務端發起一條請求
2)服務端先檢查查詢緩存,如果命中緩存,則直接返回結果,否則交給下一階段
3)服務器進行SQL解析,預處理,在經過查詢優化形成對應的執行計划
4)mysql根據執行計划,調用API給存儲引擎,進行數據的讀取和存儲
5)將結果返回給客戶端,並緩存查詢結果
大致整體的步驟就是這樣的,我們要把每一步都盡量的深入思考下,我可能考慮的有欠缺,歡迎大家進行留言
今天先不深入數據庫里面,先將mysql的通信協議搞清楚,也就是執行sql語句之前都干了什么
要了解mysql通信協議,就要知道mysql是通過什么連接的,這個怎么考率的,mysql是應用,我們需要實現的是mysql客戶端與服務端進行通信,這里好比http,所以在客戶端找到服務端之前,就需要他們所處的物理機先建立起連接,就如同http建立連接之前,需要tcp先建立連接。
Mysql的主要連接方式包括:Unix套接字,內存共享,命名管道,TCP/IP套接字等。
有的同學可能問了,怎么這么多方式,那我用的哪一種呢,或者是我應該用哪一種呢,其實他們並不是等價的
Unix套接字:
在Linux和Unix環境下,可以使用Unix套接字進行Mysql服務器的連接;Unix套接字其實不是一個網絡協議,只能在客戶端和Mysql服務器在同一台電腦上才可以使用
命名管道和內存共享
在window系統中客戶端和Mysql服務器在同一台電腦上,可以使用命名管道和共享內存的方式,
命名管道開啟:–shared-memory=on/off;
共享內存開啟:–enable-named-pipe=on/off;
TCP/IP套接字
任何系統下都可以使用的方式,也是使用的最多的方式,我主要介紹的也是這種方式
其實熟悉操作系統的朋友應該能體會出來,像前兩種,因為客戶端和服務端在同一台主機上,也就是一台主機的兩個應用,所以這也就是進程間通信的方式,而在不同的主機上就不一樣了,就需要網絡,tcp/ip建立了。
mysql通信過程
了解了mysql基於的就是tcp的底層協議,所以必然,需要經歷tcp的三次握手,沒錯第一步就是tcp的三次握手(因為這里不是重點,就不詳細說明,不太清楚的同學,可以查看我network的相關博客),建立連接之后就可以發送sql命令了嗎,當然不能,細心的同學會發現,我用客戶端登陸的時候,是需要用戶名,密碼的,這才是真正的mysql客戶端與服務端的交互過程,之前還沒有到應用層,下面就說一下,交互過程
mysql客戶端與服務端的交互過程
主要分為兩部分:握手認證階段,命令執行階段
注意哦,這個握手和上面過的握手不一樣哦
1.1握手認證階段
握手認證階段為客戶端與服務器建立連接后進行,交互過程如下:
服務器 -> 客戶端:握手初始化消息
客戶端 -> 服務器:登陸認證消息
服務器 -> 客戶端:認證結果消息
1.2命令執行階段
客戶端認證成功后,會進入命令執行階段,交互過程如下:
客戶端 -> 服務器:執行命令消息
服務器 -> 客戶端:命令執行結果
不知道大家看了有沒有產生一些問題,那我就我的一些問題說一下
為什么還要進行三次握手認證
因為tcp三次握手,只是將客戶端與服務端建立起了連接,然后通過端口知道我要訪問的是mysql這個服務,但是mysql它不同於http,只要你知道url就能得到一個響應,mysql必須登陸后你才能進行操作。所以這個過程最重要的就是驗證客戶端的登陸權限。
為什么是服務端主動給客戶端發送認證呢?
http不是說只有客戶端主動與服務端進行請求,服務端不是不能在沒有請求的情況下主動進行響應嗎?
首先,大家也不要陷入誤區,在進行認證的這個交互中,實際上客戶端與服務端還沒有進行任何業務上的往來,只是進行一個認證,所以與上面說的http的不同,如果細心的話你會發現,認證成功后,在命令執行階段,mysql這種通信方式是與http非常類似的,在沒有請求的情況下,服務端的mysql也不會主動給你發送任何數據,所以這里不要混淆。
再說為什么服務端先發送,那肯定是因為他有不得不發送的道理,所以我們就需要理解一下,他發送的是什么東西。
mysql報文
主要分成三個部分:登錄認證報文,客戶端請求報文以及服務器端返回報,基於mysql5.1.73(mysql4.1以后的版本)
登陸認證報文
1)握手初始化報文(服務端->客戶端)
協議版本號:服務端所使用的mysql協議的版本號
服務器線程ID:服務端為此客戶端所創建的線程的ID
挑戰隨機數:MySQL數據庫用戶認證采用的是挑戰/應答的方式,服務器生成該挑戰數並發送給客戶端,由客戶端進行處理並返回相應結果,然后服務器檢查是否與預期的結果相同,從而完成用戶認證的過程。
**服務器權能標志:**用於與客戶端協商通訊方式
這個我稍微解釋下,他這個只要發送的就是,我服務端能接受的通訊方式是什么樣的,我們下面詳細說一下
登陸認證報文(客戶端 -> 服務器)
客戶端權能標志客戶端收到服務器發來的初始化報文后,會對服務器發送的權能標志進行修改,保留自身所支持的功能,然后將權能標返回給服務器,從而保證服務器與客戶端通訊的兼容性。
消息長度客戶端發送請求時所支持的最大消息長度值
字符編碼表示通訊過程中使用的字符編碼,與服務器在認證報文中發送的相同
用戶名客戶端登陸的用戶名
挑戰認證數據:客戶端用戶密碼使用服務器發送的挑戰隨機數進行加密后,生成挑戰認證數據,返回給服務器用於服務端的認證
服務端認證結果報文(服務端->客戶端)
這個就比較好理解了,服務端主要驗證,用戶名,密碼是否正確存在,如果都是正確的,就返回ok報文,錯誤的話就是ERROR報文
好了,我相信大家應該都有一定的了解了,那么現在再想一下那個問題是不是覺得是非常有道理的,之所以服務端先發送一個握手過去,就是提前通知一下客戶端,你要遵循的一些協議
舉個例子:
小明找工作,投了一個簡歷給某個公司
這時候,某公司就主動打電話了,告訴他,我們需要筆試,筆試的時間,網址,以及一些別的相關信息,規則等
小明接收到這個消息之后,到了那個時間他就會請求那個網址,並將自己的信息告訴他
這時候公司驗證你的信息,驗證成功后,你就可以開始筆試了
大家可以類比這理解一下,就能體會出,這是非常有必要的。
這一章先講這么多,下一張我們講一下,挑戰隨機數和全能標志位的原理,有興趣的同學可以關注一下,有問題也可以隨時交流。
Payload
Type | Name | Description |
---|---|---|
int<3> | payload_length |
Length of the payload. The number of bytes in the packet beyond the initial 4 bytes that make up the packet header. |
int<1> | sequence_id |
Sequence ID |
string<var> | payload |
[len=payload_length] payload of the packet |
版本2:
Mysql JDBC的通信協議(報文的格式和基本類型)
mysql client和server端之間的的數據根據不同的協議規則的進行組織發送。每包數據在發送的時候都要添加上協議頭。
mysql源碼采用5.7.10版本:
協議頭:
每個協議頭共4個字節
包數據長度:
前三個字節表示數據部分的長度(不包括協議頭),三字節能表示的最大長度是16M-1(2^24 - 1),如果要發送的數據部分大於這個長度,要進行拆包,每16M-1個長度為一包。接收端在接受數據的時候,如果檢測到包的長度是16M-1,說明后續還有數據部分,直到接收到<16M-1長度的數據包結束。這意味着最后一包的數據長度可能為0.
序號:
1個字節,從0開始遞增。當發送一個新的sql、數據庫重連,該值清0(函數sql/Net_serv.cc : net_clear).
數據類型:
除了固定長度的整型或者字符串之外,還有其他幾種類型的數據。(固定長度字段數據的存取:include/Mybyte_order.h : 存值 int*store 取值:int*korr 多字節的處理按照小端優先的方式)
1. 可變長度的整數
對該類數據的存取在函數:sql-common/Pack.c: 存整數: net_store_length 讀整數:net_field_length
如果數值<251,直接用一個字節存儲這個值。
如果251<=數值<2^16, 采用3個字節存儲,第一個字節是252, 另外2個字節存儲整數內容
如果2^16<=數值<2^24,采用4字節存儲,第一個字節是252,另外3個字節存儲整數內容
如果2^24<=數值<2^64,采用9字節存儲,第一個字節255,另外8字節存儲整數內容
如果第一個字節為251,表示該整數字段為null
如果第一個字節為255,表示該字節是ERR包的第一個字節
2. 可編碼長度的字符串
字符串的長度采用可變長度的整數進行編碼。
數據長度不固定,長度值由數據前的1-9個字節決定,其中長度值所占的字節數不定,字節數由第1個字節決定,如下表:
第一個字節值 | 后續字節數 | 長度值說明 |
---|---|---|
0-250 | 0 | 第一個字節值即為數據的真實長度 |
251 | 0 | 空數據,數據的真實長度為零 |
252 | 2 | 后續額外2個字節標識了數據的真實長度 |
253 | 3 | 后續額外3個字節標識了數據的真實長度 |
254 | 8 | 后續額外8個字節標識了數據的真實長度 |
3. null結尾的字符串
服務器響應包:
服務器響應包分為4類: OK包 ERR包 EOF包 數據包
OK包:在5.7.5之前,ok包首字節為0,;在5.7.5之后,ok包的首字節可能為0xFE,表示EOF。
該包包括成功執行后影響的行數,最新的自增id, 告警信息(4.1版本之上),服務器狀態信息:status_flag(該字段要留意,后續后講到)
執行函數:sql/protocol_classic.cc : net_send_ok
由於協議的內容容易變更,建議查看官網的最新版格式:https://dev.mysql.com/doc/internals/en/packet-OK_Packet.html
ERR包:首字節是255,報錯錯誤碼和錯誤的描述信息。4.1版本之上包括錯誤狀態。
執行函數:sql/protocol_classic.cc : net_send_error_packet
具體包格式:https://dev.mysql.com/doc/internals/en/packet-ERR_Packet.html
EOF包:首字節254,包括服務器狀態和告警數量(4.1版本之上)
執行函數:sql/protocol_classic.cc : net_send_eof
具體包格式:https://dev.mysql.com/doc/internals/en/packet-EOF_Packet.html
數據包:和具體的協議類型有關,后續講解