阿里巴巴DBA,原去哪儿网DBA。专注于MySQL源码研究、DBA运维、CGroup虚拟化及Linux Kernel源码研究等。 github:https://github.com/HengWang/ Email:king_wangheng@163.com 微博 :@王恒-Henry QQ :506437736
分类: Mysql/postgreSQL
2013-02-21 22:09:29
目的
MySQL数据结构Protocol及相关处理方法,是MySQL服务器端遵循的协议规范。本文通过解析MySQL服务器端的协议,分析各种协议的格式,以便于更清晰的了解协议规则。
数据结构
Protocol数据结构定义在MySQL的源码sql/protocol.h中,Protocol是class类型,派生了Protocol_text和Protocol_binary两个子类,分别用于处理字符类数据和二进制数据的存储。Protocol类的关系图如下所示:
图1 Protocol类关系
Protocol类中主要的分为三类:基本参数、数据存储、数据发送。其中基本参数主要包括thd是THD数据结构,主要用于存储当前线程session的一些参数;packet是数据包字段;此外还有字段相关的变量,如field_count字段数、field_pos字段位置、field_types字段类型等。数据存储相关处理方法主要是用于处理和存储不同字段类型的数据,这些方法对不同的数据类型,可以在派生类里面进行重写。数据发送主要包括发送执行状态和发送数据等处理方法,其中在数据发送阶段是协议格式的封装过程,并且在发送数据时调用数据存储的相关处理方法,根据数据字段的类型,对数据进行封装发送。
协议格式
协议格式部分主要从发送阶段数据格式的封装角度来诠释MySQL数据传输协议。以下将对所有的数据传输传输格式进行分析,由于客户端协议版本的问题,有些数据格式也有所不同。
error信息格式
error信息是执行状态的一种,发送error信息,根据客户端不同的协议版本,有以下两种格式。
客户端协议4.1的error信息格式:
图2 客户端4.1协议error信息格式
errno是MySQL对应的错误号,2个字节存储;“#”用于将错误号与错误信息分开;sql_state表示sql执行的状态;Error msg表示错误信息,最大512个字节长度。其中“#”和sql_state是客户端4.1协议增加的格式。
客户端之前协议error信息格式:
图3 客户端之前协议error信息格式
客户端之前的协议仅包含两个字段,错误号errno和错误信息Error msg。对应的字段存储结构与4.1协议一致。
Error信息传输格式:
Error信息格式在网络层传输时,通过调用NET处理函数net_write_command()进行网络传输,传输格式如下所示:
图4 error信息传输格式
从传输格式可以看出,前三个字节存储packet的最大长度;1个字节存储下一个packet的编号;1个字节存储255,表示当前信息是错误信息,在数据信息解析过程中,用于判断是否为错误信息;ERROR内容时以上error信息格式封装的内容。特别说明的,255是Error信息与其他信息的最大区别,其他信息在传输时的格式如上类似,但是没有该字段。
OK信息格式
OK信息是SQL执行的成功状态信息,其数据格式根据客户端协议版本,有两种数据格式。
客户端协议4.1的OK信息格式:
图5 客户端4.1协议OK信息格式
从协议格式来看,0是一个标志位,用于表示为OK信息;affect_rows是SQL执行影响的记录行数;id为查询id值;server_status表示SQL执行后,server的状态值;warn_count是SQL执行产生的警告信息数,最大值为65535,大于这个数存储65535;msg表示传输的信息,没有长度限制,如果信息过长,会进行分包传输。其中warn_count是在客户端协议4.1中增加的字段。
客户端协议4.0的OK信息格式:
图6 客户端4.0协议OK信息格式
与协议4.1不同的是,4.0中不包含warn_count值。但是存储了server_status字段,用于记录server在SQL执行结束后的状态。
客户端之前协议OK信息格式:
图7 OK信息格式
对于之前的协议,OK信息格式没有server_status和warn_count字段,仅包含基本的SQL执行信息。
在传输时,传输格式类似于Error传输格式,不同的是,没有单独的255字节标示错误信息。然而在传输的信息中,OK的信息格式的第一个字节标志位,与255的意义是一样的,同样是用于标示数据内容为OK信息。
EOF信息格式
EOF信息是用于表示传输数据结束的信息,根据客户端协议的版本,该信息也有两种格式。对于客户端协议4.1之前的EOF信息格式仅仅是发送信息标示一个字节(254),对于4.1协议,格式如下所示。
客户端协议4.1的EOF信息格式:
图8 客户端4.1协议EOF信息格式
有信息格式可知,254标示EOF信息,在数据传输中,254信息类型为EOF信息;warn_count和server_status字段的含义与OK信息格式中的字段一致,不再赘述。
Metadata信息格式
Metadata数据时数据库的表定义信息,主要是将数据表的各个字段进行封装,进行数据传输。Metadata信息的格式根据客户端协议的版本,也有两种格式。
客户端协议4.1的Metadata信息格式:
Metadata是对表定义的字段进行封装,以下仅将其中一个字段的详细信息格式给出,其他字段省略。
图9 客户端4.1协议Metadata信息格式
由以上格式可知,首先“def”三个字符是字段定义的开始;然后db_name、table_name、org_table_name、col_name和org_col_name分别表示数据库名、表名、原始表名、列名、原始表名;数字12是指还需要12个字节来存储后面的字段信息;charset_num表示字符集编号;length表示字段的长度;type表示字段的数据类型;flags表示字段的标志位;decimals表示数据的精度;之后两个字节存储0,用于以后扩展。完整的一个字段存储完成后,紧接着下一个字段的定义。
客户端之前协议Metadata信息格式:
图10 Metadata信息格式
Metadata信息格式各个字段的含义是:table_name、col_name分别表示表名和字段名;3表示接下来信息占用的字节数,即length需要占用3个字节;1和type含义同上;后面的3表示flags和decimals共占用三个字节。从以上信息格式来看,metadata在之前版本中存储的格式和字段较4.1协议,存在一些不足。首先字段定义之间没有严格的分割界限;其次每个字段的长度占用一个字节去存储占用的字节数,浪费空间;最后信息内容不够全面。而这些问题,在4.1协议中有了较好的改进。
在处理函数send_result_set_metadata()中,根据flags的值,有几种不同的处理结果。其中一个是直接获取字段数,直接传输结果;另外就是发送EOF信息,表明数据传输结束。
ROW信息格式
ROW信息的存储根据不同字段的存储类型,存储方式也不同,以下将根据不同的字段类型,分别给出具体字段的信息格式。
字符类型的NULL存储格式
对于字段的数据为NULL的情况,在ROW数据封装时,仅存储251,占用1个字节。即在封装的数据包中,251表示NULL值。
字符类型存储格式
字符类型主要包括MYSQL_TYPE_NULL、MYSQL_TYPE_DECIMAL、MYSQL_TYPE_ENUM、MYSQL_TYPE_SET、MYSQL_TYPE_TINY_BLOB、MYSQL_TYPE_MEDIUM_BLOB、MYSQL_TYPE_LONG_BLOB、MYSQL_TYPE_BLOB、MYSQL_TYPE_GEOMETRY、MYSQL_TYPE_STRING、MYSQL_TYPE_VAR_STRING、MYSQL_TYPE_VARCHAR、MYSQL_TYPE_BIT、MYSQL_TYPE_NEWDECIMAL,这些数据类型都是以字符的方式存储,特别注意的是文本类型与字符集有紧密的关系。字符类型根据长度,存储格式有多种格式,具体如下所示:
1、数据长度小于251
数据长度小于251时,仅存储数据长度和数据内容。其中数据长度length占用1个字符。存储格式如下所示:
图11 数据长度小于251的字符格式
2、数据长度大于等于251小于65536
数据长度大于等于251并且小于65536时,存储格式包括三个部分:标志位252,表示数据长度范围;数据长度length,占用字节数与字符集有关;数据data内容。存储格式如下所示:
图12 数据长度小于65536的字符格式
3、数据长度大于等于65536小于16M
数据长度大于等于65536并且小于16M时,存储格式同第二种格式类似,仅标注位和数据长度占用的字节数不同。具体存储格式如下所示:
图13 数据长度小于16M的字符格式
数据类型存储格式
数据类型的存储首先调用对应的函数转化为字符类型(如tiny、long、float、decimal等类型),或者封装数据类型为字符类型(如time、datetime等)。最后处理转化为字符类型存储。
二进制数据存储格式
二进制数据格式的存储没有字符集的问题,因此数据类型也不需要转化为字符类型,直接封装数据即可,存储格式同以上格式一致。其中对NULL值的存储格式有较大差异,存储格式根据字段位置计算,存储NULL值的源码如下所示:
bool Protocol_binary::store_null() { uint offset= (field_pos+2)/8+1, bit= (1 << ((field_pos+2) & 7)); /* Room for this as it's allocated in prepare_for_send */ char *to= (char*) packet->ptr()+offset; *to= (char) ((uchar) *to | (uchar) bit); field_pos++; return 0; } |
结论
通过对协议中各种信息的格式进行分析,进一步了解了MySQL服务器端数据封装和传输的协议。更深入的,结合客户端传输内容,分析传输内容,进行中间代理层处理,从而达到控制命令和数据的目的。