本文是 MySQL 简单查询语句执行过程分析
6 篇中的第 5 篇,第 1 ~ 4 篇请看这里:
[1. 词法分析 & 语法分析]
[2. 查询准备阶段]
[3. 从 InnoDB 读数据]
[4. WHERE 条件]
经过前面几篇文章的讲述之后,终于来到了发送数据阶段,今天我们一起来看看 server 层读取到一条记录之后,发送给客户端之前都做了些什么?
对于 select 语句,MySQL 在执行过程中会把字段信息、数据记录发送给客户端,这两部分是分开发送的。完成查询优化
之后,从存储引擎读取第一条记录之前,会先把 select 语句中的字段信息
发送给客户端。然后从存储引擎读取记录,每读取一条记录,都会把该记录发送给客户端,然后再读取下一条记录并发送给客户端。
MySQL 发送字段信息和数据记录,根据发送内容的长度,有可能直接发送给客户端,也有可能是先写入网络缓冲区,等缓冲区满再一次性发送给客户端,本文中我们先不区分这两种不同的情况,都统一描述为
发送给客户端
。关于网络缓冲区,我们将在下一篇介绍。
接下来我们详细说说发送字段信息、数据记录这两部分内容。
示例表如下:
CREATE TABLE `t_recbuf` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`i1` int(10) unsigned DEFAULT '0',
`str1` varchar(32) DEFAULT '',
`str2` varchar(255) DEFAULT '',
`c1` char(11) DEFAULT '',
`e1` enum('北京','上海','广州','深圳','天津','杭州','成都','重庆','苏州','南京','哈尔滨','沈阳','长春','厦门','福州','南昌','泉州','德清','长沙','武汉') DEFAULT '北京',
`s1` set('吃','喝','玩','乐','衣','食','住','行','前后','左右','上下','里外','远近','长短','黑白','水星','金星','地球','火星','木星','土星','天王星','海王星','冥王星') DEFAULT '',
`bit1` bit(8) DEFAULT b'0',
`bit2` bit(17) DEFAULT b'0',
`blob1` blob,
`d1` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2001 DEFAULT CHARSET=utf8;
MySQL 只会把客户端需要的那些字段的字段信息发送给它,那么,MySQL 怎么知道要发送哪些字段的字段信息呢?
这个对于 MySQL 来说是小意思,select 中指定的字段,保存在一个叫 fields
的属性里,发送字段信息时,会遍历这个属性中的每一个字段,把字段信息发送给客户端。
开始发送字段信息之前,会先把字段数量
发送给客户端。
然后把每个字段的以下信息发送给客户端:
字段定义
)字段信息发送完成之后,会发送结束包
给客户端,长度 5 字节,表示的是服务器的状态。
MySQL 发送数据记录时,也只会发送客户端需要的那些字段的内容。依然要用到那个叫 fields
的属性,遍历 fields 中的每个字段,把字段的内容转换为字符串
,以及进行一些必要的逻辑处理之后,发送给客户端。
接下来,我们就来看看示例表中每种类型的字段怎么转换为字符串,以及还有可能涉及到哪些逻辑要处理?
id、i1 字段都是 int 类型,字段值会由整数转换为字符串,因为指定了是无符号整数(unsigned
),转换为字符串之后,数字前面不会有符号(+、- 都不会有)。
如果建表时,int 字段指定了 zerofill
,把整数转换为字符串之后,会在前面用 0
把字符串填充到指定长度,如 id 定义为 int(10), 假设字段值为 24,转换为字符串之后,会在前面补充 10 - 2 = 8 个 0,变成 0000000024
。
经过把整数转换为字符串,以及可能需要操作的在字符串前面补充 0
之后,内容就准备就绪了,然后把字符串长度
和内容
发送给客户端。
str1、str2 字段都是 varchar 类型,本身就是字符串,不需要转换。str1 和 str2 唯一区别就是 str1 用 1 字节存储内容长度,str2 用 2 字节存储内容长度。
发送数据前,只需要先读取字段内容的长度
(字节数),再读取相应字节数的内容,然后把长度
和内容
发送给客户端。
c1 字段是 char 类型,本身就是字符串,不需要转换。
char 类型字段,取决于 sql_mode 是否开启了 PAD_CHAR_TO_FULL_LENGTH
选项,发送给客户端的内容会有些差别。
c1 字段的定义是 char(11),我们以 c1 字段的内容 24 测试char
为例来说明。
如果开启了 PAD_CHAR_TO_FULL_LENGTH
选项,字符串内容后面会填充相应数量的空格,使内容中的字符数达到字段定义时的数量,24 测试char
有 9 个字符,会在后面填充 11 - 9 = 2 个空格,变成 24 测试char__
,注意:2 个下划线代表 2 个空格。
如果 sql_mode 没有开启 PAD_CHAR_TO_FULL_LENGTH
选项,字符串内容后面不会有空格,就是这样的了:24 测试char
,注意:最后没有空格。
上面处理完成后,就可以愉快的把字段内容长度
和字段内容
发送给客户端了。
enum 类型字段,在存储引擎中以整数存储,发送数据之前,会找到整数对应选项的内容作为字段内容
,如果没有找到对应的选项,字段内容就是:空字符串
。
示例表中 e1 字段各选项及其对应的整数值如下图:
假设存储引擎返回的 e1 字段整数值为 7
,发送数据之前,会找到 7 对应选项的内容成都
,占用字节数为 2(字数)* 3(utf8 一个汉字占用字节数)= 6 字节,把内容长度 6
和内容成都
发送给客户端。
set 类型字段,在存储引擎中也是以整数存储,发送数据之前,通过整数找到一个或多个对应选项的内容作为字段内容
,如果有多个选项,字段内容中多个选项的内容之间用逗号分隔。
示例表中 s1 字段定义时指定的选项为:吃, 喝, 玩, 乐, 衣, 食, 住, 行, 前后, 左右, 上下, 里外, 远近, 长短, 黑白, 水星, 金星, 地球, 火星, 木星, 土星, 天王星, 海王星, 冥王星,共 24 个选项,每个选项占用 1 bit。
假设存储引擎返回的 s1 字段整数值为 2163720
,遍历 s1 字段的 24 个选项,判断 2163720 中每一个选项对应的 bit 是否为 1
,如果为 1,则把该选项内容(如天王星
)追加到 s1 字符串值的后面,用逗号分隔,最终会得到字段内容乐,上下,金星,天王星
,然后把字段内容长度 27
和字段内容乐,上下,金星,天王星
发送给客户端。
一个汉字占 3 字节,一个逗号(,)占 1 字节。
为什么 2163720 转换为字符串是乐,上下,金星,天王星
?
乐
是第 4 个选项,序号为 3,值为 1 << 3 = 8。上下
是第 11 个选项,序号为 10,值为 1 << 10 = 1024。金星
是第 17 个选项,序号为 16,值为 1 << 16 = 65536。天王星
是第 22 个选项,序号为 21,值为 1 << 21 = 2097152。2163720 由 4 个选项按位或
计算得到:8 | 1024 | 65536 | 2097152 = 2163720,二进制示意图如下:
bit 类型字段,发送数据前,也要转换为字符串,就是我们熟悉的那种只包含 0 和 1 的二进制内容。
bit 类型字段在 InnoDB 中是以 C/C++ 中的 char 类型存储的,实际就是按字节存储,1 字节可以存储 8 bit,示例表结构中,bit1 定义为 bit(8) 正好占用 1 字节,bit2 定义为 bit(17),用 2 字节不够存,多出来 1 bit 也需要占用 1 字节,所以需要 3 字节来存储。
存储引擎返回 bit 类型字段给 server 层时,就是以 C/C++ 中的 char *
指针返回的。
例如,某条记录的 bit1 字段值转换为字符串 00010001
,长度为 8 字节,把长度 8
和内容 00010001
发送给客户端。bit2 字段值转换为字符串 00000000000011111
,长度为 17 字节,把长度 17
和内容 00000000000011111
发送给客户端。
blob 类型字段,本身就是字符串,不需要转换。
blob 类型字段有个和其它字段不一样的地方,存储引擎返回记录给 server 层时,并没有把 blob 字段内容写入记录缓冲区
,而是在引擎层分配了一块内存用于存储 blob 字段内容,并把内容的内存首地址
写入记录缓冲区,发送数据时,根据内容的内存首地址
直接从引擎层存储 blob 字段内容的内存区域中读取字段内容,把字段容内容长度
和字段内容
发送给客户端。
关于 server 层和引擎层交换内容的
记录缓冲区
,可以参考这篇文章:[MySQL server 层和存储引擎层是怎么交互数据的?]
decimal 类型在存储引擎中是以二进制存储的,发送数据之前,会先把二进制转换为浮点数,然后再把浮点数转换为字符串,把字符串内容长度
和字符串内容
发送给客户端。
以上,就是本文的全部内容了,感谢大家花时间阅读,如果觉得有用,还请帮忙转发朋友圈,让更多的人看到,大家一起进步,谢谢 ^_^
预告一下,下一篇要写的内容是 MySQL 简单查询语句执行过程分析(六)网络缓冲区
,敬请关注!
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8