mysql协议解析_MySQL协议分析
MySQL协议分析,主要参考MySQL Forge上的 wiki
和源码.协议的全图见 这⾥
, 给同事分享的ppt见 这⾥
,下载见 这⾥
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
2 c —-1—->s 1 auth
2c
开始两⽅都为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/: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
2 c
解包的细节
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发出给对端->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;
resultset 遍历3059 DBUG_RETURN(res->current_row=tmp);
mysql_free_result是把result->data指定的⾏数据释放掉.