MySQL通讯协议(1)数据类型

原创
2020/03/31 18:55
阅读数 402

[TOC]

MySQL通讯协议(1)数据类型

对于大部分开发者来说,并不需要了解MySQL客户端和服务端是如何交互的。但是当业务发展到一定阶段,数据量增大时,分库分表就成了不得不考虑的一种优化措施。目前主流的方案主要有两种,一种是本地代理连接、分析重写SQL、路由、执行、合并结果,一种是把这些放到中间件里。而后一种就必须了解MySQL通讯协议,因为这个中间件要作为客户端与MySQL服务交互,还要作为服务端与MySQL客户端交互。

连接MySQL的协议

协议 支持的操作系统
TCP 全部 使用TCP/IP协议连接本地或远程服务器
SOCKET Unix 使用Unix socket连接本地服务器
PIPE Windows 使用Windows named-pipe连接本地服务器
MEMORY Windows 使用Windows shared-memory连接本地服务器

可见后三种方式都受制于各自平台,且只能连接本地服务器,不符合大部分应用场景。所以,我们接触到的都是基于TCP协议的连接方式。

MySQL通讯协议是基于TCP/IP协议的一个应用层协议,所以TCP/IP协议该有的三次握手、滑动窗口、重传机制都有,这里不再赘述。

概述

MySQL通讯协议被设计用于MySQL客户端和MySQL服务端之间通讯,主要用于:

  • 各种语言实现的连接器,如JDBC驱动
  • MySQL代理
  • 主从复制之间的通信

基本数据类型

协议里用到的数据类型,有Integer和String两种

Integer

整数类型,分为两种固定长度和

固定长度整数

占用空间固定的无符号整数,有6种int<1>、int<2>、int<3>、int<4>、int<6>、int<8>,尖括号里的数字,代表占用几个字节。可以简单理解成byte、short、int、long,但是实际表示并不一样。因为网络传输的单位是字节,即8个bit,int<2>以上没法直接传输,必须经过简单的编码。

编码方式就是把要编码的值,从右向左,每次拿一个字节,依次排列。

例如一个int<2>类型的值2020,转成二进制:111 11100100,第一个字节:11100100,第二个字节:00000111,所以编码后的数据就是:11100100 00000111 ,用十六进制表示:0xE4 0x07

用代码表示:

int a = 2020;
byte[] data = new byte[2];
data[0] = (byte)(a & 0xFF)
data[1] = (byte)(a & >>> 8)

解码就是反过来:

int[] data = new int[]{228, 7};
int i = (data[0] & 0xff) | ((data[1] & 0xff) << 8);

编码长度整数

int<lenenc>,根据数据值大小,动态的使用1、3、 4、9个字节保存。类似于UTF-8的编码方式。

  • 如果值 < 251,就用1字节表示。
  • 如果值 ≥ 251 且 < (2^16),就用0xfc + 2字节表示,共3字节。
  • 如果值 ≥ (2^16) 且 < (2^24),就用0xfd + 3字节表示,共4字节。
  • 如果值 ≥ (2^24)且 < (2^64),就用0xfe + 8字节表示,共9字节。

例如,值300,属于第二种情况,转成2字节为:00101100 00000001,前面加上0xfd,最后结果就是:0xfd 0x2C 0x01。

解码就是先取第一位,判断是否<251,如果如就是第一种情况,如果不是,根据第一位是0xfc0xfd0xfe 判断是那种情况,然后读取后面N位即可。

这种编码方式的好处是,在不能提前确定数据大小时,提供一种动态的编码方式,当实际数值较小时,可以不用传输大量的空值(占位的0),从而减少数据包大小,提高传输效率。

String

字节序列类型,分为5种类型

固定长度字符串

string<fix>,长度固定的字符串,跟固定长度的整数一样,但区别是字符串是按数据固有顺序编码的,不需要像整数一样,从右向左编码。

Null结尾的字符串

string<NUL>,长度不固定,但是已一个空数据(0x00)结尾的字符串。类似于HTTP协议Head的结束标志\r\n\r\n,只要读到0x00就认为结束。

变量长度字符串

string<var>,字符串的长度由另一个字段决定,或者在运行时计算。比如说消息体的长度,就是由消息头里的payload_length这个字段指定的。

编码长度字符串

string<lenenc>,用前缀整数指定长度的字符串,跟编码长度整数字符串一致。

包结尾字符串

string<EOF>,放在包最后的字符串,因为在最后,所以只要用包长度 - 已读长度就是剩余字符串的长度了。

总结

所有的数据类型如下:

Type Description
int<1> 1 byte Protocol::FixedLengthInteger
int<2> 2 byte Protocol::FixedLengthInteger
int<3> 3 byte Protocol::FixedLengthInteger
int<4> 4 byte Protocol::FixedLengthInteger
int<6> 6 byte Protocol::FixedLengthInteger
int<8> 8 byte Protocol::FixedLengthInteger
int<lenenc> Protocol::LengthEncodedInteger
string<lenenc> Protocol::LengthEncodedString
string<fix> Protocol::FixedLengthString
string<var> Protocol::VariableLengthString:
string<EOF> Protocol::RestOfPacketString
string<NUL> Protocol::NulTerminatedString

那么为什么要定义这些数据类型?因为在网络传输数据时,有两个问题:1,数据表示什么,2,数据边界在哪。

例如:客户端向服务器提交用户名密码,数据在网络上是一个个字节发送的,服务器怎么知道哪段是用户名,哪段是密码。最常见的解决办法是这样的:

{"username":"san", "password":"123"}

纯文本、便于阅读、结构清晰、没有特殊关键字、没有过多无用符号,比xml好多了。但是仍然不够,大括号、双引号、冒号,都是额外的开销,当数据量大到一定地步,都是巨大的资源浪费。这就是很多大厂开发RPC框架的原因,例如Dubbo、Protobuf(当然对于服务调用HTTP本身也很浪费资源)。

MySQL这种对性能有极致要求的数据库当然也不能这么做,同时,因为数据库通讯数据结构很固定,每次传输的字段基本没变化。所以可以这么做:

  • 按提前规定好的顺序发送每个字段值,比如先发用户名,后发密码

  • 特殊的标志位、常量直接规定长度,比如成功/失败,一个字节就够了

  • 不确定的值就用各种方法描述,动态长度字符串

这样就可以把没用的数据去掉了,对于一个登陆请求,客户端完全可以传:3san3123。实际上Protobuf之所以快,就是用类似的方法做的。

参考资料:

https://dev.mysql.com/doc/refman/8.0/en/connection-options.html#option_general_protocol

https://dev.mysql.com/doc/dev/mysql-server/8.0.19/page_protocol_basic_data_types.html

https://dev.mysql.com/doc/internals/en/basic-types.html

展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部