MySQL协议定义了多种类型的数据包用于客户端和服务端之间的通信,Query包是客户端向服务端发送SQL查询语句时使用的核心数据包,所有增删改查类的SQL请求都会通过Query包传递到服务端处理。

Query包的整体结构
Query包属于MySQL协议中的命令包类型,整体结构由包头和包体两部分组成,具体格式如下:
| 字段名称 | 长度(字节) | 说明 |
|---|---|---|
| payload_length | 3 | 包体部分的长度,不包含自身和序号字段 |
| sequence_id | 1 | 数据包序号,同一个请求的不同分包序号递增 |
| command | 1 | 命令类型,Query包对应的命令值为0x03 |
| query | 变长 | 要执行的SQL语句,以字符串形式存储,不需要结尾的空字符 |
各字段详细解析
包头部分
包头由payload_length和sequence_id两个字段组成,共4个字节。payload_length采用小端字节序存储,也就是低字节在前,高字节在后,计算时需要把三个字节按顺序拼接成整数。比如payload_length的三个字节是0x05、0x00、0x00,那么实际长度是5字节。
sequence_id的取值范围是0到255,每次客户端发起新的请求时序号从0开始,同一个请求如果有多个分包,序号依次递增,服务端返回响应时也会使用对应的序号。
包体部分
包体的第一个字节是command字段,Query包的固定命令值是0x03,服务端通过这个字段判断当前数据包是查询请求。之后的所有内容都是query字段,也就是客户端要发送的SQL语句,SQL语句以原始字符串形式存储,不需要添加额外的结束符。
Query包解析示例
下面给出基于Python的Query包解析代码,假设我们已经拿到了完整的Query包字节流数据:
import struct
def parse_query_packet(packet_bytes):
# 解析payload_length,3字节小端
payload_length = struct.unpack('<B<B<B', packet_bytes[0:3])[0] + (struct.unpack('<B<B<B', packet_bytes[0:3])[1] << 8) + (struct.unpack('<B<B<B', packet_bytes[0:3])[2] << 16)
# 解析sequence_id
sequence_id = packet_bytes[3]
# 解析command,Query包固定为0x03
command = packet_bytes[4]
if command != 0x03:
raise ValueError("当前数据包不是Query包")
# 解析query字段,从索引5开始到包尾
query_bytes = packet_bytes[5:5+payload_length-1] # 减去1个字节的command长度
query = query_bytes.decode('utf-8')
return {
"payload_length": payload_length,
"sequence_id": sequence_id,
"command": hex(command),
"query": query
}
# 构造一个示例Query包,查询语句为SELECT 1
# 假设SQL长度为8字节,payload_length为9(8字节SQL + 1字节command)
example_packet = b'x09x00x00' + b'x00' + b'x03' + b'SELECT 1'
result = parse_query_packet(example_packet)
print("解析结果:", result)
上述代码中,我们先从字节流中提取前4个字节的包头,解析出包体长度和序号,然后判断命令类型是否为Query包的命令值,最后提取出SQL语句并解码为字符串,就完成了Query包的解析。
注意事项
- MySQL协议中所有的整数类型字段,比如payload_length、端口号等,默认都是小端字节序,解析时需要注意字节顺序。
- 如果Query包的包体长度超过16MB,MySQL协议会使用分包机制,此时需要按照序号拼接多个分包后再解析。
- SQL语句的编码默认是utf8,但是客户端和服务端协商后可能使用其他编码,解析时需要根据实际的字符集调整解码方式。