网站 优化,专业建公司网站,jae wordpress,wordpress 变更中文点击上方蓝色“冰河技术”#xff0c;关注并选择“设为星标”持之以恒#xff0c;贵在坚持#xff0c;每天进步一点点#xff01;作者个人研发的在高并发场景下#xff0c;提供的简单、稳定、可扩展的延迟消息队列框架#xff0c;具有精准的定时任务和延迟队列处理功能。… 点击上方蓝色“冰河技术”关注并选择“设为星标”持之以恒贵在坚持每天进步一点点作者个人研发的在高并发场景下提供的简单、稳定、可扩展的延迟消息队列框架具有精准的定时任务和延迟队列处理功能。自开源半年多以来已成功为十几家中小型企业提供了精准定时调度方案经受住了生产环境的考验。为使更多童鞋受益现给出开源框架地址https://github.com/sunshinelyz/mykit-delayPS: 欢迎各位Star源码也可以pr你牛逼哄哄的代码。 写在前面最近在开发一个分库分表中间件由于功能需求需要分析MySQL协议发现网上对于MySQL协议分析的文章大部分都过时了原因是分析的MySQL版本太低了。怎么办呢于是乎我便硬着头皮开始啃MySQL源码经过两个多月的整理终于总结出这篇MySQL协议。注部分来自于互联网感谢数据库大牛前辈们的默默付出交互过程MySQL客户端与服务器的交互主要分为两个阶段握手认证阶段和命令执行阶段。握手认证阶段握手认证阶段为客户端与服务器建立连接后进行交互过程如下服务器 - 客户端握手初始化消息客户端 - 服务器登陆认证消息服务器 - 客户端认证结果消息命令执行阶段客户端认证成功后会进入命令执行阶段交互过程如下客户端 - 服务器执行命令消息服务器 - 客户端命令执行结果MySQL客户端与服务器的完整交互过程如下基本类型整型值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个字节消息体长度由消息头中的长度字段决定报文结构如下消息头报文长度用于标记当前请求消息的实际数据长度值以字节为单位占用3个字节最大值为 0xFFFFFF即接近 16 MB 大小(比16MB少1个字节)。序号在一次完整的请求/响应交互过程中用于保证消息顺序的正确每次客户端发起请求时序号值都会从0开始计算。消息体消息体用于存放请求的内容及响应的数据长度由消息头中的长度值决定。报文类型登陆认证交互报文握手初始化报文(服务器 - 客户端)服务协议版本号该值由 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 passwordsCLIENT_FOUND_ROWS0x0002Found instead of affected rowsCLIENT_LONG_FLAG0x0004Get all column flagsCLIENT_CONNECT_WITH_DB0x0008One can specify db on connectCLIENT_NO_SCHEMA0x0010Do not allow database.table.columnCLIENT_COMPRESS0x0020Can use compression protocolCLIENT_ODBC0x0040Odbc clientCLIENT_LOCAL_FILES0x0080Can use LOAD DATA LOCALCLIENT_IGNORE_SPACE0x0100Ignore spaces before (CLIENT_PROTOCOL_410x0200New 4.1 protocolCLIENT_INTERACTIVE0x0400This is an interactive clientCLIENT_SSL0x0800Switch to SSL after handshakeCLIENT_IGNORE_SIGPIPE0x1000IGNORE sigpipesCLIENT_TRANSACTIONS0x2000Client knows about transactionsCLIENT_RESERVED0x4000Old flag for 4.1 protocolCLIENT_SECURE_CONNECTION0x8000New 4.1 authenticationCLIENT_MULTI_STATEMENTS0x0001 0000Enable/disable multi-stmt supportCLIENT_MULTI_RESULTS0x0002 0000Enable/disable multi-results字符编码标识服务器所使用的字符集。服务器状态状态值定义如下(参考MySQL源代码/include/mysql_com.h中的宏定义)状态名称状态值SERVER_STATUS_IN_TRANS0x0001SERVER_STATUS_AUTOCOMMIT0x0002SERVER_STATUS_CURSOR_EXISTS0x0040SERVER_STATUS_LAST_ROW_SENT0x0080SERVER_STATUS_DB_DROPPED0x0100SERVER_STATUS_NO_BACKSLASH_ESCAPES0x0200SERVER_STATUS_METADATA_CHANGED0x0400登陆认证报文(客户端 - 服务器)MySQL 4.0 及之前的版本MySQL 4.1 及之后的版本客户端权能标志用于与客户端协商通讯方式标志位含义与握手初始化报文中的相同。客户端收到服务器发来的初始化报文后会对服务器发送的权能标志进行修改保留自身所支持的功能然后将权能标返回给服务器从而保证服务器与客户端通讯的兼容性。最大消息长度客户端发送请求报文时所支持的最大消息长度值。字符编码标识通讯过程中使用的字符编码与服务器在认证初始化报文中发送的相同。用户名客户端登陆用户的用户名称。挑战认证数据客户端用户密码使用服务器发送的挑战随机数进行加密后生成挑战认证数据然后返回给服务器用于对用户身份的认证。数据库名称当客户端的权能标志位 CLIENT_CONNECT_WITH_DB 被置位时该字段必须出现。客户端命令请求报文(客户端 - 服务器)命令用于标识当前请求消息的类型例如切换数据库(0x02)、查询命令(0x03)等。命令值的取值范围及说明如下表(参考MySQL源代码/include/mysql_com.h头文件中的定义)类型值命令功能关联函数0x00COM_SLEEP(内部线程状态)(无)0x01COM_QUIT关闭连接mysql_close0x02COM_INIT_DB切换数据库mysql_select_db0x03COM_QUERYSQL查询请求mysql_real_query0x04COM_FIELD_LIST获取数据表字段信息mysql_list_fields0x05COM_CREATE_DB创建数据库mysql_create_db0x06COM_DROP_DB删除数据库mysql_drop_db0x07COM_REFRESH清除缓存mysql_refresh0x08COM_SHUTDOWN停止服务器mysql_shutdown0x09COM_STATISTICS获取服务器统计信息mysql_stat0x0ACOM_PROCESS_INFO获取当前连接的列表mysql_list_processes0x0BCOM_CONNECT(内部线程状态)(无)0x0CCOM_PROCESS_KILL中断某个连接mysql_kill0x0DCOM_DEBUG保存服务器调试信息mysql_dump_debug_info0x0ECOM_PING测试连通性mysql_ping0x0FCOM_TIME(内部线程状态)(无)0x10COM_DELAYED_INSERT(内部线程状态)(无)0x11COM_CHANGE_USER重新登陆(不断连接)mysql_change_user0x12COM_BINLOG_DUMP获取二进制日志信息(无)0x13COM_TABLE_DUMP获取数据表结构信息(无)0x14COM_CONNECT_OUT(内部线程状态)(无)0x15COM_REGISTER_SLAVE从服务器向主服务器进行注册(无)0x16COM_STMT_PREPARE预处理SQL语句mysql_stmt_prepare0x17COM_STMT_EXECUTE执行预处理语句mysql_stmt_execute0x18COM_STMT_SEND_LONG_DATA发送BLOB类型的数据mysql_stmt_send_long_data0x19COM_STMT_CLOSE销毁预处理语句mysql_stmt_close0x1ACOM_STMT_RESET清除预处理语句参数缓存mysql_stmt_reset0x1BCOM_SET_OPTION设置语句选项mysql_set_server_option0x1CCOM_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_GRANT0x02: REFRESH_LOG0x04: REFRESH_TABLES0x08: REFRESH_HOSTS0x10: REFRESH_STATUS0x20: REFRESH_THREADS0x40: REFRESH_SLAVE0x80: REFRESH_MASTERCOM_SHUTDOWN 消息报文功能停止MySQL服务。执行mysqladmin shutdown命令时发送该消息。字节说明1停止服务选项0x00: SHUTDOWN_DEFAULT0x01: SHUTDOWN_WAIT_CONNECTIONS0x02: SHUTDOWN_WAIT_TRANSACTIONS0x08: SHUTDOWN_WAIT_UPDATES0x10: SHUTDOWN_WAIT_ALL_BUFFERS0x11: SHUTDOWN_WAIT_CRITICAL_BUFFERS0xFE: KILL_QUERY0xFF: KILL_CONNECTIONCOM_STATISTICS 消息报文功能查看MySQL服务的统计信息(例如运行时间、每秒查询次数等)。执行mysqladmin status命令时发送该消息无参数。COM_PROCESS_INFO 消息报文功能获取当前活动的线程(连接)列表。等同于SQL语句SHOW PROCESSLIST或是执行mysqladmin processlist命令时发送该消息无参数。COM_PROCESS_KILL 消息报文功能要求服务器中断某个连接。等同于SQL语句KILL。字节说明4连接ID号(小字节序)COM_DEBUG 消息报文功能要求服务器将调试信息保存下来保存的信息多少依赖于编译选项设置(debugno|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(长度121字节)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_CURSOR0x01: CURSOR_TYPE_READ_ONLY0x02: CURSOR_TYPE_FOR_UPDATE0x04: CURSOR_TYPE_SCROLLABLE4保留(值恒为0x01)如果参数数量大于0n空位图(Null-Bitmap长度 (参数数量 7) / 8 字节)1参数分隔标志如果参数分隔标志值为1n每个参数的类型值(长度 参数数量 * 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_ONMYSQL_OPTION_MULTI_STATEMENTS_OFF字节说明2选项值(小字节序)COM_FETCH_STMT 消息报文功能获取预处理语句的执行结果(一次可以获取多行数据)。字节说明4预处理语句的ID值(小字节序)4数据的行数(小字节序)服务器响应报文(服务器 - 客户端)当客户端发起认证请求或命令请求后服务器会返回相应的执行结果给客户端。客户端在收到响应报文后需要首先检查第1个字节的值来区分响应报文的类型。响应报文类型第1个字节取值范围OK 响应报文0x00Error 响应报文0xFFResult Set 报文0x01 - 0xFAField 报文0x01 - 0xFARow Data 报文0x01 - 0xFAEOF 报文0xFE注响应报文的第1个字节在不同类型中含义不同比如在OK报文中该字节并没有实际意义值恒为0x00而在Result Set报文中该字节又是长度编码的二进制数据结构(Length Coded Binary)中的第1字节。响应报文客户端的命令执行正确时服务器会返回OK响应报文。MySQL 4.0 及之前的版本字节说明1OK报文值恒为0x001-9受影响行数(Length Coded Binary)1-9索引ID值(Length Coded Binary)2服务器状态n服务器消息(字符串到达消息尾部时结束无结束符)MySQL 4.1 及之后的版本字节说明1OK报文值恒为0x001-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报文值恒为0xFF2错误编号(小字节序)n服务器消息MySQL 4.1 及之后的版本字节说明1Error报文值恒为0xFF2错误编号(小字节序)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_DECIMAL0x01FIELD_TYPE_TINY0x02FIELD_TYPE_SHORT0x03FIELD_TYPE_LONG0x04FIELD_TYPE_FLOAT0x05FIELD_TYPE_DOUBLE0x06FIELD_TYPE_NULL0x07FIELD_TYPE_TIMESTAMP0x08FIELD_TYPE_LONGLONG0x09FIELD_TYPE_INT240x0AFIELD_TYPE_DATE0x0BFIELD_TYPE_TIME0x0CFIELD_TYPE_DATETIME0x0DFIELD_TYPE_YEAR0x0EFIELD_TYPE_NEWDATE0x0FFIELD_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_ENUM0xF8FIELD_TYPE_SET0xF9FIELD_TYPE_TINY_BLOB0xFAFIELD_TYPE_MEDIUM_BLOB0xFBFIELD_TYPE_LONG_BLOB0xFCFIELD_TYPE_BLOB0xFDFIELD_TYPE_VAR_STRING0xFEFIELD_TYPE_STRING0xFFFIELD_TYPE_GEOMETRY列(字段)标志各标志位定义如下(参考源代码/include/mysql_com.h头文件中的宏定义)标志位名称0x0001NOT_NULL_FLAG0x0002PRI_KEY_FLAG0x0004UNIQUE_KEY_FLAG0x0008MULTIPLE_KEY_FLAG0x0010BLOB_FLAG0x0020UNSIGNED_FLAG0x0040ZEROFILL_FLAG0x0080BINARY_FLAG0x0100ENUM_FLAG0x0200AUTO_INCREMENT_FLAG0x0400TIMESTAMP_FLAG0x0800SET_FLAG数值精度该字段对DECIMAL和NUMERIC类型的数值字段有效用于标识数值的精度(小数点位置)。默认值该字段用在数据表定义中普通的查询结果中不会出现。附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报文值为0x004预处理语句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, 字符串的长度保存在字符串的前面■ length251 1 byte■ length 256^2 3 byte(第一个byte是252)■ length256^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_connectauth packet● 该协议是从客户端对密码使用scramble加密后发送到服务端● 其中databasename是可选的.salt就是加密后的密码.● sql_client.c:mysql_real_connect● sql_connect.c:check_connectionok packet● ok包,命令和insert,update,delete的返回结果● 包体首字节为0.● insert_id, affect_rows也是一并发过来.● src/protocol.cc:net_send_okerror packet● 错误的命令,非法的sql的返回包● 包体首字节为255.● error code就是CR_***,include/errmsg.h ● sqlstate marker是#● sqlstate是错误状态,include/sql_state.h● message是错误的信息● sql/protocol.cc:net_send_error_packetresultset 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_resultresultset packet - eof● eof包是用于分割field packet和row packet.● 包体首字节为254● sql/protocol.cc:net_send_eofresultset packet - row● row packet里才是真正的数据包.一行数据一个packet.● row里的每个字段都是length coded binary● 字段的个数在header packet里● sql/client.c:cli_read_rowscommand 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_readNET缓冲● 每次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-dataPACKET 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(pkt0)之后自增为1,然后等待对端发送过来pkt1的包查询每次查询,服务客户端都会对net-pkt_nr进行清零include/mysql_com.h #define net_new_transaction(net) ((net)-pkt_nr0)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长度的包体。伪代码如下remain4for(i 0; i 2; i) { //数据是否读完 while (remain0) { length read(fd, net-buff, remain) remain remain - length } //第一次 if (i0) { remain uint3korr(net-buffnet-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_rowtmp);mysql_free_result是把result-data指定的行数据释放掉.大部分参考http://hutaow.com/blog/2013/11/06/mysql-protocol-analysis/重磅福利微信搜一搜【冰河技术】微信公众号关注这个有深度的程序员每天阅读超硬核技术干货公众号内回复【PDF】有我准备的一线大厂面试资料和我原创的超硬核PDF技术文档以及我为大家精心准备的多套简历模板(不断更新中)希望大家都能找到心仪的工作学习是一条时而郁郁寡欢时而开怀大笑的路加油。如果你通过努力成功进入到了心仪的公司一定不要懈怠放松职场成长和新技术学习一样不进则退。如果有幸我们江湖再见另外我开源的各个PDF后续我都会持续更新和维护感谢大家长期以来对冰河的支持写在最后如果你觉得冰河写的还不错请微信搜索并关注「 冰河技术 」微信公众号跟冰河学习高并发、分布式、微服务、大数据、互联网和云原生技术「 冰河技术 」微信公众号更新了大量技术专题每一篇技术文章干货满满不少读者已经通过阅读「 冰河技术 」微信公众号文章吊打面试官成功跳槽到大厂也有不少读者实现了技术上的飞跃成为公司的技术骨干如果你也想像他们一样提升自己的能力实现技术能力的飞跃进大厂升职加薪那就关注「 冰河技术 」微信公众号吧每天更新超硬核技术干货让你对如何提升技术能力不再迷茫留言区