MYSQL CLENT/SERVER資料包傳輸及net packet buffer作用解析
原創:轉載請說明出處
水平有限再加上原始碼的複雜性,難免出現錯誤,請共同研究予以糾正
本文參考原始碼:
Net_serv.cc(主要參考)
Mysql.h.pp
Mysql_socket.h
Violite.h
Viosocket.c
Vio.c
參考書籍:
深入理解MYSQL核心技術
MYSQL核心內幕
internals-en
MYSQL官方手冊
LINUX系統程式設計手冊
注意:
1、本文將主要解析非壓縮MYSQL NET包,而儘量不考慮壓縮的MYSQL NET包來減小難度
2、本文主要以TCP->IP->乙太網為藍本進行描述,不考慮其他協議如(UDP)
3、本文主要以Net_serv.cc的my_net_write來描述寫入socket階段,而沒有考慮net_write_command
實際上net_write_command函式是client傳遞命令包的主要函式入口,呼叫的下層函式一致
4、寫入階段可以達到net buffer滿寫入也可以呼叫net_flush()寫入,但是這裡無力研究net_flush()只研究滿寫入的情況
一、基本概念
在這之前我們必須明白如下的一些基本概念,否則不利於閱讀
1、socket:是一種程式間通訊的方式,可以用於多型計算機和本地兩個程式進行通訊,類似管道是雙向
通訊的一種方式,在網路上主要通過繫結IP和埠和識別唯一的網路服務端,在本地通過綁
定一個本地檔案進行通訊,它工作在LINUX 核心態。
2、通訊協議:協議也就是客戶端和服務端事先商量好的一種格式,如果格式不對則解包失敗,比如TCP
協議格式如下,MYSQL有自己的通訊協議。
3、MYSQL協議:MYSQL作為大型資料庫系統,他有著自己的協議,至少包含如下一些資料包。
1、握手階段
--服務端到客戶端 初始化握手包
--客戶端到服務端 客戶端認證包
--服務端到客戶端 OK包、ERROR包
2、連線建立階段
--客戶端到服務端 命令(command)包
--服務端到客戶端 OK包、ERROR包、結果集包
其中結果集包包含:
1、包頭包
2、FILED屬性包
3、EOF包
4、行資料包
FILED屬性包:為列屬性每個列都會有一個
行資料包:為返回資料每行一個包
如果一個SELECT 返回 2行3列資料
會包含3(列)+2(行)+1(包頭包)+2(EOF包)個資料包
由於MYSQL資料包的複雜性本文並不準備解析MYSQL協議各種包,可以參考:
MYSQL核心內幕
internals-en
下圖是展示了MYSQL 服務端和客戶端之間如何握手成功,並且進行資料傳輸
我們約定它叫做MYSQL資料包
4、MYSQL NET包:它是實際的傳輸包的大小,大小最大為16M-1,這是在原始碼中定義死了的,每個MYSQL NET包
包含一個包頭,至少包含4個位元組(非壓縮包,如果壓縮包會多3個位元組),如下:
3 bytes:(壓縮後)payload長度
1 bytes:序號
(壓縮)3 bytes:壓縮前payload長度
其中payload就是實際資料
比如這樣一個MYSQL NET包:
為什麼有一個序號呢?因為為了保證每個命令傳送的包是有序的,比如一個結果
集合包會包含多個包,而其中的行資料包(SERVER->CLIENT的時候每一行資料是一個MYSQL資料包)
包很可能大於16M-1,那麼我們就需要將整個結果集包分為多個MYSQL NET包進行傳輸,當到達
client的時候保證他順序。當然並不是每個MYSQL NET包都很大,比如一些MYSQL資料包如OK包,就很
小,我們知道在乙太網傳輸的最大幀為MTU 1500位元組,那麼可能出現一個乙太網幀包含多個MYSQL NET
包(如OK包),也可能一個MYSQL NET包在多個乙太網幀中,同時可能出現一個MYSQL資料包在多個MYSQL
NET包中,但是最小一個MYSQL NET包至少包含一個MYSQL資料包(如OK包),當然TCP
注意當一個MYSQL資料包分為多個MYSQL NET包的時候其最後會緊跟一個長度為0作為結束的標識,原始碼中
/* End of big multi-packet. */
if (!pkt_len)
goto end;
我們約定它叫做MYSQL NET包
5、NET結構體說明
下面先來看幾個截圖說明:
可以看到NET結構中封裝了一個BUFFER,而這個BUFFER正是由引數net-buffer-length控制
其大小不能超過引數max-allowed-packet大小的這個buffer,本文約定將它叫做net buffer。
net-buffer-length 預設16K最大為1M
max-allowed-packet 預設4M最大1G
結構體還封裝了2個unsigned int的變數write_timeout,read_timeout. 他們正是
net-wirte-timeout,net-read-timeout引數指定,用來表示在返回一個ETIMEDOUT錯誤前能夠
KEEPLIVE最大的時間。
設定超時的底層呼叫很有可能是
ret= mysql_socket_setsockopt(vio->mysql_socket, SOL_SOCKET, optname,optval, sizeof(timeout));
之類的呼叫
另外結構體還封裝了retry_count這是在遇到EINTR錯誤的時候重試的次數由引數net-retry-count
控制,在後面將會講述
6、LINUX ETIMEDOUT、EINTR、EWOULDBLOCK、EAGAIN
#define ETIMEDOUT 110 /* Connection timed out */
#define EINTR 4 /* Interrupted system call */
#define EAGAIN 11 /* Try again */
#define EWOULDBLOCK EAGAIN /* Operation would block *
7、LINUX平臺下MYSQL讀取和寫入scoket函式
位於Mysql_socket.h中
send(mysql_socket.fd, buf, IF_WIN((int),) n, flags);
recv(mysql_socket.fd, buf, IF_WIN((int),) n, flags);
當然如果是WIN_32平臺send和recv函式有底層封裝
8、包封裝流程
如下圖:
本文研究是應用層MYSQL通過自己的協議進行資料包封裝後如何進行傳輸的
二、MYSQL資料包的寫入scoket階段
1、將可能大的MYSQL資料包進行拆分
函式原型
my_bool my_net_write(NET *net, const uchar *packet, size_t len)
net:NET結構體指標
packet:MYSQL資料包指標,MYSQL資料包由MYSQL協議棧準備好
len:MYSQL資料包長度
這個過程會將大的MYSQL資料包進行拆分打包為多個MYSQL NET包,如果是小的MYSQL資料包(如OK包)就進行
打包為MYSQL NET包呼叫net_write_buff下面我將我寫的中文註釋加上原始碼部分一同放出如下:
2、寫入快取階段
函式原型
static my_bool net_write_buff(NET *net, const uchar *packet, ulong len)
net:NET結構體指標
packet:MYSQL資料包指標,注意這個指標和上面不同,由於my_net_write分包後這個指標
也會每次相應的增加到上次寫入後的位置
len:如果是拆分的大包就是16M-1,如果是小包(如OK包)就是其相應的長度,還可能是MYSQL NET包頭長度
這個過程分為如下情況:
--如果MYSQL NET包大於net buffer的剩餘空間
--將MYSQL NET包一部分呼叫memcpy寫入到剩餘空間,完成後呼叫net_write_packet來進行一次傳輸,清空net buffer
--如果MYSQL NET包的剩餘部分任然大於net buffer(net-buffer-length)則直接呼叫net_write_packet進行傳輸
--如果MYSQL NET包能夠儲存在net buffer中
--直接呼叫memcpy寫入到net buffer即可
這裡有幾個重點
one、MYSQL這樣處理實際上講大的MYSQL NET包和小的MYSQL NET進行區分開,使用net buffer來減小傳輸的次數,提高
效能
two、這裡也揭示了寫入階段不會出現超過net buffer大小的情況,這和read不同,在寫入階段net buffer只是一個提高
效能的快取,如果大於他可以直接呼叫net_write_packet寫入,而read階段不同net buffer還承載了另外一個重要
的功能將多個MYSQL NET包合併為一個MYSQL 資料包的功能,所以net buffer的大小小於一個MYSQL資料包的大小會
直接導致報錯如:Got a packet bigger than 'max_allowed_packet' bytes
three、net buffer的設定也就是net-buffer-length引數設定會直接影響到這裡,同時這裡並不會進行擴充到max_allowed_packet
的操作,擴充到max_allowed_packet是在read 階段才會出現,後面會描述
下面我將我寫的中文註釋加上原始碼部分一同放出如下:
3、進行壓縮階段
函式原型
my_bool net_write_packet(NET *net, const uchar *packet, size_t length)
return TRUE on error, FALSE on success.
net:NET結構體指標
packet:這裡的packet有2個可能的來源
--來自net buffer
--原始的MYSQL 資料包指標偏移後的位置如16M-1的大MYSQL NET包
lenth:寫入長度
這一步實際上是進行一個壓縮功能,並沒有進行真正的傳輸,所以我們不進行過多的討論
下面我將我寫的中文註釋加上原始碼部分一同放出如下
4、呼叫vio虛擬I/O介面進行寫入階段
函式原型
static my_bool net_write_raw_loop(NET *net, const uchar *buf, size_t count)
net:NET結構體指標
packet:這裡的buffer有3個可能的來源
--來自net buffer
--原始的MYSQL 資料包指標偏移後的位置如16M-1的大MYSQL NET包
--經過壓縮後的上面兩種包
lenth:寫入長度
這個函式呼叫真正的底層vio_write虛擬IO介面函式進行寫入,同時如果遇到EINTR錯誤會進行如下的操作:
--執行緒安全客戶端如果是EINTR總是重試
--非執行緒安全客戶端或者伺服器端如果是EINTR並且達到net->retry_count就跳出迴圈
服務端MYSQLD肯定是執行緒安全的但是為了服務端的效能不可能在EINTR錯誤下面無限重試
非執行緒安全的客戶端可能全域性區資料已經混亂造成I/O錯誤
此外如果資料沒有傳送完成或者剩餘了一部分會根據錯誤碼判斷拋錯
--ETIMEOUT錯誤,如果是則報錯Got timeout writing communication packets
--否則Got an error writing communication packets
注意這裡的ETIMEOUT就是根據引數net-wirte-timeout設定的SOCKET超時設定
下面我將我寫的中文註釋加上原始碼部分一同放出如下
到這裡MYSQL層次對MYSQL資料包到MYSQL NET包的轉換和傳輸準備已經完成接下來就是通過
底層TCP/IP、乙太網等協議進行封裝然後通過socket傳輸了。下面一張圖對上面的說明
進行一個彙總,但是圖中有些細節並沒有表示出來還是最好通過原始碼備註瞭解
三、MYSQL資料包的讀取scoket階段
1、合併多個MYSQL NET包為一個MYSQL 資料包
函式原型
ulong my_net_read(NET *net)
net:NET結構體指標,一個MYSQL 資料包儲存在一個NET結構體的buffer所指向的記憶體
空間中
返回值為讀取到的實際一個MYSQL 資料包的長度,不包MYSQL NET包的包頭位元組數
這個函式呼叫net_read_packet來讀取一個MYSQL 資料包,如果為大的MYSQL 資料包完成解壓
合併操作原始碼註釋中將大的MYSQL 資料包分為多個MYSQL NET包叫做packet of a multi-packet
下面我將我寫的中文註釋加上原始碼部分一同放出如下,注意我忽略瞭解壓操作來降低學習的難度
2、獲得MYSQL NET包長度階段
函式原型
static ulong net_read_packet(NET *net, size_t *complen)
net:NET結構體指標,一個MYSQL 資料包儲存在一個NET結構體的buffer所指向的記憶體
空間中
complen:為輸出形參,輸出的是可能的壓縮前的資料長度
返回值為實際讀取的MYSQL NET包的長度大小( Read the packet data (payload))
失敗返回packet_error
本函式主要是為了獲得MYSQL NET包的長度而封裝的一層函式,net_read_packet_header為獲得MYSQL
NET包長度函式,並且本函式計算多個MYSQL NET包為一個MYSQL 資料包後需要的記憶體空間是否夠用
如果不夠用分為如下操作
1、如果擴充後NET BUFFER的大小不會超過引數max_packet_size設定的大小,則呼叫net_realloc()擴充成功
2、如果擴充後NET BUFFER的大小超過引數max_packet_size設定的大小,則呼叫net_realloc擴充失敗報錯
{ "ER_NET_PACKET_TOO_LARGE", 1153, "Got a packet bigger than \'max_allowed_packet\' bytes" }
這也是非常常見的一個錯誤
當然如果記憶體不足都會引起如下錯誤
{ "ER_OUT_OF_RESOURCES", 1041, "Out of memory; check if mysqld or some other process uses all
available memory; if not, you may have to use \'ulimit\' to allow mysqld to use more memory
or you can add more swap space" }
這裡不對net_realloc函式和net_read_packet_header函式進行詳細分析,如果有興趣自行研究
下面我將我寫的中文註釋加上原始碼部分一同放出如下
3、呼叫vio虛擬I/O介面進行讀取階段
函式原型
static my_bool net_read_raw_loop(NET *net, size_t count)
net:NET結構體指標,一個MYSQL 資料包儲存在一個NET結構體的buffer所指向的記憶體
空間中
count:本次讀取的MYSQL NET包有多大,如果是壓縮過的MYSQL NET包不是壓縮前的資料而是壓縮後的MYSQL NET包長度
(@return TRUE on error, FALSE on success.)
成功返回FALSE、失敗返回TURE
這個函式和前面寫階段的時候差不多,呼叫底層vio虛擬IO介面進行實際的讀取
也會出現如果資料沒有傳送完成或者剩餘了一部分會根據錯誤碼判斷拋錯
--ETIMEOUT錯誤,如果是則報錯Got timeout reading communication packets
--否則Got an error reading communication packets
注意這裡的ETIMEOUT就是根據引數net-read-timeout設定的SOCKET超時設定
到這裡MYSQL層次對從讀取到MYSQL NET包到MYSQL資料包的轉換合併過程就完成了,讀取工作
成接下來就是通過底層TCP/IP、乙太網等協議進行封裝然後通過socket讀取了。下面一張圖對上面的說明
進行一個彙總,但是圖中有些細節並沒有表示出來還是最好通過原始碼備註瞭解
四、使用TCPDUMP抓取MYSQL NET包解析例項
雖然本文不解析MYSQL 協議但是通過tcpdump抓包來進行一下簡單的客戶端和服務端的連線建立後的互動情況
使用命令
tcpdump tcp port 3307 -X >log.log
1、select 模型
客戶端:select * from test.test;(命令包)
服務端:返回查詢結果(結果集包)
id1 id2
1 1
2 2
3 3
4 4
5 5
6 6
7 7
1311 12:48:29.632228 IP bogon.61796 > testmy.opsession-prxy: Flags [P.], seq 53:82, ack 19791, win 16142, length 29
1312 0x0000: 4500 0045 0dbe 4000 4006 2f45 c0a8 be01 E..E..@.@./E....
1313 0x0010: c0a8 be5d f164 0ceb 097a 4b52 7e10 8b88 ...].d...zKR~...
1314 0x0020: 5018 3f0e 9de8 0000 1900 0000 0373 656c P.?..........sel
1315 0x0030: 6563 7420 2a20 6672 6f6d 2074 6573 742e ect.*.from.test.
1316 0x0040: 7465 7374 3b test;
1317 12:48:29.632651 IP testmy.opsession-prxy > bogon.61796: Flags [P.], seq 19791:19956, ack 82, win 131, length 165
1318 0x0000: 4500 00cd f754 4000 4006 4526 c0a8 be5d E....T@.@.E&...]
1319 0x0010: c0a8 be01 0ceb f164 7e10 8b88 097a 4b6f .......d~....zKo
1320 0x0020: 5018 0083 fe6f 0000 0100 0001 0226 0000 P....o.......&..
1321 0x0030: 0203 6465 6604 7465 7374 0474 6573 7404 ..def.test.test.
1322 0x0040: 7465 7374 0269 6402 6964 0c3f 000b 0000 test.id.id.?....
1323 0x0050: 0003 0350 0000 0028 0000 0303 6465 6604 ...P...(....def.
1324 0x0060: 7465 7374 0474 6573 7404 7465 7374 0369 test.test.test.i
1325 0x0070: 6432 0369 6432 0c3f 000b 0000 0003 0000 d2.id2.?........
1326 0x0080: 0000 0005 0000 04fe 0000 2200 0400 0005 ..........".....
1327 0x0090: 0131 0131 0400 0006 0132 0132 0400 0007 .1.1.....2.2....
1328 0x00a0: 0133 0133 0400 0008 0134 0134 0400 0009 .3.3.....4.4....
1329 0x00b0: 0135 0135 0400 000a 0136 0136 0400 000b .5.5.....6.6....
1330 0x00c0: 0137 0137 0500 000c fe00 0022 00 .7.7.......".
客戶端:
IP bogon.61796 > testmy.opsession-prxy 客戶端埠61796埠傳送給3307埠的TCP包
1314 0x0020: 1900 0000 0373 656c .sel
1315 0x0030: 6563 7420 2a20 6672 6f6d 2074 6573 742e ect.*.from.test.
1316 0x0040: 7465 7374 3b test;
我們只看這一段
1900 00 MYSQL NET包長度小端顯示為0X19=25 當然數一數後面從0373開始一共25個位元組
00 MYSQL NET序號
03 MYSQL 協議命令包的第一個自己0X03代表是COM_QUERY指令
後面的沒什麼說的攜帶就是select * from test.test;的strings模式
服務端:
IP testmy.opsession-prxy > bogon.61796 服務端端埠3307埠傳送給客戶端埠61796的TCP包
服務端我們將列屬性包的分析留給讀者這裡從列屬性過後的EOF包開始
05 0000:小端顯示為0X05=05
04:這個MYSQL NET包在整個結果集包中的SEQ
fe:總是0XFE
0000:警告數量
2200:狀態標示
0400 00:小端顯示為0X05=04MYSQL NET長度
05:這個MYSQL NET包在整個結果集包中的SEQ
0131:01為返回結果集第一個列資料的長度 31就是實際資料1
0131:02為返回結果集第一個列資料的長度 31就是實際資料1
以此類推可以看到全部的結果,這裡也展示這樣一個事實、因為一行記錄為一個結果集行包,那麼
當這行資料很長而導致超過客戶端(如MYSQL MYSQLDUMP)max_packet_size大小的時候會報錯,這點
在原始碼分析中已經分析這點也要非常注意
2、insert模型
客戶端:insert into test.test values(100,100),(101,102),(103,103),(104,104),
(105,105),(106,107),(108,109),(111,123);(命令包)
服務端:返回受影響的行數等(OK包)
[SQL] insert into test.test values(100,100),(101,102),(103,103),(104,104),
(105,105),(106,107),(108,109),(111,123);
受影響的行: 8
時間: 0.027s
13:07:39.121552 IP bogon.61796 > testmy.opsession-prxy: Flags [P.], seq 220:335, ack 39809, win 16140, length 115
0x0000: 4500 009b 100f 4000 4006 2c9e c0a8 be01 E.....@.@.,.....
0x0010: c0a8 be5d f164 0ceb 097a 4de5 7e11 54c6 ...].d...zM.~.T.
0x0020: 5018 3f0c 9890 0000 6f00 0000 0369 6e73 P.?.....o....ins
0x0030: 6572 7420 696e 746f 2074 6573 742e 7465 ert.into.test.te
0x0040: 7374 2076 616c 7565 7328 3130 302c 3130 st.values(100,10
0x0050: 3029 2c28 3130 312c 3130 3229 2c28 3130 0),(101,102),(10
0x0060: 332c 3130 3329 2c28 3130 342c 3130 3429 3,103),(104,104)
0x0070: 2c0d 0a28 3130 352c 3130 3529 2c28 3130 ,..(105,105),(10
0x0080: 362c 3130 3729 2c28 3130 382c 3130 3929 6,107),(108,109)
0x0090: 2c28 3131 312c 3132 3329 3b ,(111,123);
13:07:39.147808 IP testmy.opsession-prxy > bogon.61796: Flags [P.], seq 39809:39859, ack 335, win 140, length 50
0x0000: 4500 005a f77f 4000 4006 456e c0a8 be5d E..Z..@.@.En...]
0x0010: c0a8 be01 0ceb f164 7e11 54c6 097a 4e58 .......d~.T..zNX
0x0020: 5018 008c fdfc 0000 2e00 0001 0008 0002 P...............
0x0030: 0000 0026 5265 636f 7264 733a 2038 2020 ...&Records:.8..
0x0040: 4475 706c 6963 6174 6573 3a20 3020 2057 Duplicates:.0..W
0x0050: 6172 6e69 6e67 733a 2030 arnings:.0
客戶端:
bogon.61796 > testmy.opsession-prxy 客戶端埠61796埠傳送給3307埠的TCP包
6f00 0000 0369 6e73從這裡開始
6f00 00:MYSQL NET包長度小端顯示為0X6f=111 當然數一數後面從0369開始一共111個位元組
00:MYSQL NET序號
03:MYSQL 協議命令包的第一個自己0X03代表是COM_QUERY指令 這裡query不止代表SELECT
代表了INSERT\UPDATE\DELETE\SELECT
後面沒什麼說的就是insert into test.test values(100,100),(101,102),(103,103),(104,104),
(105,105),(106,107),(108,109),(111,123);
的strings acsii編碼
這裡也展示了這樣一個事實如果客戶端source匯入語句的時候那麼每一個INSERT語句是一個指令包
如果這個指令包大於了服務端max_packet_size的大小就會報錯、或者其他錯誤這點需要非常注意
服務端:
IP testmy.opsession-prxy > bogon.61796 服務端端埠3307埠傳送給客戶端埠61796的TCP包
我們從2e00 0001 0008 0002開始解析
2e00 00 MYSQL NET包長度小端顯示為0X2e=46
01 MYSQL NET序號
00 總為0
08 影響行數
00 插入ID
02 00 伺服器狀態
00 00 警告數量
最後就是訊息的ASCII編碼沒什麼好說的了
五、本文中提到的一些錯誤
1、{ "ER_OUT_OF_RESOURCES", 1041, "Out of memory; check if mysqld or some
other process uses all available memory; if not, you may have to use
\'ulimit\' to allow mysqld to use more memory or you can add more swap space" },
記憶體不足分配記憶體失敗
2、{ "ER_NET_PACKET_TOO_LARGE", 1153, "Got a packet bigger than \'max_allowed_packet\' bytes" }
讀取階段由於max_allowed_packet大小的限制net buffer不能進行擴充,一個MYSQL資料包必須存放到一個
net buffer中。
3、{ "ER_NET_WRITE_INTERRUPTED", 1161, "Got timeout writing communication packets" }
{ "ER_NET_READ_INTERRUPTED", 1159, "Got timeout reading communication packets" }
由於net-wirte-timeout,net-read-timeout引數指定,在返回ETIMEDOUT錯誤前保持連線活躍的
秒數
六、錯誤演示
MYSQLD服務端
MYSQL 客戶端程式官方文件給出的
? max_allowed_packet
The maximum size of the buffer for client/server communication. The default is 16MB, the maximum is
1GB.
? net_buffer_length
The buffer size for TCP/IP and socket communication. (Default value is 16KB.)
原始碼中定義 mysql.cc
{"max_allowed_packet", OPT_MAX_ALLOWED_PACKET,
"The maximum packet length to send to or receive from server.",
&opt_max_allowed_packet, &opt_max_allowed_packet, 0,
GET_ULONG, REQUIRED_ARG, 16 *1024L*1024L, 4096,
(longlong) 2*1024L*1024L*1024L, MALLOC_OVERHEAD, 1024, 0},
{"net_buffer_length", OPT_NET_BUFFER_LENGTH,
"The buffer size for TCP/IP and socket communication.",
&opt_net_buffer_length, &opt_net_buffer_length, 0, GET_ULONG,
REQUIRED_ARG, 16384, 1024, 512*1024*1024L, MALLOC_OVERHEAD, 1024, 0},
MYSQLDUMP 客戶端程式官方文件給出的
? max_allowed_packet
The maximum size of the buffer for client/server communication. The default is 24MB, the maximum is
1GB.
? net_buffer_length
The initial size of the buffer for client/server communication. When creating multiple-row INSERT
statements (as with the --extended-insert or --opt option), mysqldump creates rows up
to net_buffer_length bytes long. If you increase this variable, ensure that the MySQL server
net_buffer_length system variable has a value at least this large.
原始碼中定義mysqldump.c
{"max_allowed_packet", OPT_MAX_ALLOWED_PACKET,
"The maximum packet length to send to or receive from server.",
&opt_max_allowed_packet, &opt_max_allowed_packet, 0,
GET_ULONG, REQUIRED_ARG, 24*1024*1024, 4096,
(longlong) 2L*1024L*1024L*1024L, MALLOC_OVERHEAD, 1024, 0},
{"net_buffer_length", OPT_NET_BUFFER_LENGTH,
"The buffer size for TCP/IP and socket communication.",
&opt_net_buffer_length, &opt_net_buffer_length, 0,
GET_ULONG, REQUIRED_ARG, 1024*1024L-1025, 4096, 16*1024L*1024L,
MALLOC_OVERHEAD-1024, 1024, 0},
我們可以看不到不管是MYSQLDUMP還是MYSQL客戶端程式都有這樣兩個命令列引數,他的功能已經在上面原始碼解析中
進行了說明,在MYSQLDUMP中net_buffer_length還有一個額外注意的地方,也就是當多個結果集行包進入NET BUFFER
後需要進行輸出,這裡看起來他的輸出就是以NET BUFFER為單位的官方文件也有說明,換句話說,當使用multiple-row
INSERT 方式的時候一條語句的長度由他控制,注意在匯入的時候服務端的max_allowed_packet一定要大於這個值,因為
匯入的時候一個INSERT語句就是客戶端到服務端的一個命令包,這個MYSQL服務端讀取這個資料命令包必須儲存在一個
NET BUFFER中
我們來驗證一下MYSQLDUMP的這種說法
/mysqldata/mysql5.7/bin/mysqldump --socket=/mysqldata/mysql5.7/mysqld3307.sock -uroot -pgelc123 --net-buffer-length=4k test testpack2>log10.log
/mysqldata/mysql5.7/bin/mysqldump --socket=/mysqldata/mysql5.7/mysqld3307.sock -uroot -pgelc123 --net-buffer-length=16k test testpack2>log11.log
cat log10.log |grep INSERT |head -n 1 >test10.log
cat log11.log |grep INSERT |head -n 1 >test11.log
[root@testmy ~]# du -hs test1*
4.0K test10.log
16K test11.log
確實如我們期望了一個multiple-row 由於NET BUFFER的變動而改變了大小。
接下來我們來模擬這種服務端和客戶端一個mysql資料包大於max_allowed_packet報錯的情況
1、服務端報錯
使用
/mysqldata/mysql5.7/bin/mysqldump --socket=/mysqldata/mysql5.7/mysqld3307.sock -uroot -pgelc123 --net-buffer-length=5m test testpack2>log11.log
這樣會生成一個大約5m的命令包
然後在服務端進行source
預設的服務端max_allowed_packet為4M,net-buffer-length 為16k
mysql> show variables like '%max_allowed_packet%';
+--------------------------+------------+
| Variable_name | Value |
+--------------------------+------------+
| max_allowed_packet | 4194304 |
| slave_max_allowed_packet | 1073741824 |
+--------------------------+------------+
mysql> source /root/test11.log
ERROR 2006 (HY000): MySQL server has gone away
No connection. Trying to reconnect...
Connection id: 63
Current database: test
ERROR 2006 (HY000): MySQL server has gone away
No connection. Trying to reconnect...
Connection id: 64
Current database: test
ERROR 2006 (HY000): MySQL server has gone away
可以看到服務端報錯了
2017-05-07T07:14:15.957486Z 58 [Note] Aborted connection 58 to db: 'test' user: 'root' host: 'localhost' (Got a packet bigger than 'max_allowed_packet' bytes)
2017-05-07T07:14:16.020153Z 63 [Note] Aborted connection 63 to db: 'test' user: 'root' host: 'localhost' (Got a packet bigger than 'max_allowed_packet' bytes)
2017-05-07T07:14:16.080146Z 64 [Note] Aborted connection 64 to db: 'test' user: 'root' host: 'localhost' (Got a packet bigger than 'max_allowed_packet' bytes)
原因在前面已經做了詳細的描述,我們來修改max_allowed_packet為6M
再次source
mysql> show variables like '%max_allowed_packet%';
+--------------------------+------------+
| Variable_name | Value |
+--------------------------+------------+
| max_allowed_packet | 5999616 |
| slave_max_allowed_packet | 1073741824 |
+--------------------------+------------+
mysql> use test
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> source /root/log11.log
Query OK, 349522 rows affected (3.49 sec)
Records: 349522 Duplicates: 0 Warnings: 0
Query OK, 174766 rows affected (1.77 sec)
Records: 174766 Duplicates: 0 Warnings: 0
2、客戶端MYSQL
為了方便測試我構造可一行2M左右資料的行,我們知道一行資料就是一個MYSQL資料包
這裡模擬了另外一個錯誤
mysql> insert into testpack2 values(100,repeat('a',7000000));
ERROR 1301 (HY000): Result of repeat() was larger than max_allowed_packet (5999616) - truncated
報錯明顯大於了我們服務端設定的max_allowed_packet (5999616),而這個命令列包雖然使用了repeat但是repeat
的個數超過了服務端的max_allowed_packet設定,導致報錯.
我們改為2M左右
mysql> insert into testpack2 values(100,repeat('a',2000000));
Query OK, 1 row affected (0.06 sec)
沒有問題
[root@testmy data]# /mysqldata/mysql5.7/bin/mysql --socket=/mysqldata/mysql5.7/mysqld3307.sock -uroot -pgelc123 --max-allowed-packet=1m -e 'select * from test.testpack2 where id=100' >log.log
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 2020 (HY000) at line 1: Got packet bigger than 'max_allowed_packet' bytes
我們看到了預期的報錯
修改--max-allowed-packet為2M
[root@testmy data]# /mysqldata/mysql5.7/bin/mysql --socket=/mysqldata/mysql5.7/mysqld3307.sock -uroot -pgelc123 --max-allowed-packet=2m -e 'select * from test.testpack2 where id=100' >log.log
mysql: [Warning] Using a password on the command line interface can be insecure.
[root@testmy data]#
報錯消失
3、客戶端MYSQLDUMP
沿用上面的資料,這裡出現一樣的結果 不需要過多描述了
[root@testmy data]# /mysqldata/mysql5.7/bin/mysqldump --socket=/mysqldata/mysql5.7/mysqld3307.sock -uroot -pgelc123 --max-allowed-packet=1m test testpack2>log2.log
mysqldump: [Warning] Using a password on the command line interface can be insecure.
Warning: A partial dump from a server that has GTIDs will by default include the GTIDs of all transactions, even those that changed suppressed parts of the database. If you don't want to restore GTIDs, pass --set-gtid-purged=OFF. To make a complete dump, pass --all-databases --triggers --routines --events.
mysqldump: Error 2020: Got packet bigger than 'max_allowed_packet' bytes when dumping table `testpack2` at row: 524288
[root@testmy data]# ^C
[root@testmy data]# /mysqldata/mysql5.7/bin/mysqldump --socket=/mysqldata/mysql5.7/mysqld3307.sock -uroot -pgelc123 --max-allowed-packet=2m test testpack2>log2.log
mysqldump: [Warning] Using a password on the command line interface can be insecure.
Warning: A partial dump from a server that has GTIDs will by default include the GTIDs of all transactions, even those that changed suppressed parts of the database. If you don't want to restore GTIDs, pass --set-gtid-purged=OFF. To make a complete dump, pass --all-databases --triggers --routines --events.
[root@testmy data]#
這3個測試分別用來證明了在讀取階段不管是客戶端還是服務端都需要將一個MYSQL資料包在NET BUFFER中進行合併,如果一個MYSQL資料包大於了--max-allowed-packet設定就會拋錯,而寫入階段當然不需要,原始碼解析的時候已經做了詳細解析。
至此整個文章從預備知識到原始碼解析到抓包解析到錯誤證明都進行了詳細的描述,耗費了我大約2天半的時間基本就是整個週末多一點,因為怕上班時間少有時間研究,所以加緊完成
其中肯定有一些不嚴謹或者錯誤的地方特別是原始碼解析因為沒有過多的資料而且要了解設計者的思想特別困難,還有就是涉及到底層SOCKET通訊的地方,因為沒有過多的去剖析所以
有的地方一筆帶過,如果日後進行詳細的分析會在以文章的方式給出。
作者微信:
水平有限再加上原始碼的複雜性,難免出現錯誤,請共同研究予以糾正
本文參考原始碼:
Net_serv.cc(主要參考)
Mysql.h.pp
Mysql_socket.h
Violite.h
Viosocket.c
Vio.c
參考書籍:
深入理解MYSQL核心技術
MYSQL核心內幕
internals-en
MYSQL官方手冊
LINUX系統程式設計手冊
注意:
1、本文將主要解析非壓縮MYSQL NET包,而儘量不考慮壓縮的MYSQL NET包來減小難度
2、本文主要以TCP->IP->乙太網為藍本進行描述,不考慮其他協議如(UDP)
3、本文主要以Net_serv.cc的my_net_write來描述寫入socket階段,而沒有考慮net_write_command
實際上net_write_command函式是client傳遞命令包的主要函式入口,呼叫的下層函式一致
4、寫入階段可以達到net buffer滿寫入也可以呼叫net_flush()寫入,但是這裡無力研究net_flush()只研究滿寫入的情況
一、基本概念
在這之前我們必須明白如下的一些基本概念,否則不利於閱讀
1、socket:是一種程式間通訊的方式,可以用於多型計算機和本地兩個程式進行通訊,類似管道是雙向
通訊的一種方式,在網路上主要通過繫結IP和埠和識別唯一的網路服務端,在本地通過綁
定一個本地檔案進行通訊,它工作在LINUX 核心態。
2、通訊協議:協議也就是客戶端和服務端事先商量好的一種格式,如果格式不對則解包失敗,比如TCP
協議格式如下,MYSQL有自己的通訊協議。
3、MYSQL協議:MYSQL作為大型資料庫系統,他有著自己的協議,至少包含如下一些資料包。
1、握手階段
--服務端到客戶端 初始化握手包
--客戶端到服務端 客戶端認證包
--服務端到客戶端 OK包、ERROR包
2、連線建立階段
--客戶端到服務端 命令(command)包
--服務端到客戶端 OK包、ERROR包、結果集包
其中結果集包包含:
1、包頭包
2、FILED屬性包
3、EOF包
4、行資料包
FILED屬性包:為列屬性每個列都會有一個
行資料包:為返回資料每行一個包
如果一個SELECT 返回 2行3列資料
會包含3(列)+2(行)+1(包頭包)+2(EOF包)個資料包
由於MYSQL資料包的複雜性本文並不準備解析MYSQL協議各種包,可以參考:
MYSQL核心內幕
internals-en
下圖是展示了MYSQL 服務端和客戶端之間如何握手成功,並且進行資料傳輸
我們約定它叫做MYSQL資料包
4、MYSQL NET包:它是實際的傳輸包的大小,大小最大為16M-1,這是在原始碼中定義死了的,每個MYSQL NET包
包含一個包頭,至少包含4個位元組(非壓縮包,如果壓縮包會多3個位元組),如下:
3 bytes:(壓縮後)payload長度
1 bytes:序號
(壓縮)3 bytes:壓縮前payload長度
其中payload就是實際資料
比如這樣一個MYSQL NET包:
為什麼有一個序號呢?因為為了保證每個命令傳送的包是有序的,比如一個結果
集合包會包含多個包,而其中的行資料包(SERVER->CLIENT的時候每一行資料是一個MYSQL資料包)
包很可能大於16M-1,那麼我們就需要將整個結果集包分為多個MYSQL NET包進行傳輸,當到達
client的時候保證他順序。當然並不是每個MYSQL NET包都很大,比如一些MYSQL資料包如OK包,就很
小,我們知道在乙太網傳輸的最大幀為MTU 1500位元組,那麼可能出現一個乙太網幀包含多個MYSQL NET
包(如OK包),也可能一個MYSQL NET包在多個乙太網幀中,同時可能出現一個MYSQL資料包在多個MYSQL
NET包中,但是最小一個MYSQL NET包至少包含一個MYSQL資料包(如OK包),當然TCP
注意當一個MYSQL資料包分為多個MYSQL NET包的時候其最後會緊跟一個長度為0作為結束的標識,原始碼中
/* End of big multi-packet. */
if (!pkt_len)
goto end;
我們約定它叫做MYSQL NET包
5、NET結構體說明
下面先來看幾個截圖說明:
可以看到NET結構中封裝了一個BUFFER,而這個BUFFER正是由引數net-buffer-length控制
其大小不能超過引數max-allowed-packet大小的這個buffer,本文約定將它叫做net buffer。
net-buffer-length 預設16K最大為1M
max-allowed-packet 預設4M最大1G
結構體還封裝了2個unsigned int的變數write_timeout,read_timeout. 他們正是
net-wirte-timeout,net-read-timeout引數指定,用來表示在返回一個ETIMEDOUT錯誤前能夠
KEEPLIVE最大的時間。
設定超時的底層呼叫很有可能是
ret= mysql_socket_setsockopt(vio->mysql_socket, SOL_SOCKET, optname,optval, sizeof(timeout));
之類的呼叫
另外結構體還封裝了retry_count這是在遇到EINTR錯誤的時候重試的次數由引數net-retry-count
控制,在後面將會講述
6、LINUX ETIMEDOUT、EINTR、EWOULDBLOCK、EAGAIN
#define ETIMEDOUT 110 /* Connection timed out */
#define EINTR 4 /* Interrupted system call */
#define EAGAIN 11 /* Try again */
#define EWOULDBLOCK EAGAIN /* Operation would block *
7、LINUX平臺下MYSQL讀取和寫入scoket函式
位於Mysql_socket.h中
send(mysql_socket.fd, buf, IF_WIN((int),) n, flags);
recv(mysql_socket.fd, buf, IF_WIN((int),) n, flags);
當然如果是WIN_32平臺send和recv函式有底層封裝
8、包封裝流程
如下圖:
本文研究是應用層MYSQL通過自己的協議進行資料包封裝後如何進行傳輸的
二、MYSQL資料包的寫入scoket階段
1、將可能大的MYSQL資料包進行拆分
函式原型
my_bool my_net_write(NET *net, const uchar *packet, size_t len)
net:NET結構體指標
packet:MYSQL資料包指標,MYSQL資料包由MYSQL協議棧準備好
len:MYSQL資料包長度
這個過程會將大的MYSQL資料包進行拆分打包為多個MYSQL NET包,如果是小的MYSQL資料包(如OK包)就進行
打包為MYSQL NET包呼叫net_write_buff下面我將我寫的中文註釋加上原始碼部分一同放出如下:
點選(此處)摺疊或開啟
-
my_bool my_net_write(NET *net, const uchar *packet, size_t len) //將長度為packet的資料寫入到net->buffer
-
{
-
uchar buff[NET_HEADER_SIZE]; // lenth 3 seq 1 4bytes
-
int rc;
-
-
if (unlikely(!net->vio)) /* nowhere to write */
-
return 0;
-
-
MYSQL_NET_WRITE_START(len);
-
-
DBUG_EXECUTE_IF("simulate_net_write_failure", {
-
my_error(ER_NET_ERROR_ON_WRITE, MYF(0));
-
return 1;
-
};
-
);
-
-
/*
-
Big packets are handled by splitting them in packets of MAX_PACKET_LENGTH
-
length. The last packet is always a packet that is < MAX_PACKET_LENGTH.
-
(The last packet may even have a length of 0)
-
*/
-
while (len >= MAX_PACKET_LENGTH) //如果寫入MYSQL 協議包的長度大於了最大mysq NET包 就分為多個MYSQL NET包
-
{
-
const ulong z_size = MAX_PACKET_LENGTH; // 16M-1 計為包的長度
-
int3store(buff, z_size); //將長度寫入到棧 buff中
-
buff[3]= (uchar) net->pkt_nr++; //將buffer中的 seq+1 當然 pkt_nr 序列也+1
-
if (net_write_buff(net, buff, NET_HEADER_SIZE) || //寫入MYSQL NET包頭部
-
net_write_buff(net, packet, z_size)) //將長度為z_size的進行拆分的MYSQL 協議包一分部寫入到net buffer中
-
{
-
MYSQL_NET_WRITE_DONE(1);
-
return 1;
-
}
-
packet += z_size; //將packet的指標 加上z_size的大小 其實也就是16M-1
-
len-= z_size; //當然len 也就相應的減少z_size 其實也就是16M-1
-
}
-
//如果不是大的MYSQL 協議包或者是MYSQL協議包的最後一部分則執行下面程式碼
-
/* Write last packet */
-
int3store(buff,len); //將最後的長度計入buffer 頭3位元組
-
buff[3]= (uchar) net->pkt_nr++; //當然序號繼續+1
-
if (net_write_buff(net, buff, NET_HEADER_SIZE)) //寫入MYSQL NET包頭部
-
{
-
MYSQL_NET_WRITE_DONE(1);
-
return 1;
-
}
-
#ifndef DEBUG_DATA_PACKETS
-
DBUG_DUMP("packet_header", buff, NET_HEADER_SIZE);
-
#endif
-
rc= MY_TEST(net_write_buff(net,packet,len));//寫入 MYSQL 協議包 的最後資料寫入到net buffer中
-
MYSQL_NET_WRITE_DONE(rc);
-
return rc;
- }
函式原型
static my_bool net_write_buff(NET *net, const uchar *packet, ulong len)
net:NET結構體指標
packet:MYSQL資料包指標,注意這個指標和上面不同,由於my_net_write分包後這個指標
也會每次相應的增加到上次寫入後的位置
len:如果是拆分的大包就是16M-1,如果是小包(如OK包)就是其相應的長度,還可能是MYSQL NET包頭長度
這個過程分為如下情況:
--如果MYSQL NET包大於net buffer的剩餘空間
--將MYSQL NET包一部分呼叫memcpy寫入到剩餘空間,完成後呼叫net_write_packet來進行一次傳輸,清空net buffer
--如果MYSQL NET包的剩餘部分任然大於net buffer(net-buffer-length)則直接呼叫net_write_packet進行傳輸
--如果MYSQL NET包能夠儲存在net buffer中
--直接呼叫memcpy寫入到net buffer即可
這裡有幾個重點
one、MYSQL這樣處理實際上講大的MYSQL NET包和小的MYSQL NET進行區分開,使用net buffer來減小傳輸的次數,提高
效能
two、這裡也揭示了寫入階段不會出現超過net buffer大小的情況,這和read不同,在寫入階段net buffer只是一個提高
效能的快取,如果大於他可以直接呼叫net_write_packet寫入,而read階段不同net buffer還承載了另外一個重要
的功能將多個MYSQL NET包合併為一個MYSQL 資料包的功能,所以net buffer的大小小於一個MYSQL資料包的大小會
直接導致報錯如:Got a packet bigger than 'max_allowed_packet' bytes
three、net buffer的設定也就是net-buffer-length引數設定會直接影響到這裡,同時這裡並不會進行擴充到max_allowed_packet
的操作,擴充到max_allowed_packet是在read 階段才會出現,後面會描述
下面我將我寫的中文註釋加上原始碼部分一同放出如下:
點選(此處)摺疊或開啟
-
static my_bool
-
net_write_buff(NET *net, const uchar *packet, ulong len)
-
{
-
ulong left_length;
-
//下面計算buffer->max_packet的剩餘空間
-
if (net->compress && net->max_packet > MAX_PACKET_LENGTH)
-
left_length= (ulong) (MAX_PACKET_LENGTH - (net->write_pos - net->buff));
-
else
-
left_length= (ulong) (net->buff_end - net->write_pos);
-
-
#ifdef DEBUG_DATA_PACKETS
-
DBUG_DUMP("data", packet, len);
-
#endif
-
if (len > left_length) //如果長度大於剩餘空間
-
{
-
if (net->write_pos != net->buff)
-
{
-
/* Fill up already used packet and write it */
-
memcpy(net->write_pos, packet, left_length); //這裡使用指標packet後left_lengeh長度來填滿整個net buffer
-
if (net_write_packet(net, net->buff,
-
(size_t) (net->write_pos - net->buff) + left_length))//寫滿後,然後呼叫net_write_packet寫到scoket
-
//(size_t) (net->write_pos - net->buff) + left_length 為整個buffer長度
-
return 1;
-
net->write_pos= net->buff; //這裡wirte_pos指標 應該也是移動了到了wirte_pos+left_lengeh
-
packet+= left_length; //packet 指標增加
-
len-= left_length; //長度相應減少
-
}
-
if (net->compress)//壓縮屬性先不考慮,實際是壓縮開啟使用Zlib進行壓縮位於Zlib/compress中
-
{
- ..................
-
}
-
if (len > net->max_packet) //如果填滿 net->max_packet 後剩餘的資料 還是大於整個net buffer 大小,則跳過緩衝區直接寫scoket (重要)
-
//實際上這裡len 最大為16M-1, 如果為16M-1的MYSQL NET包始終會使用直接寫入的方法,這點
-
//和read階段不同,read階段會有一個合併mysql net包為MYSQL協議包過程,net buffer有著額外
-
//的使命
-
return net_write_packet(net, packet, len); //直接呼叫net_write_packet寫入
-
/* Send out rest of the blocks as full sized blocks */
-
}
-
memcpy(net->write_pos, packet, len); //如果長度小於 net buffer剩餘的空間,只是寫入net buffer 即可
-
net->write_pos+= len; //這裡wirte_pos指標也移動相應的長度
-
return 0;
- }
函式原型
my_bool net_write_packet(NET *net, const uchar *packet, size_t length)
return TRUE on error, FALSE on success.
net:NET結構體指標
packet:這裡的packet有2個可能的來源
--來自net buffer
--原始的MYSQL 資料包指標偏移後的位置如16M-1的大MYSQL NET包
lenth:寫入長度
這一步實際上是進行一個壓縮功能,並沒有進行真正的傳輸,所以我們不進行過多的討論
下面我將我寫的中文註釋加上原始碼部分一同放出如下
點選(此處)摺疊或開啟
-
my_bool
-
net_write_packet(NET *net, const uchar *packet, size_t length) //函式並沒有真正傳輸只是做了一層封裝將資料壓縮封裝在內
-
//注意這裡的資料可能來自net->buffer 可能來自net_flush
-
{
-
my_bool res;
-
DBUG_ENTER("net_write_packet");
-
-
#if defined(MYSQL_SERVER) && defined(USE_QUERY_CACHE)
-
query_cache_insert((char*) packet, length, net->pkt_nr);
-
#endif
-
-
/* Socket can't be used */
-
if (net->error == 2)
-
DBUG_RETURN(TRUE);
-
-
net->reading_or_writing= 2; //設定標示表示開始寫入
-
-
#ifdef HAVE_COMPRESS //引數是否開啟
-
const bool do_compress= net->compress;
-
if (do_compress) //MYSQL自己決定是否開啟壓縮
-
{
-
if ((packet= compress_packet(net, packet, &length)) == NULL) //壓縮資料 如果記憶體不足報錯
-
//{ "ER_OUT_OF_RESOURCES", 1041, "Out of memory; check if mysqld or some other process uses all available memory; if not, you may have to use \'ulimit\' to allow mysqld to use more memory or you can add more swap space" },
-
//壓縮完成後返回一個malloc的記憶體空間(壓縮後資料的記憶體首地址)給packet,這個時候packet已經不是形參的packet了,需要釋放
-
{
-
net->error= 2;
-
net->last_errno= ER_OUT_OF_RESOURCES;
-
/* In the server, allocation failure raises a error. */
-
net->reading_or_writing= 0;
-
DBUG_RETURN(TRUE);
-
}
-
}
-
#endif /* HAVE_COMPRESS */
-
-
#ifdef DEBUG_DATA_PACKETS
-
DBUG_DUMP("data", packet, length);
-
#endif
-
-
res= net_write_raw_loop(net, packet, length); //進行真正的底層傳輸工作
-
-
#ifdef HAVE_COMPRESS//引數是否開啟
-
if (do_compress)//mysql自己決定
-
my_free((void *) packet);//如前所述這裡需要釋放壓縮後資料的記憶體避免洩露
-
#endif
-
-
net->reading_or_writing= 0;//關閉標示
-
-
DBUG_RETURN(res);
- }
函式原型
static my_bool net_write_raw_loop(NET *net, const uchar *buf, size_t count)
net:NET結構體指標
packet:這裡的buffer有3個可能的來源
--來自net buffer
--原始的MYSQL 資料包指標偏移後的位置如16M-1的大MYSQL NET包
--經過壓縮後的上面兩種包
lenth:寫入長度
這個函式呼叫真正的底層vio_write虛擬IO介面函式進行寫入,同時如果遇到EINTR錯誤會進行如下的操作:
--執行緒安全客戶端如果是EINTR總是重試
--非執行緒安全客戶端或者伺服器端如果是EINTR並且達到net->retry_count就跳出迴圈
服務端MYSQLD肯定是執行緒安全的但是為了服務端的效能不可能在EINTR錯誤下面無限重試
非執行緒安全的客戶端可能全域性區資料已經混亂造成I/O錯誤
此外如果資料沒有傳送完成或者剩餘了一部分會根據錯誤碼判斷拋錯
--ETIMEOUT錯誤,如果是則報錯Got timeout writing communication packets
--否則Got an error writing communication packets
注意這裡的ETIMEOUT就是根據引數net-wirte-timeout設定的SOCKET超時設定
下面我將我寫的中文註釋加上原始碼部分一同放出如下
點選(此處)摺疊或開啟
-
static my_bool
-
net_write_raw_loop(NET *net, const uchar *buf, size_t count)
-
{
-
unsigned int retry_count= 0;
-
-
while (count)
-
{
-
size_t sentcnt= vio_write(net->vio, buf, count);//成功放回寫入位元組數量 失敗返回-1 這裡真正寫max_packet buffer包/mysql NET包>max_packet buffer的資料到socket
-
-
/* VIO_SOCKET_ERROR (-1) indicates an error. */
-
if (sentcnt == VIO_SOCKET_ERROR) //如果寫操作遇到錯誤下面是異常處理 總體來說就是暈倒的是EINTR就做重試,否則直接退出傳送資料迴圈進入異常處理if語句
-
{
-
/* A recoverable I/O error occurred? */
-
if (net_should_retry(net, &retry_count))
-
//1、執行緒安全客戶端如果是EINTR總是重試
-
//2、非執行緒安全客戶端或者伺服器端如果是EINTR並且達到net->retry_count就跳出迴圈
-
//服務端MYSQLD肯定是執行緒安全的但是為了服務端的效能不可能在EINTR錯誤下面無線重試
-
//非執行緒安全的客戶端可能全域性區資料已經混亂造成I/O錯誤
-
continue;
-
else
-
break;
-
}
-
//下面是正常情況下
-
count-= sentcnt; //總數-傳送的
-
buf+= sentcnt; //指標當然也就相應增加
-
update_statistics(thd_increment_bytes_sent(sentcnt));
-
}
-
-
/* On failure, propagate the error code. */
-
if (count) //如果count>0 也就是還有未傳送的資料
-
{
-
/* Socket should be closed. */
-
net->error= 2;
-
-
/* Interrupted by a timeout? */
-
if (vio_was_timeout(net->vio)) //是否為ETIMEOUT錯誤,如果是則報錯Got timeout writing communication packets
-
net->last_errno= ER_NET_WRITE_INTERRUPTED;
-
else //否則報錯Got an error writing communication packets
- net->last_errno= ER_NET_ERROR_ON_WRITE;
-
#ifdef MYSQL_SERVER
-
my_error(net->last_errno, MYF(0));
-
#endif
- }
底層TCP/IP、乙太網等協議進行封裝然後通過socket傳輸了。下面一張圖對上面的說明
進行一個彙總,但是圖中有些細節並沒有表示出來還是最好通過原始碼備註瞭解
三、MYSQL資料包的讀取scoket階段
1、合併多個MYSQL NET包為一個MYSQL 資料包
函式原型
ulong my_net_read(NET *net)
net:NET結構體指標,一個MYSQL 資料包儲存在一個NET結構體的buffer所指向的記憶體
空間中
返回值為讀取到的實際一個MYSQL 資料包的長度,不包MYSQL NET包的包頭位元組數
這個函式呼叫net_read_packet來讀取一個MYSQL 資料包,如果為大的MYSQL 資料包完成解壓
合併操作原始碼註釋中將大的MYSQL 資料包分為多個MYSQL NET包叫做packet of a multi-packet
下面我將我寫的中文註釋加上原始碼部分一同放出如下,注意我忽略瞭解壓操作來降低學習的難度
點選(此處)摺疊或開啟
-
ulong
-
my_net_read(NET *net) //
-
{
-
size_t len, complen;
-
-
MYSQL_NET_READ_START();
-
-
#ifdef HAVE_COMPRESS
-
if (!net->compress)//如果沒有壓縮
-
{
-
#endif
-
len= net_read_packet(net, &complen); //讀取一個MYSQL NET包返回實際長度在len變數中,如果有壓縮
-
//壓縮前長度儲存在complen變數中 這個函式還有一個重要
-
//功能就是擴張max_packet buffer的長度直到max_packet_size
-
//限制,如果不能擴張就報錯,這裡也指出了一個現實每個MYSQL
-
//協議包必須放到一個max_packet buffer中,這也是很多packet
-
//buffer 不夠報錯的根源
-
if (len == MAX_PACKET_LENGTH) //是否為一個滿包及大小為16M-1大小
-
{
-
/* First packet of a multi-packet. Concatenate the packets */
-
ulong save_pos = net->where_b;
-
size_t total_length= 0;
-
do //這裡這個迴圈完成多個mysql net包合併為一個MYSQL 協議包的動作
-
{
-
net->where_b += len; //讀取偏移量不斷增加
-
total_length += len; //總長度不斷增加
-
len= net_read_packet(net, &complen); //讀取動作
-
} while (len == MAX_PACKET_LENGTH);
-
if (len != packet_error) //packet_err被定義為 ~((unsigned long)(0))
-
len+= total_length; //這裡要注意MYSQL協議包分為多個mysql net包後結束包的長度是0,所以也不會增加len
-
net->where_b = save_pos;
-
}
-
net->read_pos = net->buff + net->where_b;
-
if (len != packet_error)
-
net->read_pos[len]=0; /* Safeguard for mysql_use_result */
-
MYSQL_NET_READ_DONE(0, len);
-
return len; //返回讀取的總長度
-
#ifdef HAVE_COMPRESS
-
}
-
else //不考慮壓縮
- {.....
2、獲得MYSQL NET包長度階段
函式原型
static ulong net_read_packet(NET *net, size_t *complen)
net:NET結構體指標,一個MYSQL 資料包儲存在一個NET結構體的buffer所指向的記憶體
空間中
complen:為輸出形參,輸出的是可能的壓縮前的資料長度
返回值為實際讀取的MYSQL NET包的長度大小( Read the packet data (payload))
失敗返回packet_error
本函式主要是為了獲得MYSQL NET包的長度而封裝的一層函式,net_read_packet_header為獲得MYSQL
NET包長度函式,並且本函式計算多個MYSQL NET包為一個MYSQL 資料包後需要的記憶體空間是否夠用
如果不夠用分為如下操作
1、如果擴充後NET BUFFER的大小不會超過引數max_packet_size設定的大小,則呼叫net_realloc()擴充成功
2、如果擴充後NET BUFFER的大小超過引數max_packet_size設定的大小,則呼叫net_realloc擴充失敗報錯
{ "ER_NET_PACKET_TOO_LARGE", 1153, "Got a packet bigger than \'max_allowed_packet\' bytes" }
這也是非常常見的一個錯誤
當然如果記憶體不足都會引起如下錯誤
{ "ER_OUT_OF_RESOURCES", 1041, "Out of memory; check if mysqld or some other process uses all
available memory; if not, you may have to use \'ulimit\' to allow mysqld to use more memory
or you can add more swap space" }
這裡不對net_realloc函式和net_read_packet_header函式進行詳細分析,如果有興趣自行研究
下面我將我寫的中文註釋加上原始碼部分一同放出如下
點選(此處)摺疊或開啟
-
static ulong net_read_packet(NET *net, size_t *complen)
-
{
-
size_t pkt_len, pkt_data_len;
-
-
*complen= 0;
-
-
net->reading_or_writing= 1; //將讀寫標示設定為1,表示讀取開始
-
-
/* Retrieve packet length and number. */
-
if (net_read_packet_header(net)) //讀取一個MYSQL net包的長度和MYSQL NET sequence
-
goto error;
-
-
net->compress_pkt_nr= net->pkt_nr;
-
-
#ifdef HAVE_COMPRESS
-
if (net->compress)//先不考慮壓縮
-
{
- .......
-
}
-
#endif
-
-
/* The length of the packet that follows. */
-
pkt_len= uint3korr(net->buff+net->where_b);//獲得本MYSQL NET包的長度
-
-
/* End of big multi-packet. */
-
if (!pkt_len) //判斷是否為mysql資料包分包後的結束包
-
goto end;
-
-
pkt_data_len = max(pkt_len, *complen) + net->where_b; //獲得讀取此MYSQL NET包後需要的記憶體空間,也就是整個NET BUFFER需要多大,需要判斷如果是
-
//是經過壓縮的需要的空間是資料壓縮前的長度
-
-
/* Expand packet buffer if necessary. */
-
if ((pkt_data_len >= net->max_packet) && net_realloc(net, pkt_data_len)) //這裡實際的判斷net buffer是否夠用,如果不夠用呼叫realloc進行記憶體擴充,
-
//在realloc中判斷是否超過max_packet_size的設定
-
goto error;
-
-
/* Read the packet data (payload). */
-
if (net_read_raw_loop(net, pkt_len)) //開始進行實際的讀取操作
-
goto error;
-
-
end:
-
net->reading_or_writing= 0; //將讀寫標示設定為0,表示讀取結束
-
return pkt_len; //函式返回本次讀取
-
-
error: //出錯返回值
-
net->reading_or_writing= 0;
- return packet_error;
- }
3、呼叫vio虛擬I/O介面進行讀取階段
函式原型
static my_bool net_read_raw_loop(NET *net, size_t count)
net:NET結構體指標,一個MYSQL 資料包儲存在一個NET結構體的buffer所指向的記憶體
空間中
count:本次讀取的MYSQL NET包有多大,如果是壓縮過的MYSQL NET包不是壓縮前的資料而是壓縮後的MYSQL NET包長度
(@return TRUE on error, FALSE on success.)
成功返回FALSE、失敗返回TURE
點選(此處)摺疊或開啟
-
static my_bool net_read_raw_loop(NET *net, size_t count)
-
{
-
bool eof= false;
-
unsigned int retry_count= 0;
-
uchar *buf= net->buff + net->where_b;
-
-
while (count)
-
{
-
size_t recvcnt= vio_read(net->vio, buf, count); //如果寫操作遇到錯誤下面是異常處理 總體來說就是暈倒的是EINTR就做重試,否則直接退出傳送資料迴圈進入異常處理if語句
-
-
/* VIO_SOCKET_ERROR (-1) indicates an error. */
-
if (recvcnt == VIO_SOCKET_ERROR) //
-
{
-
/* A recoverable I/O error occurred? */
-
if (net_should_retry(net, &retry_count))
-
//1、執行緒安全客戶端如果是EINTR總是重試
-
//2、非執行緒安全客戶端或者伺服器端如果是EINTR並且達到net->retry_count就跳出迴圈
-
//服務端MYSQLD肯定是執行緒安全的但是為了服務端的效能不可能在EINTR錯誤下面無線重試
-
//非執行緒安全的客戶端可能全域性區資料已經混亂造成I/O錯誤
-
-
continue;
-
else
-
break;
-
}
-
/* Zero indicates end of file. */
-
else if (!recvcnt) //recv半連線狀態? LINUX man recv:The return values will be 0 when the peer has performed an orderly shutdown
-
{
-
eof= true;
-
break;
-
}
-
-
count-= recvcnt;
-
buf+= recvcnt;
-
update_statistics(thd_increment_bytes_received(recvcnt));
-
}
-
-
/* On failure, propagate the error code. */
-
if (count)//如果count>0 也就是沒有讀取到預期的資料
-
{
-
/* Socket should be closed. */
-
net->error= 2;
-
-
/* Interrupted by a timeout? */
-
if (!eof && vio_was_timeout(net->vio)) //是否為ETIMEOUT錯誤,如果是則報錯Got timeout reading communication packets
-
net->last_errno= ER_NET_READ_INTERRUPTED;
-
else
-
net->last_errno= ER_NET_READ_ERROR;//否則報錯Got an error reading communication packets
-
-
#ifdef MYSQL_SERVER
-
my_error(net->last_errno, MYF(0));
-
#endif
-
}
-
-
return MY_TEST(count);
- }
也會出現如果資料沒有傳送完成或者剩餘了一部分會根據錯誤碼判斷拋錯
--ETIMEOUT錯誤,如果是則報錯Got timeout reading communication packets
--否則Got an error reading communication packets
注意這裡的ETIMEOUT就是根據引數net-read-timeout設定的SOCKET超時設定
到這裡MYSQL層次對從讀取到MYSQL NET包到MYSQL資料包的轉換合併過程就完成了,讀取工作
成接下來就是通過底層TCP/IP、乙太網等協議進行封裝然後通過socket讀取了。下面一張圖對上面的說明
進行一個彙總,但是圖中有些細節並沒有表示出來還是最好通過原始碼備註瞭解
四、使用TCPDUMP抓取MYSQL NET包解析例項
雖然本文不解析MYSQL 協議但是通過tcpdump抓包來進行一下簡單的客戶端和服務端的連線建立後的互動情況
使用命令
tcpdump tcp port 3307 -X >log.log
1、select 模型
客戶端:select * from test.test;(命令包)
服務端:返回查詢結果(結果集包)
id1 id2
1 1
2 2
3 3
4 4
5 5
6 6
7 7
1311 12:48:29.632228 IP bogon.61796 > testmy.opsession-prxy: Flags [P.], seq 53:82, ack 19791, win 16142, length 29
1312 0x0000: 4500 0045 0dbe 4000 4006 2f45 c0a8 be01 E..E..@.@./E....
1313 0x0010: c0a8 be5d f164 0ceb 097a 4b52 7e10 8b88 ...].d...zKR~...
1314 0x0020: 5018 3f0e 9de8 0000 1900 0000 0373 656c P.?..........sel
1315 0x0030: 6563 7420 2a20 6672 6f6d 2074 6573 742e ect.*.from.test.
1316 0x0040: 7465 7374 3b test;
1317 12:48:29.632651 IP testmy.opsession-prxy > bogon.61796: Flags [P.], seq 19791:19956, ack 82, win 131, length 165
1318 0x0000: 4500 00cd f754 4000 4006 4526 c0a8 be5d E....T@.@.E&...]
1319 0x0010: c0a8 be01 0ceb f164 7e10 8b88 097a 4b6f .......d~....zKo
1320 0x0020: 5018 0083 fe6f 0000 0100 0001 0226 0000 P....o.......&..
1321 0x0030: 0203 6465 6604 7465 7374 0474 6573 7404 ..def.test.test.
1322 0x0040: 7465 7374 0269 6402 6964 0c3f 000b 0000 test.id.id.?....
1323 0x0050: 0003 0350 0000 0028 0000 0303 6465 6604 ...P...(....def.
1324 0x0060: 7465 7374 0474 6573 7404 7465 7374 0369 test.test.test.i
1325 0x0070: 6432 0369 6432 0c3f 000b 0000 0003 0000 d2.id2.?........
1326 0x0080: 0000 0005 0000 04fe 0000 2200 0400 0005 ..........".....
1327 0x0090: 0131 0131 0400 0006 0132 0132 0400 0007 .1.1.....2.2....
1328 0x00a0: 0133 0133 0400 0008 0134 0134 0400 0009 .3.3.....4.4....
1329 0x00b0: 0135 0135 0400 000a 0136 0136 0400 000b .5.5.....6.6....
1330 0x00c0: 0137 0137 0500 000c fe00 0022 00 .7.7.......".
客戶端:
IP bogon.61796 > testmy.opsession-prxy 客戶端埠61796埠傳送給3307埠的TCP包
1314 0x0020: 1900 0000 0373 656c .sel
1315 0x0030: 6563 7420 2a20 6672 6f6d 2074 6573 742e ect.*.from.test.
1316 0x0040: 7465 7374 3b test;
我們只看這一段
1900 00 MYSQL NET包長度小端顯示為0X19=25 當然數一數後面從0373開始一共25個位元組
00 MYSQL NET序號
03 MYSQL 協議命令包的第一個自己0X03代表是COM_QUERY指令
後面的沒什麼說的攜帶就是select * from test.test;的strings模式
服務端:
IP testmy.opsession-prxy > bogon.61796 服務端端埠3307埠傳送給客戶端埠61796的TCP包
服務端我們將列屬性包的分析留給讀者這裡從列屬性過後的EOF包開始
05 0000:小端顯示為0X05=05
04:這個MYSQL NET包在整個結果集包中的SEQ
fe:總是0XFE
0000:警告數量
2200:狀態標示
0400 00:小端顯示為0X05=04MYSQL NET長度
05:這個MYSQL NET包在整個結果集包中的SEQ
0131:01為返回結果集第一個列資料的長度 31就是實際資料1
0131:02為返回結果集第一個列資料的長度 31就是實際資料1
以此類推可以看到全部的結果,這裡也展示這樣一個事實、因為一行記錄為一個結果集行包,那麼
當這行資料很長而導致超過客戶端(如MYSQL MYSQLDUMP)max_packet_size大小的時候會報錯,這點
在原始碼分析中已經分析這點也要非常注意
2、insert模型
客戶端:insert into test.test values(100,100),(101,102),(103,103),(104,104),
(105,105),(106,107),(108,109),(111,123);(命令包)
服務端:返回受影響的行數等(OK包)
[SQL] insert into test.test values(100,100),(101,102),(103,103),(104,104),
(105,105),(106,107),(108,109),(111,123);
受影響的行: 8
時間: 0.027s
13:07:39.121552 IP bogon.61796 > testmy.opsession-prxy: Flags [P.], seq 220:335, ack 39809, win 16140, length 115
0x0000: 4500 009b 100f 4000 4006 2c9e c0a8 be01 E.....@.@.,.....
0x0010: c0a8 be5d f164 0ceb 097a 4de5 7e11 54c6 ...].d...zM.~.T.
0x0020: 5018 3f0c 9890 0000 6f00 0000 0369 6e73 P.?.....o....ins
0x0030: 6572 7420 696e 746f 2074 6573 742e 7465 ert.into.test.te
0x0040: 7374 2076 616c 7565 7328 3130 302c 3130 st.values(100,10
0x0050: 3029 2c28 3130 312c 3130 3229 2c28 3130 0),(101,102),(10
0x0060: 332c 3130 3329 2c28 3130 342c 3130 3429 3,103),(104,104)
0x0070: 2c0d 0a28 3130 352c 3130 3529 2c28 3130 ,..(105,105),(10
0x0080: 362c 3130 3729 2c28 3130 382c 3130 3929 6,107),(108,109)
0x0090: 2c28 3131 312c 3132 3329 3b ,(111,123);
13:07:39.147808 IP testmy.opsession-prxy > bogon.61796: Flags [P.], seq 39809:39859, ack 335, win 140, length 50
0x0000: 4500 005a f77f 4000 4006 456e c0a8 be5d E..Z..@.@.En...]
0x0010: c0a8 be01 0ceb f164 7e11 54c6 097a 4e58 .......d~.T..zNX
0x0020: 5018 008c fdfc 0000 2e00 0001 0008 0002 P...............
0x0030: 0000 0026 5265 636f 7264 733a 2038 2020 ...&Records:.8..
0x0040: 4475 706c 6963 6174 6573 3a20 3020 2057 Duplicates:.0..W
0x0050: 6172 6e69 6e67 733a 2030 arnings:.0
客戶端:
bogon.61796 > testmy.opsession-prxy 客戶端埠61796埠傳送給3307埠的TCP包
6f00 0000 0369 6e73從這裡開始
6f00 00:MYSQL NET包長度小端顯示為0X6f=111 當然數一數後面從0369開始一共111個位元組
00:MYSQL NET序號
03:MYSQL 協議命令包的第一個自己0X03代表是COM_QUERY指令 這裡query不止代表SELECT
代表了INSERT\UPDATE\DELETE\SELECT
後面沒什麼說的就是insert into test.test values(100,100),(101,102),(103,103),(104,104),
(105,105),(106,107),(108,109),(111,123);
的strings acsii編碼
這裡也展示了這樣一個事實如果客戶端source匯入語句的時候那麼每一個INSERT語句是一個指令包
如果這個指令包大於了服務端max_packet_size的大小就會報錯、或者其他錯誤這點需要非常注意
服務端:
IP testmy.opsession-prxy > bogon.61796 服務端端埠3307埠傳送給客戶端埠61796的TCP包
我們從2e00 0001 0008 0002開始解析
2e00 00 MYSQL NET包長度小端顯示為0X2e=46
01 MYSQL NET序號
00 總為0
08 影響行數
00 插入ID
02 00 伺服器狀態
00 00 警告數量
最後就是訊息的ASCII編碼沒什麼好說的了
五、本文中提到的一些錯誤
1、{ "ER_OUT_OF_RESOURCES", 1041, "Out of memory; check if mysqld or some
other process uses all available memory; if not, you may have to use
\'ulimit\' to allow mysqld to use more memory or you can add more swap space" },
記憶體不足分配記憶體失敗
2、{ "ER_NET_PACKET_TOO_LARGE", 1153, "Got a packet bigger than \'max_allowed_packet\' bytes" }
讀取階段由於max_allowed_packet大小的限制net buffer不能進行擴充,一個MYSQL資料包必須存放到一個
net buffer中。
3、{ "ER_NET_WRITE_INTERRUPTED", 1161, "Got timeout writing communication packets" }
{ "ER_NET_READ_INTERRUPTED", 1159, "Got timeout reading communication packets" }
由於net-wirte-timeout,net-read-timeout引數指定,在返回ETIMEDOUT錯誤前保持連線活躍的
秒數
六、錯誤演示
MYSQLD服務端
MYSQL 客戶端程式官方文件給出的
? max_allowed_packet
The maximum size of the buffer for client/server communication. The default is 16MB, the maximum is
1GB.
? net_buffer_length
The buffer size for TCP/IP and socket communication. (Default value is 16KB.)
原始碼中定義 mysql.cc
{"max_allowed_packet", OPT_MAX_ALLOWED_PACKET,
"The maximum packet length to send to or receive from server.",
&opt_max_allowed_packet, &opt_max_allowed_packet, 0,
GET_ULONG, REQUIRED_ARG, 16 *1024L*1024L, 4096,
(longlong) 2*1024L*1024L*1024L, MALLOC_OVERHEAD, 1024, 0},
{"net_buffer_length", OPT_NET_BUFFER_LENGTH,
"The buffer size for TCP/IP and socket communication.",
&opt_net_buffer_length, &opt_net_buffer_length, 0, GET_ULONG,
REQUIRED_ARG, 16384, 1024, 512*1024*1024L, MALLOC_OVERHEAD, 1024, 0},
MYSQLDUMP 客戶端程式官方文件給出的
? max_allowed_packet
The maximum size of the buffer for client/server communication. The default is 24MB, the maximum is
1GB.
? net_buffer_length
The initial size of the buffer for client/server communication. When creating multiple-row INSERT
statements (as with the --extended-insert or --opt option), mysqldump creates rows up
to net_buffer_length bytes long. If you increase this variable, ensure that the MySQL server
net_buffer_length system variable has a value at least this large.
原始碼中定義mysqldump.c
{"max_allowed_packet", OPT_MAX_ALLOWED_PACKET,
"The maximum packet length to send to or receive from server.",
&opt_max_allowed_packet, &opt_max_allowed_packet, 0,
GET_ULONG, REQUIRED_ARG, 24*1024*1024, 4096,
(longlong) 2L*1024L*1024L*1024L, MALLOC_OVERHEAD, 1024, 0},
{"net_buffer_length", OPT_NET_BUFFER_LENGTH,
"The buffer size for TCP/IP and socket communication.",
&opt_net_buffer_length, &opt_net_buffer_length, 0,
GET_ULONG, REQUIRED_ARG, 1024*1024L-1025, 4096, 16*1024L*1024L,
MALLOC_OVERHEAD-1024, 1024, 0},
我們可以看不到不管是MYSQLDUMP還是MYSQL客戶端程式都有這樣兩個命令列引數,他的功能已經在上面原始碼解析中
進行了說明,在MYSQLDUMP中net_buffer_length還有一個額外注意的地方,也就是當多個結果集行包進入NET BUFFER
後需要進行輸出,這裡看起來他的輸出就是以NET BUFFER為單位的官方文件也有說明,換句話說,當使用multiple-row
INSERT 方式的時候一條語句的長度由他控制,注意在匯入的時候服務端的max_allowed_packet一定要大於這個值,因為
匯入的時候一個INSERT語句就是客戶端到服務端的一個命令包,這個MYSQL服務端讀取這個資料命令包必須儲存在一個
NET BUFFER中
我們來驗證一下MYSQLDUMP的這種說法
/mysqldata/mysql5.7/bin/mysqldump --socket=/mysqldata/mysql5.7/mysqld3307.sock -uroot -pgelc123 --net-buffer-length=4k test testpack2>log10.log
/mysqldata/mysql5.7/bin/mysqldump --socket=/mysqldata/mysql5.7/mysqld3307.sock -uroot -pgelc123 --net-buffer-length=16k test testpack2>log11.log
cat log10.log |grep INSERT |head -n 1 >test10.log
cat log11.log |grep INSERT |head -n 1 >test11.log
[root@testmy ~]# du -hs test1*
4.0K test10.log
16K test11.log
確實如我們期望了一個multiple-row 由於NET BUFFER的變動而改變了大小。
接下來我們來模擬這種服務端和客戶端一個mysql資料包大於max_allowed_packet報錯的情況
1、服務端報錯
使用
/mysqldata/mysql5.7/bin/mysqldump --socket=/mysqldata/mysql5.7/mysqld3307.sock -uroot -pgelc123 --net-buffer-length=5m test testpack2>log11.log
這樣會生成一個大約5m的命令包
然後在服務端進行source
預設的服務端max_allowed_packet為4M,net-buffer-length 為16k
mysql> show variables like '%max_allowed_packet%';
+--------------------------+------------+
| Variable_name | Value |
+--------------------------+------------+
| max_allowed_packet | 4194304 |
| slave_max_allowed_packet | 1073741824 |
+--------------------------+------------+
mysql> source /root/test11.log
ERROR 2006 (HY000): MySQL server has gone away
No connection. Trying to reconnect...
Connection id: 63
Current database: test
ERROR 2006 (HY000): MySQL server has gone away
No connection. Trying to reconnect...
Connection id: 64
Current database: test
ERROR 2006 (HY000): MySQL server has gone away
可以看到服務端報錯了
2017-05-07T07:14:15.957486Z 58 [Note] Aborted connection 58 to db: 'test' user: 'root' host: 'localhost' (Got a packet bigger than 'max_allowed_packet' bytes)
2017-05-07T07:14:16.020153Z 63 [Note] Aborted connection 63 to db: 'test' user: 'root' host: 'localhost' (Got a packet bigger than 'max_allowed_packet' bytes)
2017-05-07T07:14:16.080146Z 64 [Note] Aborted connection 64 to db: 'test' user: 'root' host: 'localhost' (Got a packet bigger than 'max_allowed_packet' bytes)
原因在前面已經做了詳細的描述,我們來修改max_allowed_packet為6M
再次source
mysql> show variables like '%max_allowed_packet%';
+--------------------------+------------+
| Variable_name | Value |
+--------------------------+------------+
| max_allowed_packet | 5999616 |
| slave_max_allowed_packet | 1073741824 |
+--------------------------+------------+
mysql> use test
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> source /root/log11.log
Query OK, 349522 rows affected (3.49 sec)
Records: 349522 Duplicates: 0 Warnings: 0
Query OK, 174766 rows affected (1.77 sec)
Records: 174766 Duplicates: 0 Warnings: 0
2、客戶端MYSQL
為了方便測試我構造可一行2M左右資料的行,我們知道一行資料就是一個MYSQL資料包
這裡模擬了另外一個錯誤
mysql> insert into testpack2 values(100,repeat('a',7000000));
ERROR 1301 (HY000): Result of repeat() was larger than max_allowed_packet (5999616) - truncated
報錯明顯大於了我們服務端設定的max_allowed_packet (5999616),而這個命令列包雖然使用了repeat但是repeat
的個數超過了服務端的max_allowed_packet設定,導致報錯.
我們改為2M左右
mysql> insert into testpack2 values(100,repeat('a',2000000));
Query OK, 1 row affected (0.06 sec)
沒有問題
[root@testmy data]# /mysqldata/mysql5.7/bin/mysql --socket=/mysqldata/mysql5.7/mysqld3307.sock -uroot -pgelc123 --max-allowed-packet=1m -e 'select * from test.testpack2 where id=100' >log.log
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 2020 (HY000) at line 1: Got packet bigger than 'max_allowed_packet' bytes
我們看到了預期的報錯
修改--max-allowed-packet為2M
[root@testmy data]# /mysqldata/mysql5.7/bin/mysql --socket=/mysqldata/mysql5.7/mysqld3307.sock -uroot -pgelc123 --max-allowed-packet=2m -e 'select * from test.testpack2 where id=100' >log.log
mysql: [Warning] Using a password on the command line interface can be insecure.
[root@testmy data]#
報錯消失
3、客戶端MYSQLDUMP
沿用上面的資料,這裡出現一樣的結果 不需要過多描述了
[root@testmy data]# /mysqldata/mysql5.7/bin/mysqldump --socket=/mysqldata/mysql5.7/mysqld3307.sock -uroot -pgelc123 --max-allowed-packet=1m test testpack2>log2.log
mysqldump: [Warning] Using a password on the command line interface can be insecure.
Warning: A partial dump from a server that has GTIDs will by default include the GTIDs of all transactions, even those that changed suppressed parts of the database. If you don't want to restore GTIDs, pass --set-gtid-purged=OFF. To make a complete dump, pass --all-databases --triggers --routines --events.
mysqldump: Error 2020: Got packet bigger than 'max_allowed_packet' bytes when dumping table `testpack2` at row: 524288
[root@testmy data]# ^C
[root@testmy data]# /mysqldata/mysql5.7/bin/mysqldump --socket=/mysqldata/mysql5.7/mysqld3307.sock -uroot -pgelc123 --max-allowed-packet=2m test testpack2>log2.log
mysqldump: [Warning] Using a password on the command line interface can be insecure.
Warning: A partial dump from a server that has GTIDs will by default include the GTIDs of all transactions, even those that changed suppressed parts of the database. If you don't want to restore GTIDs, pass --set-gtid-purged=OFF. To make a complete dump, pass --all-databases --triggers --routines --events.
[root@testmy data]#
這3個測試分別用來證明了在讀取階段不管是客戶端還是服務端都需要將一個MYSQL資料包在NET BUFFER中進行合併,如果一個MYSQL資料包大於了--max-allowed-packet設定就會拋錯,而寫入階段當然不需要,原始碼解析的時候已經做了詳細解析。
至此整個文章從預備知識到原始碼解析到抓包解析到錯誤證明都進行了詳細的描述,耗費了我大約2天半的時間基本就是整個週末多一點,因為怕上班時間少有時間研究,所以加緊完成
其中肯定有一些不嚴謹或者錯誤的地方特別是原始碼解析因為沒有過多的資料而且要了解設計者的思想特別困難,還有就是涉及到底層SOCKET通訊的地方,因為沒有過多的去剖析所以
有的地方一筆帶過,如果日後進行詳細的分析會在以文章的方式給出。
作者微信:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7728585/viewspace-2138631/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Netty使用Google Protocol Buffer完成伺服器高效能資料傳輸NettyGoProtocol伺服器
- LoRa資料包結構分析及資料傳輸時間的計算
- oracle net manager 資料傳輸安全Oracle
- 聊聊 MySQL 網路緩衝區、net_buffer_length、max_allowed_packet 那些事MySql
- 資料傳輸 | 利用 DTLE 將 MySQL 資料同步到 DBLEMySql
- Lost connection to MySQL server at 'reading authorization packet'MySqlServer
- 網路遊戲資料傳輸:粘包的處理遊戲
- 開源 | MySQL資料傳輸中介軟體—DTLEMySql
- JS閉包作用域解析JS
- netty系列之:基於流的資料傳輸Netty
- 無線模組透明傳輸原理及過程解析
- MySQL Server-id的作用MySqlServer
- Mysql資料庫的max_allowed_packet設定方法MySql資料庫
- 【Netty】Netty傳輸Netty
- iOS資料傳輸工具iOS
- node express buffer excel傳輸到前端下載ExpressExcel前端
- MySQ匯出資料包錯Error 2020: Got packet bigger than 'max_allowed_packet'ErrorGo
- 關於印度跨境資料傳輸,印度放寬了跨境資料傳輸
- MySQL報錯Slave: received end packet from server, apparent master shutdownMySqlServerAPPAST
- 解決MySQL資料匯入報錯Got a packet bigger than‘max_allowed_packet’bytesMySqlGo
- 路由器網路中資料包傳輸分析路由器
- .NET API 介面資料傳輸加密最佳實踐記錄示例API加密
- mysql各個安裝包作用MySql
- 圖解作用域及閉包圖解
- MySQL Galera Cluster全解析 Part 3 狀態快照傳輸(SST)MySql
- nodejs 介面傳輸資料NodeJS
- 特殊條件資料傳輸
- 讓資料傳輸更安全
- openssl與資料傳輸安全
- iPaas資料傳輸的方式
- HDFSDATANODE資料傳輸詳解
- TCP傳輸資料長度TCP
- Mysql連線錯誤:Lost connection to Mysql server at 'waiting for initial communication packet'MySqlServerAI
- 企業大資料傳輸方式,鐳速提供大資料傳輸解決方案大資料
- FAQ系列|net_buffer_length選項對資料備份及恢復影響
- Netty整合SpringBoot並使用Protobuf進行資料傳輸NettySpring Boot
- MySQL預設資料庫的作用MySql資料庫
- 迴圈輸出——閉包、變數作用域變數