代码改变世界

Mysql 报文格式

2020-09-24 18:14  宋海宾  阅读(1957)  评论(0编辑  收藏  举报

我准备从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报文

好了,我相信大家应该都有一定的了解了,那么现在再想一下那个问题是不是觉得是非常有道理的,之所以服务端先发送一个握手过去,就是提前通知一下客户端,你要遵循的一些协议
举个例子:
小明找工作,投了一个简历给某个公司
这时候,某公司就主动打电话了,告诉他,我们需要笔试,笔试的时间,网址,以及一些别的相关信息,规则等

小明接收到这个消息之后,到了那个时间他就会请求那个网址,并将自己的信息告诉他

这时候公司验证你的信息,验证成功后,你就可以开始笔试了

大家可以类比这理解一下,就能体会出,这是非常有必要的。

这一章先讲这么多,下一张我们讲一下,挑战随机数和全能标志位的原理,有兴趣的同学可以关注一下,有问题也可以随时交流。

 
 
mysql报文最大可以容纳16MB的数据,如果一次发送的数据超过16MB会被分割成多个mysql报文。
 
mysql报文的格式如下:

Payload

TypeNameDescription
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
payload_length:用前3个字节表示报文真实数据的长度(不包括最前面的4个字节:前3个字节表明真实数据的长度,第4个字节表明该报文的序列)
sequence_id:用第4个字节表示报文唯一序列号,通常来说是相邻两个报文的序列号是递增的
payload:这部分存放真实的数据内容,字节长度为payload_length指定的长度
 
eg:
 
COM_QUIT命令发送的报文如下所示:
 
01 00 00 00 01 
 
payload_length为1
sequence_id为0
payload内容为01,长度为1(由payload_length指定)

 

版本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

          数据包:和具体的协议类型有关,后续讲解