mysql long类型_怒肝两个月MySQL源码,我总结出这篇2W字的MySQL协议详解(超硬核干货)!!...

点击上方蓝色“冰河技术”,关注并选择“设为星标”

持之以恒,贵在坚持,每天进步一点点!

162b90a54a748bf35d1905d32783692b.png

作者个人研发的在高并发场景下,提供的简单、稳定、可扩展的延迟消息队列框架,具有精准的定时任务和延迟队列处理功能。自开源半年多以来,已成功为十几家中小型企业提供了精准定时调度方案,经受住了生产环境的考验。为使更多童鞋受益,现给出开源框架地址:

https://github.com/sunshinelyz/mykit-delay

PS: 欢迎各位Star源码,也可以pr你牛逼哄哄的代码。      

写在前面

最近,在开发一个分库分表中间件,由于功能需求,需要分析MySQL协议,发现网上对于MySQL协议分析的文章大部分都过时了,原因是分析的MySQL版本太低了。怎么办呢?于是乎,我便硬着头皮开始啃MySQL源码,经过两个多月的整理,终于总结出这篇MySQL协议。

注:部分来自于互联网,感谢数据库大牛前辈们的默默付出!!

交互过程

MySQL客户端与服务器的交互主要分为两个阶段:握手认证阶段和命令执行阶段。

握手认证阶段

握手认证阶段为客户端与服务器建立连接后进行,交互过程如下:

  • 服务器 -> 客户端:握手初始化消息
  • 客户端 -> 服务器:登陆认证消息
  • 服务器 -> 客户端:认证结果消息

命令执行阶段

客户端认证成功后,会进入命令执行阶段,交互过程如下:

  • 客户端 -> 服务器:执行命令消息
  • 服务器 -> 客户端:命令执行结果

MySQL客户端与服务器的完整交互过程如下

6c985c3365edb85a39c1bdfb3026ed85.png

基本类型

整型值

MySQL报文中整型值分别有1、2、3、4、8字节长度,使用小字节序传输。

字符串(以NULL结尾)(Null-Terminated String)

字符串长度不固定,当遇到'NULL'(0x00)字符时结束。

二进制数据(长度编码)(Length Coded Binary)

数据长度不固定,长度值由数据前的1-9个字节决定,其中长度值所占的字节数不定,字节数由第1个字节决定,如下表:

第一个字节值后续字节数长度值说明
0-2500第一个字节值即为数据的真实长度
2510空数据,数据的真实长度为零
2522后续额外2个字节标识了数据的真实长度
2533后续额外3个字节标识了数据的真实长度
2548后续额外8个字节标识了数据的真实长度

字符串(长度编码)(Length Coded String)

字符串长度不固定,无'NULL'(0x00)结束符,编码方式与上面的 Length Coded Binary 相同。

报文结构

报文分为消息头和消息体两部分,其中消息头占用固定的4个字节,消息体长度由消息头中的长度字段决定,报文结构如下:

f8f39bb43b84abbae8b6a3fc70f317f8.png

消息头

报文长度

用于标记当前请求消息的实际数据长度值,以字节为单位,占用3个字节,最大值为 0xFFFFFF,即接近 16 MB 大小(比16MB少1个字节)。

序号

在一次完整的请求/响应交互过程中,用于保证消息顺序的正确,每次客户端发起请求时,序号值都会从0开始计算。

消息体

消息体用于存放请求的内容及响应的数据,长度由消息头中的长度值决定。

报文类型

登陆认证交互报文

握手初始化报文(服务器 -> 客户端)

aa26efb0fae6f5bfc9bfde6ec916071c.png

服务协议版本号:该值由 PROTOCOL_VERSION 宏定义决定(参考MySQL源代码/include/mysql_version.h头文件定义)

服务版本信息:该值为字符串,由 MYSQL_SERVER_VERSION 宏定义决定(参考MySQL源代码/include/mysql_version.h头文件定义)

服务器线程ID:服务器为当前连接所创建的线程ID。

挑战随机数:MySQL数据库用户认证采用的是挑战/应答的方式,服务器生成该挑战数并发送给客户端,由客户端进行处理并返回相应结果,然后服务器检查是否与预期的结果相同,从而完成用户认证的过程。

服务器权能标志:用于与客户端协商通讯方式,各标志位含义如下(参考MySQL源代码/include/mysql_com.h中的宏定义):

标志位名称标志位说明
CLIENT_LONG_PASSWORD0x0001new more secure passwords
CLIENT_FOUND_ROWS0x0002Found instead of affected rows
CLIENT_LONG_FLAG0x0004Get all column flags
CLIENT_CONNECT_WITH_DB0x0008One can specify db on connect
CLIENT_NO_SCHEMA0x0010Do not allow database.table.column
CLIENT_COMPRESS0x0020Can use compression protocol
CLIENT_ODBC0x0040Odbc client
CLIENT_LOCAL_FILES0x0080Can use LOAD DATA LOCAL
CLIENT_IGNORE_SPACE0x0100Ignore spaces before '('
CLIENT_PROTOCOL_410x0200New 4.1 protocol
CLIENT_INTERACTIVE0x0400This is an interactive client
CLIENT_SSL0x0800Switch to SSL after handshake
CLIENT_IGNORE_SIGPIPE0x1000IGNORE sigpipes
CLIENT_TRANSACTIONS0x2000Client knows about transactions
CLIENT_RESERVED0x4000Old flag for 4.1 protocol
CLIENT_SECURE_CONNECTION0x8000New 4.1 authentication
CLIENT_MULTI_STATEMENTS0x0001 0000Enable/disable multi-stmt support
CLIENT_MULTI_RESULTS0x0002 0000Enable/disable multi-results

字符编码:标识服务器所使用的字符集。

服务器状态:状态值定义如下(参考MySQL源代码/include/mysql_com.h中的宏定义):

状态名称状态值
SERVER_STATUS_IN_TRANS0x0001
SERVER_STATUS_AUTOCOMMIT0x0002
SERVER_STATUS_CURSOR_EXISTS0x0040
SERVER_STATUS_LAST_ROW_SENT0x0080
SERVER_STATUS_DB_DROPPED0x0100
SERVER_STATUS_NO_BACKSLASH_ESCAPES0x0200
SERVER_STATUS_METADATA_CHANGED0x0400

登陆认证报文(客户端 -> 服务器)

MySQL 4.0 及之前的版本

f16b707f920a12f2faf57130ac5cb3c7.png

MySQL 4.1 及之后的版本

c72d05eda1bec2d96270c49e02a8bff0.png

客户端权能标志:用于与客户端协商通讯方式,标志位含义与握手初始化报文中的相同。客户端收到服务器发来的初始化报文后,会对服务器发送的权能标志进行修改,保留自身所支持的功能,然后将权能标返回给服务器,从而保证服务器与客户端通讯的兼容性。

最大消息长度:客户端发送请求报文时所支持的最大消息长度值。

字符编码:标识通讯过程中使用的字符编码,与服务器在认证初始化报文中发送的相同。

用户名:客户端登陆用户的用户名称。

挑战认证数据:客户端用户密码使用服务器发送的挑战随机数进行加密后,生成挑战认证数据,然后返回给服务器,用于对用户身份的认证。

数据库名称:当客户端的权能标志位 CLIENT_CONNECT_WITH_DB 被置位时,该字段必须出现。

客户端命令请求报文(客户端 -> 服务器)

365912afa5efc103c2781c9488e91cdc.png

命令:用于标识当前请求消息的类型,例如切换数据库(0x02)、查询命令(0x03)等。命令值的取值范围及说明如下表(参考MySQL源代码/include/mysql_com.h头文件中的定义):

类型值命令功能关联函数
0x00COM_SLEEP(内部线程状态)(无)
0x01COM_QUIT关闭连接mysql_close
0x02COM_INIT_DB切换数据库mysql_select_db
0x03COM_QUERYSQL查询请求mysql_real_query
0x04COM_FIELD_LIST获取数据表字段信息mysql_list_fields
0x05COM_CREATE_DB创建数据库mysql_create_db
0x06COM_DROP_DB删除数据库mysql_drop_db
0x07COM_REFRESH清除缓存mysql_refresh
0x08COM_SHUTDOWN停止服务器mysql_shutdown
0x09COM_STATISTICS获取服务器统计信息mysql_stat
0x0ACOM_PROCESS_INFO获取当前连接的列表mysql_list_processes
0x0BCOM_CONNECT(内部线程状态)(无)
0x0CCOM_PROCESS_KILL中断某个连接mysql_kill
0x0DCOM_DEBUG保存服务器调试信息mysql_dump_debug_info
0x0ECOM_PING测试连通性mysql_ping
0x0FCOM_TIME(内部线程状态)(无)
0x10COM_DELAYED_INSERT(内部线程状态)(无)
0x11COM_CHANGE_USER重新登陆(不断连接)mysql_change_user
0x12COM_BINLOG_DUMP获取二进制日志信息(无)
0x13COM_TABLE_DUMP获取数据表结构信息(无)
0x14COM_CONNECT_OUT(内部线程状态)(无)
0x15COM_REGISTER_SLAVE从服务器向主服务器进行注册(无)
0x16COM_STMT_PREPARE预处理SQL语句mysql_stmt_prepare
0x17COM_STMT_EXECUTE执行预处理语句mysql_stmt_execute
0x18COM_STMT_SEND_LONG_DATA发送BLOB类型的数据mysql_stmt_send_long_data
0x19COM_STMT_CLOSE销毁预处理语句mysql_stmt_close
0x1ACOM_STMT_RESET清除预处理语句参数缓存mysql_stmt_reset
0x1BCOM_SET_OPTION设置语句选项mysql_set_server_option
0x1CCOM_STMT_FETCH获取预处理语句的执行结果mysql_stmt_fetch

参数:内容是用户在MySQL客户端输入的命令(不包括每行命令结尾的";"分号)。另外这个字段的字符串不是以NULL字符结尾,而是通过消息头中的长度值计算而来。

例如:当我们在MySQL客户端中执行use hutaow;命令时(切换到hutaow数据库),发送的请求报文数据会是下面的样子:

0x02 0x68 0x75 0x74 0x61 0x6f 0x77

其中,0x02为请求类型值COM_INIT_DB,后面的0x68 0x75 0x74 0x61 0x6f 0x77为ASCII字符hutaow

COM_QUIT 消息报文

功能:关闭当前连接(客户端退出),无参数。

COM_INIT_DB 消息报文

功能:切换数据库,对应的SQL语句为USE

字节说明
n数据库名称(字符串到达消息尾部时结束,无结束符)

COM_QUERY 消息报文

功能:最常见的请求消息类型,当用户执行SQL语句时发送该消息。

字节说明
nSQL语句(字符串到达消息尾部时结束,无结束符)

COM_FIELD_LIST 消息报文

功能:查询某表的字段(列)信息,等同于SQL语句SHOW [FULL] FIELDS FROM ...

字节说明
n表格名称(Null-Terminated String)
n字段(列)名称或通配符(可选)

COM_CREATE_DB 消息报文

功能:创建数据库,该消息已过时,而被SQL语句CREATE DATABASE代替。

字节说明
n数据库名称(字符串到达消息尾部时结束,无结束符)

COM_DROP_DB 消息报文

功能:删除数据库,该消息已过时,而被SQL语句DROP DATABASE代替。

字节说明
n数据库名称(字符串到达消息尾部时结束,无结束符)

COM_REFRESH 消息报文

功能:清除缓存,等同于SQL语句FLUSH,或是执行mysqladmin flush-foo命令时发送该消息。

字节说明
1清除缓存选项(位图方式存储,各标志位含义如下)
0x01: REFRESH_GRANT
0x02: REFRESH_LOG
0x04: REFRESH_TABLES
0x08: REFRESH_HOSTS
0x10: REFRESH_STATUS
0x20: REFRESH_THREADS
0x40: REFRESH_SLAVE
0x80: REFRESH_MASTER

COM_SHUTDOWN 消息报文

功能:停止MySQL服务。执行mysqladmin shutdown命令时发送该消息。

字节说明
1停止服务选项
0x00: SHUTDOWN_DEFAULT
0x01: SHUTDOWN_WAIT_CONNECTIONS
0x02: SHUTDOWN_WAIT_TRANSACTIONS
0x08: SHUTDOWN_WAIT_UPDATES
0x10: SHUTDOWN_WAIT_ALL_BUFFERS
0x11: SHUTDOWN_WAIT_CRITICAL_BUFFERS
0xFE: KILL_QUERY
0xFF: KILL_CONNECTION

COM_STATISTICS 消息报文

功能:查看MySQL服务的统计信息(例如运行时间、每秒查询次数等)。执行mysqladmin status命令时发送该消息,无参数。

COM_PROCESS_INFO 消息报文

功能:获取当前活动的线程(连接)列表。等同于SQL语句SHOW PROCESSLIST,或是执行mysqladmin processlist命令时发送该消息,无参数。

COM_PROCESS_KILL 消息报文

功能:要求服务器中断某个连接。等同于SQL语句KILL

字节说明
4连接ID号(小字节序)

COM_DEBUG 消息报文

功能:要求服务器将调试信息保存下来,保存的信息多少依赖于编译选项设置(debug=no|yes|full)。执行mysqladmin debug命令时发送该消息,无参数。

COM_PING 消息报文

功能:该消息用来测试连通性,同时会将服务器的无效连接(超时)计数器清零。执行mysqladmin ping命令时发送该消息,无参数。

COM_CHANGE_USER 消息报文

功能:在不断连接的情况下重新登陆,该操作会销毁MySQL服务器端的会话上下文(包括临时表、会话变量等)。有些连接池用这种方法实现清除会话上下文。

字节说明
n用户名(字符串以NULL结尾)
n密码(挑战数)
MySQL 3.23 版本:Null-Terminated String(长度9字节)
MySQL 4.1 版本:Length Coded String(长度1+21字节)
n数据库名称(Null-Terminated String)
2字符编码

COM_BINLOG_DUMP 消息报文

功能:该消息是备份连接时由从服务器向主服务器发送的最后一个请求,主服务器收到后,会响应一系列的报文,每个报文都包含一个二进制日志事件。如果主服务器出现故障时,会发送一个EOF报文。

字节说明
4二进制日志数据的起始位置(小字节序)
4二进制日志数据标志位(目前未使用,永远为0x00)
4从服务器的服务器ID值(小字节序)
n二进制日志的文件名称(可选,默认值为主服务器上第一个有效的文件名)

COM_TABLE_DUMP 消息报文

功能:将数据表从主服务器复制到从服务器中,执行SQL语句LOAD TABLE ... FROM MASTER时发送该消息。目前该消息已过时,不再使用。

字节说明
n数据库名称(Length Coded String)
n数据表名称(Length Coded String)

COM_REGISTER_SLAVE 消息报文

功能:在从服务器report_host变量设置的情况下,当备份连接时向主服务器发送的注册消息。

字节说明
4从服务器ID值(小字节序)
n主服务器IP地址(Length Coded String)
n主服务器用户名(Length Coded String)
n主服务器密码(Length Coded String)
2主服务器端口号
4安全备份级别(由MySQL服务器rpl_recovery_rank变量设置,暂时未使用)
4主服务器ID值(值恒为0x00)

COM_PREPARE 消息报文

功能:预处理SQL语句,使用带有"?"占位符的SQL语句时发送该消息。

字节说明
n带有"?"占位符的SQL语句(字符串到达消息尾部时结束,无结束符)

COM_EXECUTE 消息报文

功能:执行预处理语句。

字节说明
4预处理语句的ID值
1标志位
0x00: CURSOR_TYPE_NO_CURSOR
0x01: CURSOR_TYPE_READ_ONLY
0x02: CURSOR_TYPE_FOR_UPDATE
0x04: CURSOR_TYPE_SCROLLABLE
4保留(值恒为0x01)
如果参数数量大于0
n空位图(Null-Bitmap,长度 = (参数数量 + 7) / 8 字节)
1参数分隔标志
如果参数分隔标志值为1
n每个参数的类型值(长度 = 参数数量 * 2 字节)
n每个参数的值

COM_LONG_DATA 消息报文

该消息报文有两种形式,一种用于发送二进制数据,另一种用于发送文本数据。

功能:用于发送二进制(BLOB)类型的数据(调用mysql_stmt_send_long_data函数)。

字节说明
4预处理语句的ID值(小字节序)
2参数序号(小字节序)
n数据负载(数据到达消息尾部时结束,无结束符)

功能:用于发送超长字符串类型的数据(调用mysql_send_long_data函数)

字节说明
4预处理语句的ID值(小字节序)
2参数序号(小字节序)
2数据类型(未使用)
n数据负载(数据到达消息尾部时结束,无结束符)

COM_CLOSE_STMT 消息报文

功能:销毁预处理语句。

字节说明
4预处理语句的ID值(小字节序)

COM_RESET_STMT 消息报文

功能:将预处理语句的参数缓存清空。多数情况和COM_LONG_DATA一起使用。

字节说明
4预处理语句的ID值(小字节序)

COM_SET_OPTION 消息报文

功能:设置语句选项,选项值为/include/mysql_com.h头文件中定义的enum_mysql_set_option枚举类型:

  • MYSQL_OPTION_MULTI_STATEMENTS_ON
  • MYSQL_OPTION_MULTI_STATEMENTS_OFF
字节说明
2选项值(小字节序)

COM_FETCH_STMT 消息报文

功能:获取预处理语句的执行结果(一次可以获取多行数据)。

字节说明
4预处理语句的ID值(小字节序)
4数据的行数(小字节序)

服务器响应报文(服务器 -> 客户端)

当客户端发起认证请求或命令请求后,服务器会返回相应的执行结果给客户端。客户端在收到响应报文后,需要首先检查第1个字节的值,来区分响应报文的类型。

响应报文类型第1个字节取值范围
OK 响应报文0x00
Error 响应报文0xFF
Result Set 报文0x01 - 0xFA
Field 报文0x01 - 0xFA
Row Data 报文0x01 - 0xFA
EOF 报文0xFE

注:响应报文的第1个字节在不同类型中含义不同,比如在OK报文中,该字节并没有实际意义,值恒为0x00;而在Result Set报文中,该字节又是长度编码的二进制数据结构(Length Coded Binary)中的第1字节。

响应报文

客户端的命令执行正确时,服务器会返回OK响应报文。

MySQL 4.0 及之前的版本

字节说明
1OK报文,值恒为0x00
1-9受影响行数(Length Coded Binary)
1-9索引ID值(Length Coded Binary)
2服务器状态
n服务器消息(字符串到达消息尾部时结束,无结束符)

MySQL 4.1 及之后的版本

字节说明
1OK报文,值恒为0x00
1-9受影响行数(Length Coded Binary)
1-9索引ID值(Length Coded Binary)
2服务器状态
2告警计数
n服务器消息(字符串到达消息尾部时结束,无结束符,可选)

受影响行数:当执行INSERT/UPDATE/DELETE语句时所影响的数据行数。

索引ID值:该值为AUTO_INCREMENT索引字段生成,如果没有索引字段,则为0x00。注意:当INSERT插入语句为多行数据时,该索引ID值为第一个插入的数据行索引值,而非最后一个。

服务器状态:客户端可以通过该值检查命令是否在事务处理中。

告警计数:告警发生的次数。

服务器消息:服务器返回给客户端的消息,一般为简单的描述性字符串,可选字段。

响应报文

MySQL 4.0 及之前的版本

字节说明
1Error报文,值恒为0xFF
2错误编号(小字节序)
n服务器消息

MySQL 4.1 及之后的版本

字节说明
1Error报文,值恒为0xFF
2错误编号(小字节序)
1服务器状态标志,恒为'#'字符
5服务器状态(5个字符)
n服务器消息

错误编号:错误编号值定义在源代码/include/mysqld_error.h头文件中。

服务器状态:服务器将错误编号通过mysql_errno_to_sqlstate函数转换为状态值,状态值由5字节的ASCII字符组成,定义在源代码/include/sql_state.h头文件中。

服务器消息:错误消息字符串到达消息尾时结束,长度可以由消息头中的长度值计算得出。消息长度为0-512字节。

Result Set 消息

当客户端发送查询请求后,在没有错误的情况下,服务器会返回结果集(Result Set)给客户端。

Result Set 消息分为五部分,结构如下:

结构说明
[Result Set Header]列数量
[Field]列信息(多个)
[EOF]列结束
[Row Data]行数据(多个)
[EOF]数据结束

Result Set Header 结构

字节说明
1-9Field结构计数(Length Coded Binary)
1-9额外信息(Length Coded Binary)

Field结构计数:用于标识Field结构的数量,取值范围0x00-0xFA。

额外信息:可选字段,一般情况下不应该出现。只有像SHOW COLUMNS这种语句的执行结果才会用到额外信息(标识表格的列数量)。

Field 结构

Field为数据表的列信息,在Result Set中,Field会连续出现多次,次数由Result Set Header结构中的IField结构计数值决定。

MySQL 4.0 及之前的版本

字节说明
n数据表名称(Length Coded String)
n列(字段)名称(Length Coded String)
4列(字段)长度(Length Coded String)
2列(字段)类型(Length Coded String)
2列(字段)标志(Length Coded String)
1整型值精度
n默认值(Length Coded String)

MySQL 4.1 及之后的版本

字节说明
n目录名称(Length Coded String)
n数据库名称(Length Coded String)
n数据表名称(Length Coded String)
n数据表原始名称(Length Coded String)
n列(字段)名称(Length Coded String)
4列(字段)原始名称(Length Coded String)
1填充值
2字符编码
4列(字段)长度
1列(字段)类型
2列(字段)标志
1整型值精度
2填充值(0x00)
n默认值(Length Coded String)

目录名称:在4.1及之后的版本中,该字段值为"def"。

数据库名称:数据库名称标识。

数据表名称:数据表的别名(AS之后的名称)。

数据表原始名称:数据表的原始名称(AS之前的名称)。

列(字段)名称:列(字段)的别名(AS之后的名称)。

列(字段)原始名称:列(字段)的原始名称(AS之前的名称)。

字符编码:列(字段)的字符编码值。

列(字段)长度:列(字段)的长度值,真实长度可能小于该值,例如VARCHAR(2)类型的字段实际只能存储1个字符。

列(字段)类型:列(字段)的类型值,取值范围如下(参考源代码/include/mysql_com.h头文件中的enum_field_type枚举类型定义):

类型值名称
0x00FIELD_TYPE_DECIMAL
0x01FIELD_TYPE_TINY
0x02FIELD_TYPE_SHORT
0x03FIELD_TYPE_LONG
0x04FIELD_TYPE_FLOAT
0x05FIELD_TYPE_DOUBLE
0x06FIELD_TYPE_NULL
0x07FIELD_TYPE_TIMESTAMP
0x08FIELD_TYPE_LONGLONG
0x09FIELD_TYPE_INT24
0x0AFIELD_TYPE_DATE
0x0BFIELD_TYPE_TIME
0x0CFIELD_TYPE_DATETIME
0x0DFIELD_TYPE_YEAR
0x0EFIELD_TYPE_NEWDATE
0x0FFIELD_TYPE_VARCHAR (new in MySQL 5.0)
0x10FIELD_TYPE_BIT (new in MySQL 5.0)
0xF6FIELD_TYPE_NEWDECIMAL (new in MYSQL 5.0)
0xF7FIELD_TYPE_ENUM
0xF8FIELD_TYPE_SET
0xF9FIELD_TYPE_TINY_BLOB
0xFAFIELD_TYPE_MEDIUM_BLOB
0xFBFIELD_TYPE_LONG_BLOB
0xFCFIELD_TYPE_BLOB
0xFDFIELD_TYPE_VAR_STRING
0xFEFIELD_TYPE_STRING
0xFFFIELD_TYPE_GEOMETRY

列(字段)标志:各标志位定义如下(参考源代码/include/mysql_com.h头文件中的宏定义):

标志位名称
0x0001NOT_NULL_FLAG
0x0002PRI_KEY_FLAG
0x0004UNIQUE_KEY_FLAG
0x0008MULTIPLE_KEY_FLAG
0x0010BLOB_FLAG
0x0020UNSIGNED_FLAG
0x0040ZEROFILL_FLAG
0x0080BINARY_FLAG
0x0100ENUM_FLAG
0x0200AUTO_INCREMENT_FLAG
0x0400TIMESTAMP_FLAG
0x0800SET_FLAG

数值精度:该字段对DECIMALNUMERIC类型的数值字段有效,用于标识数值的精度(小数点位置)。

默认值:该字段用在数据表定义中,普通的查询结果中不会出现。

:Field结构的相关处理函数:

  • 客户端:/client/client.c源文件中的unpack_fields函数
  • 服务器:/sql/sql_base.cc源文件中的send_fields函数

EOF 结构

EOF结构用于标识Field和Row Data的结束,在预处理语句中,EOF也被用来标识参数的结束。

MySQL 4.0 及之前的版本

字节说明
1EOF值(0xFE)

MySQL 4.1 及之后的版本

字节说明
1EOF值(0xFE)
2告警计数
2状态标志位

告警计数:服务器告警数量,在所有数据都发送给客户端后该值才有效。

状态标志位:包含类似SERVER_MORE_RESULTS_EXISTS这样的标志位。

:由于EOF值与其它Result Set结构共用1字节,所以在收到报文后需要对EOF包的真实性进行校验,校验条件为:

  • 第1字节值为0xFE
  • 包长度小于9字节

:EOF结构的相关处理函数:

  • 服务器:protocol.cc源文件中的send_eof函数

Row Data 结构

在Result Set消息中,会包含多个Row Data结构,每个Row Data结构又包含多个字段值,这些字段值组成一行数据。

字节说明
n字段值(Length Coded String)
...(一行数据中包含多个字段值)

字段值:行数据中的字段值,字符串形式。

:Row Data结构的相关处理函数:

  • 客户端:/client/client.c源文件中的read_rows函数

Row Data 结构(二进制数据)

该结构用于传输二进制的字段值,既可以是服务器返回的结果,也可以是由客户端发送的(当执行预处理语句时,客户端使用Result Set消息来发送参数及数据)。

字节说明
1结构头(0x00)
(列数量 + 7 + 2) / 8空位图
n字段值
...(一行数据中包含多个字段值)

空位图:前2个比特位被保留,值分别为0和1,以保证不会和OK、Error包的首字节冲突。在MySQL 5.0及之后的版本中,这2个比特位的值都为0。

字段值:行数据中的字段值,二进制形式。

PREPARE_OK 响应报文(Prepared Statement)

用于响应客户端发起的预处理语句报文,组成结构如下:

结构说明
[PREPARE_OK]PREPARE_OK结构
如果参数数量大于0
[Field]与Result Set消息结构相同
[EOF]
如果列数大于0
[Field]与Result Set消息结构相同
[EOF]

其中 PREPARD_OK 的结构如下:

字节说明
1OK报文,值为0x00
4预处理语句ID值
2列数量
2参数数量
1填充值(0x00)
2告警计数

Parameter 响应报文(Prepared Statement)

预处理语句的值与参数正确对应后,服务器会返回 Parameter 报文。

字节说明
2类型
2标志
1数值精度
4字段长度

类型:与 Field 结构中的字段类型相同。

标志:与 Field 结构中的字段标志相同。

数值精度:与 Field 结构中的数值精度相同。

字段长度:与 Field 结构中的字段长度相同。

代码分析

议程
协议头
协议类型 网络协议相关函数 NET缓冲 VIO缓冲 MySQL API

协议头

● 数据变成在网络里传输的数据,需要额外的在头部添加4 个字节的包头.

. packet length(3字节), 包体的长度

. packet number(1字节), 从0开始的递增的

● sql “select 1” 的网络协议是?

协议头

● packet length三个字节意味着MySQL packet最大16M大于16M则被分包(net_write_command, my_net_write)

● packet number分包从0开始,依次递增.每一次执行sql, packet_number清零(sql/net_serv.c:net_clear)

协议类型

● handshake

● auth

● ok|error

● resultset

○ header

○ field

○ eof

○ row

● command packet

连接时的交互

协议说明

● 协议内字段分三种形式

○ 固定长度(include/my_global.h)

■ uint*korr 解包 *

■ int*store 封包

○ length coded binary(sql-common/pack.c)

■ net_field_length 解包

■ net_store_length 封包

○ null-terminated string

● length coded binary

○ 避免binary unsafe string, 字符串的长度保存在字符串的前面

■ length<251 1 byte

■ length <256^2 3 byte(第一个byte是252)

■ length<256^3 4byte(第一个byte是253)

■ else 9byte(第一个byte是254)

handshake packet

● 该协议由服务端发送客户端

● 括号内为字节数,字节数为n为是null-terminated string;字节数为大写的N表示length code binary.

● salt就是scramble.分成两个部分是为了兼容4.1版本

● sql_connect.cc:check_connection

● sql_client.c:mysql_real_connect

auth packet

● 该协议是从客户端对密码使用scramble加密后发送到服务端

● 其中databasename是可选的.salt就是加密后的密码.

● sql_client.c:mysql_real_connect

● sql_connect.c:check_connection

ok packet

● ok包,命令和insert,update,delete的返回结果

● 包体首字节为0.

● insert_id, affect_rows也是一并发过来.

● src/protocol.cc:net_send_ok

error packet

● 错误的命令,非法的sql的返回包

● 包体首字节为255.

● error code就是CR_***,include/errmsg.h ● sqlstate marker是#

● sqlstate是错误状态,include/sql_state.h

● message是错误的信息

● sql/protocol.cc:net_send_error_packet

resultset packet

● 结果集的数据包,由多个packet组合而成

● 例如查询一个结构集,顺序如下: ○ header ○ field1....fieldN ○ eof ○ row1...rowN ○ eof

● sql/client.c:cli_read_query_result

● 下面是一个sql "select * from d"查询结果集的例子,结果 集是6行,3个字段 ○ 公式:假设结果集有N行, M个字段.则包的个数为,header(1) + field (M) + eof(1) + row(N) + eof(1) ○ 所以这个例子的MySQL packet的个数是12个

resultset packet - header

● field packet number决定了接下来的field packet的个数.

● 一个返回6行记录,3个字段的查询语句

resultset packet - field

● 结果集中一个字段一个field packet.

● tables_alias是sql语句里表的别名,org_table才是表的真 实名字.

● sql/protocol.cc:Protocol::send_fields

● sql/client.c:cli_read_query_result

resultset packet - eof

● eof包是用于分割field packet和row packet.

● 包体首字节为254

● sql/protocol.cc:net_send_eof

resultset packet - row

● row packet里才是真正的数据包.一行数据一个packet.

● row里的每个字段都是length coded binary

● 字段的个数在header packet里

● sql/client.c:cli_read_rows

command packet

● 命令包,包括我们的sql语句还有一些常见的命令.

● 包体首字母表示命令的类型(include/mysql_com.h),大 部分命令都是COM_QUERY.

网络协议关键函数

● net_write_command(sql/net_serv.cc)所有的sql最终调用这个命令发送出去.

● my_net_write(sql/net_serv.cc)连接阶段的socket write操作调用这个函数.

● my_net_read读取包,会判断包大小,是否是分包

● my_real_read解析MySQL packet,第一次读取4字节,根据packet length再读取余下来的长度

● cli_safe_read客户端解包函数,包含了my_net_read

NET缓冲

● 每次socket操作都会先把数据写,读到net->buff,这是一 个缓冲区, 减少系统调用调用的次数.

● 当写入的数据和buff内的数据超过buff大小才会发出一次 write操作,然后再把要写入的buff里插入数, 写入不会 导致buff区区域扩展.(sql/net_serv.cc: net_write_buff).

● net->buff大小初始net->max_packet, 读取会导致会导致 buff的realloc最大net->max_packet_size

● 一次sql命令的结束都会调用net_flush,把buff里的数据 都写到socket里.

VIO缓冲

● 从my_read_read可以看出每次packet读取都是按需读取, 为了减少系统调用,vio层面加了一个read_buffer.

● 每次读取前先判断vio->read_buffer所需数据的长度是 否足够.如果存在则直接copy. 如果不够,则触发一次 socket read 读取2048个字(vio/viosocket.c: vio_read_buff)

MySQL API

● 数据从mysql_send_query处发送给服务端,实际调用的是 net_write_command.

● cli_read_query_result解析header packet, field packet,获 得field_count的个数

● mysql_store_result解析了row packet,并存储在result- >data里

● myql_fetch_row其实遍历result->data

PACKET NUMBER

在做proxy的时候在这里迷糊过,翻了几遍代码才搞明白,细节如下:  客户端服务端的net->pkt_nr都从0开始.接受包时比较packet number  和net->pkt_nr是否相等,否则报packet number乱序,连接报错;相等则pkt_nr自增.发送包时把net->pkt_nr作为packet number发送,然后对net->pkt_nr进行自增保持和对端的同步.

接收包

sql/net_serv.c:my_real_readif (net->buff[net->where_b + 3] != (uchar) net->pkt_nr)

发送包

sql/net_serv.c:my_net_writeint3store(buff,len);
   buff[3]= (uchar) net->pkt_nr++;

我们来几个具体场景的packet number, net->pkt_nr的变化

连接

 c ———–> s 0  connect
 c -0——s 1  handshake
 c —–1—–>s 1  auth
 c 2——s 0  ok

开始两方都为0,服务端发送handshake packet(pkt=0)之后自增为1,然后等待对端发送过来pkt=1的包

查询

每次查询,服务客户端都会对net->pkt_nr进行清零

include/mysql_com.h
 #define net_new_transaction(net) ((net)->pkt_nr=0)
sql/sql_parse.cc:do_commandnet_new_transaction(net);
sql/client.c:cli_advanced_commandnet_clear(&mysql->net, (command != COM_QUIT));

开始两方net->pkt_nr皆为0, 命令发送后客户端端为1,服务端开始发送分包,分包的pkt_nr的依次递增,客户端的net->pkt_nr也随之增加.

 c ——0—–> s 0  query
 c -1——s 2  resultset
 c -2——s 3  resultset

解包的细节

my_net_read负责解包,首先读取4个字节,判断packet number是否等于net->pkt_nr然后再次读取packet_number长度的包体。

伪代码如下:

remain=4
for(i = 0; i 2; i++) {
    //数据是否读完
    while (remain>0)  {
        length = read(fd, net->buff, remain)
        remain = remain - length
    }
    //第一次
    if (i=0) {
        remain = uint3korr(net->buff+net->where_b);
    }
}

网络层优化

从ppt里可以看到,一个resultset packet由多个包组成,如果每次读写包都导致系统调用那肯定是不合理,常规优化方法:写大包加预读

NET->BUFF

每个包发送到网络或者从网络读包都会先把数据包保存在net->buff里,待到net->buff满了或者一次命令结束才会通过socket发出给对端.net->buff有个初始大小(net->max_packet),会随读取数据的增多而扩展.

VIO->READ_BUFFER

每次从网络读包,并不是按包的大小读取,而是会尽量读取2048个字节,这样一个resultset包的读取不会再引起多次的系统调用了.header packet读取完毕后, 接下来的field,eof, row  apcket读取仅仅需要从vio-read_buffer拷贝指定字节的数据即可.

MYSQL API说明

api和MySQL客户端都会使用sql/client.c这个文件,解包的过程都是使用sql/client.c:cli_read_query_result.

mysql_store_result来解析row packet,并把数据存储到res->data里,此时所有数据都存内存里了.

mysql_fetch_row仅仅是使用内部的游标,遍历result->data里的数据

if (!res->data_cursor)
{
    DBUG_PRINT("info",("end of data"));
    DBUG_RETURN(res->current_row=(MYSQL_ROW) NULL);
}
tmp = res->data_cursor->data;
res->data_cursor = res->data_cursor->next;
DBUG_RETURN(res->current_row=tmp);

mysql_free_result是把result->data指定的行数据释放掉.

大部分参考:http://hutaow.com/blog/2013/11/06/mysql-protocol-analysis/

重磅福利

微信搜一搜【冰河技术】微信公众号,关注这个有深度的程序员,每天阅读超硬核技术干货,公众号内回复【PDF】有我准备的一线大厂面试资料和我原创的超硬核PDF技术文档,以及我为大家精心准备的多套简历模板(不断更新中),希望大家都能找到心仪的工作,学习是一条时而郁郁寡欢,时而开怀大笑的路,加油。如果你通过努力成功进入到了心仪的公司,一定不要懈怠放松,职场成长和新技术学习一样,不进则退。如果有幸我们江湖再见!

另外,我开源的各个PDF,后续我都会持续更新和维护,感谢大家长期以来对冰河的支持!!

写在最后

如果你觉得冰河写的还不错,请微信搜索并关注「 冰河技术 」微信公众号,跟冰河学习高并发、分布式、微服务、大数据、互联网和云原生技术,「 冰河技术 」微信公众号更新了大量技术专题,每一篇技术文章干货满满!不少读者已经通过阅读「 冰河技术 」微信公众号文章,吊打面试官,成功跳槽到大厂;也有不少读者实现了技术上的飞跃,成为公司的技术骨干!如果你也想像他们一样提升自己的能力,实现技术能力的飞跃,进大厂,升职加薪,那就关注「 冰河技术 」微信公众号吧,每天更新超硬核技术干货,让你对如何提升技术能力不再迷茫!

f7d7390a9586a507ad5f9393e42d74e8.png

留言区

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/266405.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

状压dp之二之三 炮兵阵地/玉米田 By cellur925

一、简单的状压dp 玉米田 题目描述 Farmer John has purchased a lush new rectangular pasture composed of M by N (1 ≤ M ≤ 12; 1 ≤ N ≤ 12) square parcels. He wants to grow some yummy corn for the cows on a number of squares. Regrettably, some of the squares…

LockSupport的源码实现原理以及应用

一、为什么使用LockSupport类 如果只是LockSupport在使用起来比Object的wait/notify简单&#xff0c; 那还真没必要专门讲解下LockSupport。最主要的是灵活性。 上边的例子代码中&#xff0c;主线程调用了Thread.sleep(1000)方法来等待线程A计算完成进入wait状态。如果去掉Thr…

python3 爬虫 requests安装_BOSS直聘招聘信息获取之爬虫工具分析

点击蓝色“不太灵光的程序员”关注我哟加个“星标”&#xff0c;每天上午 09:30&#xff0c;干货推送&#xff01;文中使用的组件库仅限于Python语言&#xff0c;由于最近收到一些同学的留言说&#xff0c;按照网上的教程一步一步的学习&#xff0c;却频繁的出现报错&#xff0…

假期周进度总计(四)

本周学习利用OEM工具创建删除表以及对表内数据进行增删改查的操作 一切均通过此工具进行&#xff0c;然后还学到了用SQL *Plus进行相应操作 本周每天4.5个小时&#xff0c;出现的错误就是点击的误操作&#xff0c;可以改正&#xff0c;下周进一步学习Oracle基本操作转载于:http…

Java集合框架(3)

Map(和Collection<E>一样都是集合框架的顶层接口) |--Hashtable:底层是哈希表数据结构&#xff0c;不可以用null对象作为键或值。它是线程同步的。 |--HashMap&#xff1a;底层是哈希表。允许使用null键null值&#xff0c;该集合是不同步的&#xff0c;效率高&#xff0c…

php函数从数组中取出指定的数目,PHP数组函数

1.array_rand()从数组中随机取出一个或多个元素(返回值是&#xff1a;随机元素的键)$arr[js,css,25,php,30];printf(%s,print_r($arr,true));//随机去2个元素$resarray_rand($arr,2);printf(%s,print_r($res,true));2.array_replace()使用后面数组元素相同 key 的值替换 array1…

存储过程 not supported yet_让我们来看看+Redis如何存储和计算一亿用户的活跃度

1前段时间&#xff0c;在网上看到一道面试题&#xff1a;如何用redis存储统计1亿用户一年的登陆情况&#xff0c;并快速检索任意时间窗口内的活跃用户数量。觉得很有意思&#xff0c;就仔细想了下 。并做了一系列实验&#xff0c;自己模拟了下 。还是有点收获的&#xff0c;现整…

HBase查询优化

1.概述 HBase是一个实时的非关系型数据库&#xff0c;用来存储海量数据。但是&#xff0c;在实际使用场景中&#xff0c;在使用HBase API查询HBase中的数据时&#xff0c;有时会发现数据查询会很慢。本篇博客将从客户端优化和服务端优化两个方面来介绍&#xff0c;如何提高查询…

NEC SV8100电话交换机配置梓博电话计费系统

为了节约办公成本&#xff0c;规范电话使用。公司最近为NEC SV8100电话交换机系统上线了一套梓博的电话计费系统。先将配置过程分享给各位。 1、登录电话交换机系统&#xff08;默认用户名tech、密码12345678&#xff09; 2、点击系统数据配置按钮 2、通过10-01选项调整电话交换…

判断闰年 php,PHP怎么判断一年是否为闰年?

判断是否为闰年的条件是满足下列二者条件之一&#xff1a;年号能被4整除、但不能被100整除&#xff0c;二是年号能被4整除&#xff0c;又能被400整除。那么PHP怎么判断一年是否为闰年&#xff1f;下面本篇文章就来给大家介绍一下使用PHP判断一年是否为闰年的方法&#xff0c;希…

redis desktop manager_面试官:Redis分布式锁如何解决锁超时问题?

Java面试笔试面经、Java技术每天学习一点Java面试关注不迷路作者&#xff1a;wangzaiplus来源&#xff1a;https://www.jianshu.com/u/8cb4591440ca一、前言关于redis分布式锁, 查了很多资料, 发现很多只是实现了最基础的功能, 但是, 并没有解决当锁已超时而业务逻辑还未执行完…

python print 换行_Python学习 | Python的基础语法

Python 语言与 Perl&#xff0c;C 和 Java 等语言有许多相似之处。但是&#xff0c;也存在一些差异&#xff0c;编写Paython程序之前需要对语法有所了解&#xff0c;才能编写规范的Python程序。一、行和缩进Python最大的特点之一就是Python 的代码块不使用大括号 {}了&#xff…

python 创建文件_Python入学首次项目,新手必看,简单易操作

继昨天文章python软件pycharm安装教程之后&#xff0c;今天则给新手小白们分享一哈&#xff0c;怎么制作并创建文件。print “hello world”&#xff1b;如后期需要资料文件的则可以私信留言&#xff0c;领取首次项目资料。本节知识点&#xff1a;python项目的创建pycharm的使用…

ajax跨域实现

2019独角兽企业重金招聘Python工程师标准>>> 我们都知道ajax是不能跨域的&#xff0c;那么怎么实现ajax跨域呢&#xff1f; 看了看jquery&#xff0c;当然&#xff0c;jquery封装的很好&#xff0c;$.ajax就可以实现跨域&#xff0c;只需要在参数中配置一下即可&am…

python leetcode_leetcode 刷题经验,主力 python

1. 树的先序遍历可以求高度&#xff0c;后序遍历可以求深度。剑指 Offer 55 - II. 平衡二叉树​leetcode-cn.com2. 二叉搜索树的中序遍历可以递增地返回所有元素。逆序的中序遍历&#xff08;即先右子节点&#xff0c;再根节点&#xff0c;再左子节点&#xff09;可以递减的返回…

sqlldr 导入乱码,Oracle客户端字符集问题

2019独角兽企业重金招聘Python工程师标准>>> 1&#xff0c;查Oracle数据库创建时候的字符集&#xff1a; Oracle服务器端执行 SQL> select name, value$ from sys.props$ where name like NLS%; NAME VALUE$ ------------------------------ -------------------…

Python在mysql中进行操作是十分容易和简洁的

首先声明一下&#xff0c;我用的是Windows系统&#xff01; 1、在Python中对mysql数据库进行操作首先要导入pymysql模块&#xff0c;默认情况下&#xff0c;Python中是没有安装这个模块的&#xff0c; 可以在Windows的命令行中用pip install pymysql来安装&#xff08;注意要连…

讲php fpm的书,细说PHP-fpm

最近在研究PHP的源码&#xff0c;有时候会延伸到很多东西。这里就专程找了下php-fpm的内容学习下。是什么&#xff1f;在理解php-fpm之前&#xff0c;我们要先搞清楚几个关键词以及他们之间的关系:CGIFastCGIphp-fpmphp-cgi.CGI:(Common Gateway Interface)&#xff0c;即通用网…

安卓微软雅黑字体ttf_618巨献丨精致的悦黑5字重小字体

悦黑字体简介去年双十一当天&#xff0c;小编应大家要求&#xff0c;分享了一款悦黑小字体&#xff0c;苹果和安卓都有&#xff1a;双十一巨献&#xff1a;令人瑟瑟发抖的5字重悦黑小字体大半年时间过去了&#xff0c;一直没有更新&#xff0c;今天抽空更新一下。悦黑是由造字工…

伪静态隐藏域名后缀_你想知道的动态URL、静态URl、伪静态URL概念及区别都在这里!...

【小宅按】我们说url的动态、静态、伪静态三种形式&#xff0c;其实从严格分类上来说&#xff0c;伪静态也是动态的一种&#xff0c;只是表现形式为静态。参考&#xff1a;动态url、静态url和伪静态url的详细讲解 - 好文分享动态URl动态页面的特征1、以ASP、PHP、JSP、ASP.NET …