[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
- 如果值 < 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,如果如就是第一种情况,如果不是,根据第一位是0xfc
、0xfd
、0xfe
判断是那种情况,然后读取后面N位即可。
这种编码方式的好处是,在不能提前确定数据大小时,提供一种动态的编码方式,当实际数值较小时,可以不用传输大量的空值(占位的0),从而减少数据包大小,提高传输效率。
String
字节序列类型,分为5种类型
固定长度字符串
string
Null结尾的字符串
string\r\n\r\n
,只要读到0x00
就认为结束。
变量长度字符串
string,字符串的长度由另一个字段决定,或者在运行时计算。比如说消息体的长度,就是由消息头里的_payload_length_这个字段指定的。
编码长度字符串
string
包结尾字符串
string包长度 - 已读长度
就是剩余字符串的长度了。
总结
所有的数据类型如下:
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
Protocol::LengthEncodedInteger
string
string
string
Protocol::VariableLengthString:
string
string
那么为什么要定义这些数据类型?因为在网络传输数据时,有两个问题: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