MySQL協議分析,主要參考MySQL Forge上的wiki和源碼.協議的全圖見這里, 給同事分享的ppt見這里,下載見這里
MySQL協議分析
View more presentations from ruoyi ruan
packet number
在做proxy的時候在這里迷糊過,翻了幾遍代碼才搞明白,細節如下:
客戶端服務端的net->pkt_nr都從0開始.接受包時比較packet number 和net->pkt_nr是否相等,否則報packet number亂序,連接報錯;相等則pkt_nr自增.發送包時把net->pkt_nr作為packet number發送,然后對net->pkt_nr進行自增保持和對端的同步.
接收包
sql/net_serv.c:my_real_read
898 if (net->buff[net->where_b + 3] != (uchar) net->pkt_nr)
發送包
sql/net_serv.c:my_net_write
392 int3store(buff,len);
393 buff[3]= (uchar) net->pkt_nr++;
我們來幾個具體場景的packet number, net->pkt_nr的變化
連接
0 c ———-> s 0 connect
0 c <—-0——s 1 handshake
2 c —-1—->s 1 auth
2c <—-2——s 0 ok
開始兩方都為0,服務端發送handshake packet(pkt=0)之后自增為1,然后等待對端發送過來pkt=1的包
查詢
每次查詢,服務客戶端都會對net->pkt_nr進行清零
include/mysql_com.h
388 #define net_new_transaction(net) ((net)->pkt_nr=0)
sql/sql_parse.cc:do_command
805 net_new_transaction(net);
sql/client.c:cli_advanced_command
800 net_clear(&mysql->net, (command != COM_QUIT));
開始兩方net->pkt_nr皆為0, 命令發送后客戶端端為1,服務端開始發送分包,分包的pkt_nr的依次遞增,客戶端的net->pkt_nr也隨之增加.
1 c ——0—-> s 0 query
1 c <—-1——s 2 resultset
2 c <—-2——s 3 resultset
解包的細節
my_net_read負責解包,首先讀取4個字節,判斷packet number是否等于net->pkt_nr然后再次讀取packet_number長度的包體。
偽代碼如下:
remain=4
for(i = 0; i < 2; i++) {
//數據是否讀完
while (remain>0) {
length = read(fd, net->buff, remain)
remain = remain - length
}
//第一次
if (i=0) {
remain = uint3korr(net->buff+net->where_b);
}
}
網絡層優化
從ppt里可以看到,一個resultset packet由多個包組成,如果每次讀寫包都導致系統調用那肯定是不合理,常規優化方法:寫大包加預讀
net->buff
每個包發送到網絡或者從網絡讀包都會先把數據包保存在net->buff里,待到net->buff滿了或者一次命令結束才會通過socket發出給對端.net->buff有個初始大小(net->max_packet),會隨讀取數據的增多而擴展.
vio->read_buffer
每次從網絡讀包,并不是按包的大小讀取,而是會盡量讀取2048個字節,這樣一個resultset包的讀取不會再引起多次的系統調用了.header packet讀取完畢后, 接下來的field,eof, row apcket讀取僅僅需要從vio-read_buffer拷貝指定字節的數據即可.
MySQL api說明
api和MySQL客戶端都會使用sql/client.c這個文件,解包的過程都是使用sql/client.c:cli_read_query_result.
mysql_store_result來解析row packet,并把數據存儲到res->data里,此時所有數據都存內存里了.
mysql_fetch_row僅僅是使用內部的游標,遍歷result->data里的數據
3052 if (!res->data_cursor)
3053 {
3054 DBUG_PRINT("info",("end of data"));
3055 DBUG_RETURN(res->current_row=(MYSQL_ROW) NULL);
3056 }
3057 tmp = res->data_cursor->data;
3058 res->data_cursor = res->data_cursor->next;
3059 DBUG_RETURN(res->current_row=tmp);
mysql_free_result是把result->data指定的行數據釋放掉.
原文轉自:http://blogread.cn/it/article/5604