深入了解MySQL文件排序
数据准备
CREATE TABLE `user_info` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',`name` varchar(20) NOT NULL COMMENT '用户名',`age` tinyint(4) NOT NULL DEFAULT '0' COMMENT '年龄',`sex` tinyint(2) NOT NULL DEFAULT '0' COMMENT '状态 0:男 1: 女',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`udpate_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`),KEY `idx_name` (`name`) COMMENT 'name索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户信息'
测试执行SQL
1)、查询字段为索引(索引覆盖查询),同时排序字段完全包含查询字段
EXPLAIN
select name from user_info where name like '测试用户4%' order by name;
执行流程如下:
2)、查询字段为部分索引字段部分普通字段,同时排序字段未完全包含查询字段
EXPLAIN
select name, age, sex from user_info where name like '测试用户4%' order by name;
执行流程如下:
mysql排序中有两个很关键的变量:sort_buffer_size 和 max_length_for_sort_data
sort_buffer_size:MySQL为排序准备的一块缓存区内存
- 如果排序的数据量小于sort_buffer_size,则直接在内存完成排序
- 如果排序的数据量大于sort_buffer_size,则会借助磁盘临时文件完成排序(每满一次生成一个临时文件数据排序完刷入磁盘,通过归并排序将所有文件排序完合并为一个返回)
max_length_for_sort_data:控制哪些字段进入排序缓冲区。
- 如果排序字段长度小于max_length_for_sort_data,进行全字段排序
- 如果排序字段长度大于max_length_for_sort_data,进行rowid字段排序
例如执行如下SQL:
select name, age, sex from user_info where name like '测试用户4%' order by name;
字段 name、age、sex对应类型及长度:varchar(20)、tinyint(4)、tinyint(2);nage占用22个字节(20+2[记录变长字段的长度]),age和sex各占1个字节,总是就是22+1+1=24个字节,远远小于max_length_for_sort_data(1024),这时候name、age、sex数据都将进入排序缓冲区,这就是人们常说的全字段排序。
但是此时如果将max_length_for_sort_data改为20,那么只会将主键ID和排序字段name放入缓冲区,排序后通过再次反查聚簇索引树获取所需要字段值(age、sex),这种就是常说的rowid排序。
二者的区别是:一个是空间换时间、一个时间换空间。
注意: 如果单个排序字段就已经超过max_length_for_sort_data,mysql将会根据max_sort_length对排序字段内容进行截断,将会导致排序结果可能不是预期。
MySQL 原则:如果内存够,就多利用内存,尽量减少磁盘访问。对于 InnoDB 表来说,rowid 排序会进行回表造成磁盘读
接下来从更底层了解MySQL文件排序:
参考:百度安全验证
1、整体概览
执行流程说明:
- server 层从存储引擎读取符合 where 条件的记录,写入一个专门存放待排序记录的内存区域,这个内存区域叫做
排序缓冲区(sort buffer)
。 - 排序缓冲区写满之后,会对缓冲区中的记录进行排序,排好序的记录组成一个
数据块
,数据块包含Merge_chunk
和数据记录
两部分,Merge_chunk 写入一个磁盘文件(chunk_file),数据记录写入另一个磁盘文件(temp_file)。Merge_chunk 中保存着数据记录
在磁盘文件(temp_file)中的起始位置、记录数量等信息。 - Merge_chunk 和数据记录写入磁盘文件之后,排序缓冲区中的数据被清空,然后就可以写入其它待排序记录了,再次写满之后,缓冲区中的记录同样会进行排序,组成一个数据块,并把 Merge_chunk 和数据记录分别写入磁盘文件 chunk_file 和 temp_file。
- 读完符合 where 条件的所有记录之后,可能会生成多个数据块,对数据块进行 7 路归并排序,把 7 个小数据块合并成大数据块,直到合并得到的大数据块数量小于等于 15 个(
如果归并排序之前,数据块数量本身就小于等于 15 个,此步骤跳过
)。 - 最后,通过多路归并排序把小于等于 15 个数据块合并为最终的排序结果,写入磁盘文件(out_file)。
以上就是 MySQL 文件排序实现过程的整体概览,有了这个基础,我们就能够进一步展开其中的细节了。
2、 排序缓冲区(sort buffer)
排序缓冲区是内存
缓冲区,用于存放待排序记录。对于服务器来说,内存是稀缺资源,既然排序缓冲区在内存中,其大小必然要受到限制,系统变量 sort_buffer_size
就是用于控制排序缓冲区大小的。
sort_buffer_size 默认值为 256K,最小可以设置为 32K,最大可以设置为 4G。
3、单个排序字段太长了怎么办?
order by 子句中,可能会包含一个或多个排序字段,排序字段可以是 int、char、varchar、blob 等各种类型,假设有个字段是这么定义的:a varchar(21845)
,utf8 字符集下,字段内容最大可以达到 65535 字节,将近 64K。
排序缓冲区的默认大小为 256K,如果以这样一个字段作为排序字段,就算每条记录只把这一个字段写入到排序缓冲区,写入 4 条记录缓冲区就满了,在记录数量很多情况下,就意味着大量的数据块
要写入到磁盘文件,而大量的磁盘 IO 会导致整个排序过程耗时增加,由此可见,排序内容长度也必须受到限制。
max_sort_length
就是用于控制单个字段排序内容长度的,默认值为 1024 字节,最小可以设置为 4 字节,最大可以设置为 8M。
如果单个排序字段内容长度大于 max_sort_length,只有前 max_sort_length 字节的内容会参与排序,以 max_sort_length = 1024 字节
为例,对于单个排序字段内容长度超过 1024 字节的多条记录,如果前 1024 字节的内容相同,1025 字节及之后的内容不相同,会导致 MySQL 认为记录的排序字段内容一样,从而出现排序结果和预期不一致的情况。
4、排序模式(sort mode)
排序模式是官方的叫法,实际上就是排序缓冲区或磁盘文件中会写入哪些内容
。排序模式一共有 3 种:
- <sort_key, additional_fields>
- <sort_key, packed_additional_fields>
- <sort_key, rowid>
这 3 种排序模式的共同点,都会把排序字段(sort_key)写入排序缓冲区或磁盘文件,排序字段可能是一个,也可能是多个。
4.1 <sort_key, additional_fields>
<sort_key, additional_fields> 表示排序缓冲区或磁盘文件中,除了要写入排序字段(sort_key),还要写入存储引擎返回给 server 层的所有字段(additional_fields):
additional_fields 没有描述为select 子句中的所有字段
,而是用了更长的描述存储引擎返回给 server 层的所有字段
,是因为两者并不完全一样,本文后面出现相关的场景时,也都使用后者来描述。
上图是写入到排序缓冲区中记录的示意图,以下对各个部分进行说明:
- 排序字段(sort_key):排序字段内容,会进行编码以节省存储空间,可能包含一个或多个排序字段。
- 字段 NULL 标记区域:创建表时,没有指定 NOT NULL 的字段,在这个区域会有 1 bit 用于标记记录中该字段内容是否为 NULL。
- char 长度:char 字段长度,占用 1 字节或 2 字节。
<sort_key, packed_additional_fields> 排序模式中,为了节省空间,只写入 char 字段实际内容到排序缓冲区,所以需要记录字段内容长度。为了逻辑统一,<sort_key, additional_fields> 排序模式中也会写入 char 字段长度和内容到排序缓冲区。 - char 内容:char 字段内容,以最大长度存储,
不会去掉内容尾部的空格
。 - varchar 长度:varchar 字段内容长度,占用 1 字节或 2 字节。
- varchar 内容:varchar 字段内容,占用空间为字段最大长度。
如果字段实际内容长度小于定义的最大长度,剩余空间留空
。 - 除 blob 类型之外的其它字段:指是的除 tinyblob、mediumblob、blob、longblob、tinytext、mediumtext、text、longtext、json、geometry 之外的其它类型字段,只需要把字段内容写入排序缓冲区,不需要写入字段长度。
tinyblob、mediumblob、blob、longblob、tinytext、mediumtext、text、longtext、json、geometry 都是基于 blob 类型实现的,而 select 子句中包含 blob 类型字段时,不能使用 <sort_key, additional_fields>、<sort_key, packed_additional_fields> 排序模式。 - 重点说明: 如果某个字段内容为 NULL,该字段在
字段 NULL 标记区域
对应的 NULL 标记位设置为 1,同时,该字段在排序缓冲区中还会占用存储空间,占用空间大小为字段最大长度。
<sort_key, additional_fields> 排序模式的好处,只需要从存储引擎读取一次数据,排序缓冲区或排序结果的最终文件(out_file)存放的就是已经排好序的所有记录,读取其中需要的字段返回给客户端就可以了,这是以空间换时间的方式。
如果排序缓冲区不够存储符合 where 条件的所有待排序记录,就需要把缓冲区中的记录排好序之后写入磁盘文件,虽然磁盘相比内存来说,空间可以大很多,但是磁盘 IO 相比内存访问效率低下。<sort_key, additional_fields> 排序模式虽然实现起来简单方便,但也会导致排序缓冲区只能存放更少的待排序记录、需要更多的磁盘 IO、占用更多的磁盘空间,所以,其使用会有所限制。
使用 <sort_key, additional_fields> 需要满足以下条件:
- 存储引擎返回给 server 层的字段中不能包含 blob 类型字段,因为 blob 字段一般都是用于存储比较长的内容。
- 排序字段(sort_key)长度之和
+
additional_fields 所有字段最大长度之和必须小于等于
系统变量max_length_for_sort_data
的值。
如果不满足上面两个条件,会使用 <sort_key, rowid> 排序模式,后面会讲述这种模式。
max_length_for_sort_data 默认值为 1024 字节,最小可设置为 4 字节,最大可设置为 8M。
<sort_key, additional_fields> 的优点是简单方便,它也有缺点,additional_fields 所有字段在排序缓冲区或磁盘文件中都是按照字段最大占用字节数来分配空间的,在以下两种场景中会存在空间浪费:
- 字段内容为 NULL,以 utf8 字符集的 varchar 字段为例,假设有字段
a varchar(21845)
,当字段 a 的内容为 NULL 时,它在排序缓冲区或磁盘文件中占用的空间也是 21845 * 3 = 65535 字节,对于 int、float 等其它字段也一样,都存在空间浪费。 - char、varchar 类型字段,字段内容实际占用空间小于最大长度,还是以上面的字段 a 为例,如果字段内容为
文件排序是怎么实现的
,只需要10(字符数)* 3 = 30 字节
就能够存放,但实际占用空间依然是 65535 字节。
为了解决这种排序模式浪费空间的问题,引入了另一种排序模式 <sort_key, packed_additional_fields>
。
4.2 <sort_key, packed_additional_fields>
<sort_key, packed_additional_fields> 表示排序缓冲区或磁盘文件中,除了要存入排序字段(sort_key),还要存入存储引擎返回给 server 层的所有字段(packed_additional_fields),并且会尽可能使用最少的空间存放待排序记录。
字段内容为 NULL 时,除 1 bit 的 NULL 标记位之外,字段在排序缓冲区不占用额外存储空间;char、varchar 类型字段内容长度小于字段最大长度时,字段在排序缓冲区中只占用实际内容长度大小的空间,不会像 <sort_key, additional_fields> 排序模式一样每个字段都占用字段最大长度大小的空间。
上图是写入到排序缓冲区中记录的示意图,以下对各个部分进行说明:
- 排序字段(sort_key):排序字段内容,会进行编码以节省存储空间,可能包含一个或多个排序字段。
- 记录长度:存储排序缓冲区的记录中,除
排序字段(sort_key)
之外的长度,也就是记录长度 ~ 除 blob 类型之外的其它字段
的长度。 - 字段 NULL 标记区域:创建表时,没有指定 NOT NULL 的字段,在这个区域会有 1 bit 用于存储记录中该字段内容是否为 NULL。
- char 长度:char 字段长度,占用 1 字节或 2 字节。为了节省空间,只写入 char 字段实际内容到排序缓冲区,所以需要记录字段内容长度。
- char 内容:char 字段实际内容,以实际长度存储,
会去掉内容尾部的空格
。 - varchar 长度:varchar 字段内容长度,占用 1 字节或 2 字节。
- varchar 内容:varchar 字段实际内容。
- 除 blob 类型之外的其它字段:指是的除 tinyblob、mediumblob、blob、longblob、tinytext、mediumtext、text、longtext、json、geometry 之外的其它类型,只需要把字段内容写入排序缓冲区,不需要写入字段长度。
- 重点说明: 如果某个字段内容为 NULL,该字段在
字段 NULL 标记区域
对应的 NULL 标记位设置为 1,字段不会占用排序缓冲区的额外空间。
<sort_key, packed_additional_fields> 是以 <sort_key, additional_fields> 为基础的,如果不满足使用 <sort_key, additional_fields> 的条件,也不会使用 <sort_key, packed_additional_fields>。
使用 <sort_key, packed_additional_fields> 排序模式,还需要满足以下条件:
- packed_additional_fields 所有字段最大长度之和必须
小于等于 65535 字节
。
为什么是 65535 字节?
因为只写入字段实际内容到排序缓冲区或磁盘文件,不同记录长度可能会不一样,这就需要把每条记录的长度记下来,MySQL 用 2 个字节来保存记录长度,而 2 字节无符号整数能够表示的最大数字为 65535。 - 写入排序缓冲区或磁盘文件的一条记录长度必须
小于等于
系统变量max_length_for_sort_data
的值,记录长度 = 排序字段(sort_key)长度之和 + 存储记录长度的 2 字节 + packed_additional_fields 所有字段最大长度之和。 - 可压缩字段最大长度之和必须大于 12 字节,如果可节省的空间太小,也就没必要折腾了。
当查询字段或排序字段为varchar类型时,采用<sort_key, packed_additional_fields>;其它情况采用<sort_key, additional_fields>。这地方有个varchar长字段问题,例如 name varchar(20)和 name varchar(200).
图示1:
select name from user_info_varchar20 order by age LIMIT 999995, 5;select name from user_info_varchar200 order by age LIMIT 999995, 5;
图示:2:
select name,sex from user_info_varchar20 order by age LIMIT 999995, 5;select name,sex from user_info_varchar200 order by age LIMIT 999995, 5;
图示3:
select name,sex from user_info_varchar20 order by name LIMIT 999995, 5;select name,sex from user_info_varchar200 order by name LIMIT 999995, 5;
图示1与图示2:执行后varchar(20)和varchar(200)性能基本一样,产生少量数据快。
图示3:varchar(200)比varchar(20)产生更多数据块,性能也更差。
操作都是在UTF-8字符下(show charset查看字符集和长度),对比一下二者缓存大小,差不多正好差180个长度的字节(180*3*100w[数据量]),所以怀疑sort_key用的是字段定义长度而不是内容实际长度(需以后进一步验证)。
前面我们讲述了 <sort_key, additional_fields>、<sort_key, packed_additional_fields> 的优缺点及使用限制,如果这两种排序模式都不能使用怎么办?
别急,该轮到 <sort_key, rowid> 排序模式出场了,MySQL 刚出道时采用的就是这种排序模式。
4.3 <sort_key, rowid>
<sort_key, rowid> 表示排序缓冲区或磁盘文件中,除了要存入排序字段(sort_key),还要存入记录的主键 ID(rowid)。
上图是写入到排序缓冲区中记录的示意图,相比其它两种排序模式来说,非常简单,不做过多说明了。
想必大家应该发现了 <sort_key, rowid> 和 <sort_key, additional_fields>、<sort_key, packed_additional_fields> 的区别了,使用 <sort_key, rowid> 时,排序缓冲区或磁盘文件中只包含了记录的主键 ID,而客户端可能需要除主键之外的字段,怎么办?
这就要二次访问存储引擎了,第一次从存储引擎拿到符合 where 条件的所有记录的主键 ID
,第二次根据主键 ID 从存储引擎一条一条读取记录,得到客户端需要的所有字段。
使用 <sort_key, rowid> 读取数据的过程,有点类似根据 ID 批量从 redis 获取详细数据的过程,先从某个 redis key 读取多个 ID,然后根据 ID 读取缓存的详细数据。
这种排序模式的好处是记录数量不太多的情况下,使用排序缓冲区就能够存储所有待排序记录了,能够在一定程度上避免使用磁盘文件排序。
举例说明:假设排序字段为 int 类型,主键也为 int 类型,写入排序缓冲区的一条记录占用的空间为 4 + 4 = 8 字节,排序缓冲区的默认大小为 256K,能够存放 32768 条记录,对于一般业务来说,都不会一次性读取这么多记录,由此可见,一般情况下,使用 <sort_key, rowid> 排序模式不会用到磁盘文件排序。
但是,凡事都有例外,如果一条 SQL 语句读取过多的记录,哪怕是使用 <sort_key, rowid>,当排序缓冲区满时,也需要把缓冲区中的记录排好序组成一个数据块
,写入磁盘文件,这样一来,即要使用磁盘文件,又要二次访问存储引擎,执行效率就有点惨不忍睹了。
细心的小伙伴可能发现了一点情况,到现在为止讲的三种排序模式,都是把符合 where 条件的所有记录
写入排序缓冲区或磁盘文件,那还有优化空间吗?
MySQL 为了提升性能,想了各种办法,使用了各种技巧,对于上面提到的这种情况,自然也还可以再优化,我们一起来看看吧。
5、提升排序效率
5.1 优先队列
通过前面的讲述,我们已经知道了,MySQL 会把符合 where 条件的所有记录写入排序缓冲区,当缓冲区满时,缓冲区中的记录排好序后组成一个数据块
,写入到磁盘文件(temp_file),而磁盘 IO 相比内存访问效率低下,优先队列就是为了在文件排序过程中避免使用磁盘文件
的一种优化方案。也就是说,使用了优先队列就不会使用磁盘文件排序。
优先队列在内存中维护待排序记录的队列,队列中的元素不会实际存储记录内容,而是存储指向排序缓冲区中记录的地址,因为要保证不使用磁盘文件,使用优先队列提升排序效率的必要条件
是:SQL 语句必须包含 limit
。
在满足必要条件的基础上,会评估使用优先队列进行排序是否更快,以决定是否使用。
使用优先队列提升排序效率,借鉴的是大顶堆
的思路,排序缓冲区中只需要保留待排序记录中最大或最小的 limit 条记录
(取决于正序还是倒序排序),而不需要存放所有待排序记录。基于前面的描述可以知道,使用优先队列的优势有两个:
- 不使用磁盘文件排序,避免磁盘 IO。
- 只需要对一部分待排序记录中进行排序。
如果排序缓冲区不能
存放所有待排序记录,就意味着需要借助磁盘文件排序,使用优先队列无疑是更好的选择,这种情况下,只要排序缓冲区中能够存放 limit + 1 条记录,就会使用优先队列。
limit + 1 中的 1
是优先队列需要使用的额外的一条记录的空间。
如果排序缓冲区能够存放
所有待排序记录,本身就不需要使用磁盘文件进行排序,使用优先队列的优势就剩下一个了:只需要对一部分待排序记录中进行排序。官方根据单元测试发现,使用优先队列 + 排序缓冲区
进行排序需要的时间是只使用排序缓冲区
的 3 倍
。因此,当 limit 数量小于待排序记录数量的三分之一时,使用优先队列 + 排序缓冲区
比只使用排序缓冲区
排序更快,才会使用优先队列来提升排序效率。
优先级队列部分源代码:https://github.com/mysql/mysql-server/blob/5.7/sql/filesort.h
经过前面一系列的成本评估之后,如果还是不能使用优先队列,MySQL 会进行最后一次尝试,这就是最后一哆嗦了:如果使用的排序模式是 <sort_key, additional_fields>,可能会造成需要使用磁盘文件排序,此时会判断使用 <sort_key, rowid> + 优先队列
的成本是否比使用 <sort_key, additional_fields> + 磁盘文件排序
的成本更低。。
如果使用<sort_key, additional_fields> + 磁盘文件排序
成本更低,那么还是保持原样,依然使用 <sort_key, additional_fields> 排序模式。
如果使用<sort_key, rowid> + 优先队列
成本更低,并且排序缓冲区中能够存放 limit + 1 记录的排序字段(sort_key)和主键 ID,使用的排序模式会由 <sort_key, additional_fields> 修改为 <sort_key, rowid>,并且使用优先队列来减少实际参与排序的记录数量,提升排序效率。
前面提到的 limit
并不是 SQL 语句中 limit 后面的数字,而是 SQL 语句中的 offset + limit。想要了解 MySQL 中 limit 是怎么实现的,可以参考这篇文章:MySQL 查询语句的 limit, offset 是怎么实现的?
看到这里,可能有的小伙伴会有疑问,排序模式 <sort_key, additional_fields> 是不是和优先队列两者不能共存?
<sort_key, additional_fields> 和优先队列是可以共存的,只有当使用 <sort_key, additional_fields> 排序模式导致排序缓冲区不能存放所有待排序记录,要借助磁盘文件实现排序时,如果改为使用<sort_key, rowid> + 优先队列
实现排序成本更低,才会把 <sort_key, additional_fields> 修改为 <sort_key, rowid>,并且使用优先队列提升排序效率。
最后补充一个官网的例子:
官网limit优化地址:MySQL :: MySQL 8.0 Reference Manual :: 10.2.1.19 LIMIT Query Optimization
准备数据:
/* 创建表 */
CREATE TABLE `ratings` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`category` tinyint(4) NOT NULL,
`rating` decimal(4,1) NOT NULL,
PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;/* 插入测试数据 */
INSERT INTO `ratings`(`id`, `category`, `rating`) VALUES
(1, 1, 4.5),
(2, 3, 5.0),
(3, 2, 3.7),
(4, 2, 3.5),
(5, 1, 3.2),
(6, 2, 3.5),
(7, 3, 2.7);
执行不带limit的sql:
SELECT * FROM ratings ORDER BY category;
查询优化跟踪器
SET optimizer_trace='enabled=on';
SELECT * FROM ratings ORDER BY category;
SELECT * from information_schema.OPTIMIZER_TRACE;
由于没有limit,所以未使用排序优先级队列( "cause": "not applicable (no LIMIT)")。
执行带limit的sql:
SELECT * FROM ratings ORDER BY category LIMIT 5;
注:结果和官网的稍微有点不一样,id3和id4的位置不一样,本地3在前,官网4在前。
查询优化跟踪器
SET optimizer_trace='enabled=on';
SELECT * FROM ratings ORDER BY category LIMIT 5;
SELECT * from information_schema.OPTIMIZER_TRACE;
通过分析可得知,limit SQL使用了排序优先级队列,limit就是找top N,堆排序。
//TODO 未完待续,通过堆排序推演排序优先级队列
5.2 随机 IO 变为顺序 IO
使用 <sort_key, rowid> 排序模式,从存储引擎读取符合 where 条件的所有记录的主键 ID,按照 sort_key 排序好序之后,需要根据主键 ID 从存储引擎读取记录中的需要返回给客户端的其它字段。按照 sort_key 排好序的记录,在主键索引中不一定是顺序存储的,可能是分散在主键索引的不同叶子节点中,这样一来,通过主键 ID 一条一条去存储引擎读取记录,会造成大量随机 IO,导致从存储引擎读取数据效率低下。
为了尽可能减少随机 IO,MySQL 通过一个专门的内存区域,尽量把随机 IO 变成顺序 IO,这个专门的内存区域为 随机读缓冲区(read_rnd_buffer)
。
缓冲区大小由系统变量 read_rnd_buffer_size
控制,默认大小为 256K,最小为 1 字节,最大为 2G,如果设置为 1 字节,就相当于禁用了这个缓冲区了。
随机 IO 变为顺序 IO 的实现逻辑是这样的:
- 从最终的排序结果磁盘文件(out_file)读取主键 ID,写入
随机读缓冲区
。 - 写满之后,对
随机读缓冲区
中的主键 ID 进行排序。 - 排好序之后,再按照主键 ID 逐条从存储引擎读取记录中需要返回给客户端的其它字段。
这个优化方案基于这样一个假设的前提条件:一批主键 ID,排好序之后逻辑上相邻的主键 ID,其对应的记录更有可能在主键索引的同一个叶子节点中,从存储引擎读取一个主键 ID 对应的记录之后,再读取下一个主键 ID 对应的记录,该记录所在的主键索引叶子节点有可能已经被加载到内存中了,就可以直接从内存中读取记录,从而一定程序上减少随机 IO,提升读取数据的效率。
只有当排序缓冲区存放不下所有记录的 <sort_key, rowid>,需要使用磁盘文件来存储时,才有可能使用随机缓冲区来优化文件排序,然而,这还只是入门门槛,要想使用随机缓冲区,需要满足的其它条件达 9 条之多,涉及到源码细节,就不一一展开了,大家只要知道有这么一种优化方案存在就可以了。
使用 <sort_key, additional_fields>、<sort_key, packed_additional_fields> 排序模式时,不需要使用随机缓冲区来优化文件排序,因为这两种排序模式下,需要返回给客户端的所有字段都已经在排序缓冲区或磁盘文件(out_file)中了,不需要通过主键 ID 二次访问存储引擎读取其它字段。
6、两类排序
MySQL order by 的实现过程,可能会进行两类排序:内部排序、外部排序。
前面多次提到,当排序缓冲区满,会把缓冲区中的记录排好序,组成一个数据块
,然后写入磁盘文件,这里的排序就是内部排序
。
符合 where 条件的所有记录都写入到磁盘文件之后,可能会存在多个已经内部排好序的数据块
,这些数据块需要通过多路归并排序,最终形成全局有序的结果,这里的排序就是外部排序
。
6.1 内部排序
内部排序是对排序缓冲区中的记录进行排序,是内存排序。
为了提升性能,MySQL 做了各种各样的努力,内部排序的实现又一次体现了这种追求极致性能的努力。内部排序使用了 3 种排序算法:
- 基数排序
如果排序字段内容长度之和小于等于 20 字节
,并且要排序的记录数量大于等于 1 千,小于 10 万
,同时能够满足基数排序需要的内存空间,则会使用基数排序。 - 快速排序
如果不满足使用基数排序的条件,则会考虑使用快速排序
,要排序的记录数量小于等于 100
,就会使用快速排序。 - 归并排序
归并排序是兜底的排序算法,如果不满足使用基数排序和快速排序的条件,就会使用归并排序
。
为什么使用快速排序的条件是排序记录数量小于等于 100?
源码注释是这样说的,归并排序比快速排序更快,但是归并排序申请临时缓冲区需要额外的时间成本,所以在排序记录数量很少的时候,归并排序并没有多大优势,归并排序比快速排序快的临界点是排序记录数量在 10 ~ 40 条之间
,保守一点,所以把这个阈值定为 100。
6.2 外部排序
外部排序是对磁盘文件中已经局部排好序的记录进行全局归并排序,是磁盘文件排序。
MySQL 从存储引擎读取符合 where 的条件记录写入排序缓冲区,缓冲区满时,会对缓冲区中的记录进行内部排序
,排好序的数据组成一个数据块,数据块包含两部分:Merge_chunk
和数据记录
。
Merge_chunk 写入到磁盘文件(chunk_file)中,数据记录写入到磁盘文件(temp_file)中。Merge_chunk 中保存有数据记录在 temp_file 中的起始位置
、Merge_chunk 对应的数据块在 temp_file 中的记录数量
等信息。
从存储引擎读取完符合 where 条件的所有记录之后,可能会生成多个数据块写入到磁盘文件(temp_file)。通过多路归并排序,把小数据块合并为更大的数据块,最终得到全局排好序的记录,把磁盘文件(temp_file)中多个数据块合并为一个数据块的过程,借助了优先队列
和排序缓冲区
来实现。
注意:外部排序过程中借助优先队列和排序缓冲区,和5.1 优先队列
中的优先队列 + 排序缓冲区
不是一回事,不要混淆了。外部排序过程只是使用了优先队列和排序缓冲区来加快归并排序的过程。
外部排序把小数据块合并为大数据块的过程中,会使用 7 路归并排序,把 7 个小数据块合并为 1 个大数据块,数据块数量多时,会进行多轮归并排序,直到数据块的数量小于等于 15
,多轮归并排序之后得到的小于等于 15 个数据块,经过终极归并排序得到最终的排序结果。
接下来我们以磁盘文件(temp_file)中包含 160 个数据块为例来说明外部排序的过程。
第一轮归并排序示意图:
左下角 chunk_file 中有 160 个 Merge_chunk,temp_file 有 160 个对应于 Merge_chunk 的数据记录块
。归并排序过程中会借助排序缓冲区提升执行效率,因为要进行 7 路归并排序,排序缓冲区被平均分为 7 份,每份对应于 temp_file 中的一个数据块,为了描述方便,我们暂且把排序缓冲区的七分之一叫做子排序缓冲区
。在归并排序过程中,会分别从 temp_file 中的 7 个数据记录块
中读取一部分记录到其对应的子排序缓冲区
。
读取 temp_file 中数据记录块
的数据到子排序缓冲区之后,优先队列中的每个 Merge_chunk(对应于 chunk_file 中的 Merge_chunk)中有一个属性 current_key
,指向子排序缓冲区中的第一条记录,图中优先队列的每个 Merge_chunk 有个红色箭头指向子排序缓冲区,就是表示 current_key 属性指向子排序缓冲区中的第一条记录。
current_key 指向的记录是子排序缓冲区对应的 temp_file 数据记录块中排序字段值(sort_key)最大的那条记录。
归并排序过程中,会循环把 7 个 Merge_chunk 的 current_key 指向的记录中排序字段值最大的记录写入到 temp_file2
的数据记录块
中,直到所有数据记录块中的记录都写入到 temp_file2 文件。
每次都取 current_key 指向的记录中最大的记录,有没有发现这是大顶堆
的特点?在源码实现中,找到优先队列中 Merge_chunk 的 current_key 属性指向的记录中排序字段值最大的记录,就是用大顶堆的思路实现的。
从图中右下角的 temp_file2
和 chunk_file
可以看到,经过第一轮归并排序之后,160 个小数据块合并成了 23 个更大的数据块,23 大于 15,所以还需要进行第二轮归并排序。
第二轮归并排序示意图:
第一轮归并排序,我们已经讲述了详细的合并过程,第二轮归并排序就不展开讲了,由上图右下角的 temp_file
和 chunk_file
可见,第二轮归并排序,由小数据块合并得到的 23 个更大的数据块,再次进行 7 路归并排序,最终得到 4 个数据块。4 小于 15,所以不需要进行得到中间结果
的第三轮归并排序,直接进行得到最终排序结果
的多路归并排序。
不知道大家有没有发现,第一轮归并排序示意图中,是从 temp_file 读取记录到子排序缓冲区,然后把归并排序结果写入到 temp_file2;第二轮归并排序示意图中,是从 temp_file2 读取记录到子排序缓冲区,然后把归并排序结果写入到 temp_file。这说明在外部归并排序过程中,会使用两个中间结果磁盘文件(temp_file、temp_file2),加上 chunk_file、out_file,一共会使用 4 个磁盘文件,大家知道一下就可以了。
终极多路归并排序:
从上图可见,经过前面两轮归并排序之后,得到 4 个数据块,排序缓冲区被分为 4 个子缓冲区,4 个子缓冲区中已局部排好序的记录,经过归并排序写入到存放最终排序结果的磁盘文件中(out_file)。
最后一轮归并排序和前面的 N 归并排序有些不同。
前面 N 轮归并排序,写入到磁盘文件的是中间结果
,磁盘文件(temp_file、temp_file2)中存放的还是局部排好序的记录,并且记录中还包含排序字段(sort_key),因为后面的归并排序还需要用到排序字段(sort_keky)。
最后一轮归并排序,写入到磁盘文件的是最终结果
,磁盘文件(out_file)中存放的是全局排好序的记录,此时记录中只包含存储引擎返回给 server 层的字段,已经不包含排序字段了。
7. 倒序排序
MySQL 文件排序的内部实现中,正序和倒序排序都是以正序的方式进行的,排序字段值大的在前面,排序字段值小的在后面,这样逻辑统一,实现方便。
倒序排序时,把排序字段值写入排序缓冲区之前,会对所有排序字段值进行逐字节取反
操作,取反之后,原来字段值大的变成小的,字段值小的变成大的,排序字段值取反之后再进行正序排序,最终得到的记录就是倒序排序的了。
举例说明
select num from t order by num desc
以 <sort_key, additional_fields> 排序模式为例,假设表中有 5 条记录,num 字段值分别为:95, 90, 49, 97, 6,num 字段值会写入到排序缓冲区两次,一次是作为 sort_key,一次是作为 additional_fields,5 条记录全部写入缓冲区之后,缓冲区的内容示意如下:
图中蓝色块
表示 sort_key,绿色块
表示 additional_fields,为了有个对比,1 号图示 和 2 号图示分别表示正序和倒序排序之前,排序缓冲区中的记录示意图。3 号图示表示倒序排序之后,排序缓冲区中的记录示意图。
从 3 号图示可见,倒序排序时,对排序字段值取反之后按照正序排序,最终实现了记录的倒序排序
。
上面示例中,对于 num 字段会写入排序缓冲区两次,可能有的小伙伴会有疑问,这里解释一下:实际上,排序字段作为 sort_key 写入到排序缓冲区之前,都会进行编码,编码之后的排序字段值就和原字段值不一样了,只用于排序,不作为最终结果返回给客户端,在排好序之后,sort_key 会被丢弃。
8. 窥探更多排序细节
通过 explain,我们只能看到 SQL 语句执行时,是否使用了文件排序,看不到排序过程中是只使用了排序缓冲区,还是也使用了磁盘文件,更不知道排序缓冲区被写满了多少次;也看不到是不是使用了优先队列,如果没有使用,是什么原因。
好在 MySQL 给我们提供了一个工具(optimizer trace
)可以进一步了解这些细节,执行以下 SQL 可开启 optimizer trace:
--开启optimizertrace
setoptimizer_trace="enabled=on";
--设置 optimizer_trace 输出结果最多可占用的内存空间,单位是:字节
--如果发现optimizer_trace的json结果不全,可以把这个值改成更大的数字
setoptimizer_trace_max_mem_size=1048576;
optimizer trace 的输出结果 json 中有两个和文件排序相关的属性:filesort_summary
、filesort_priority_queue_optimization
,下面我把写本文过程中使用的测试 SQL 的 optimizer trace 作为例子来说明:
filesort_summary
{
"rows":10227,
"examined_rows":10227,
"number_of_tmp_files":41,
"sort_buffer_size":262112,
"sort_mode":"<sort_key,rowid>"
}
number_of_tmp_files:
- 等于 0 表示只使用了排序缓冲区。
- 大于 0 表示还使用了磁盘文件,number_of_tmp_files 的值等于几,就表示排序缓冲区被写满了几次,也意味着写入到磁盘文件的数据块有几个。
大家不要被 number_of_tmp_files
属性名误导,虽然属性名中有 tmp_files,但这并不是表示排序过程中使用了多个临时文件。实际上不管有多少个数据块,都是写入到一个磁盘文件(temp_file)中。
相信经过本文前面的讲述,大家对于 sort_mode 都已经比较熟悉了,一共有三种排序模式:<sort_key, rowid>、<sort_key, additional_fields>、<sort_key, packed_additional_fields>。
filesort_priority_queue_optimization
{
"usable":false,
"cause":"notapplicable(noLIMIT)"
}
通过 usable = false
可知,我的测试 SQL 没有使用优先队列,原因是没有指定 limit。
有一点需要特别注意,获取 optimizer trace 结果的语句必须和 SQL 语句同时执行,不能分开一条一条的执行,否则查出来的 optimizer trace 结果是空白。
举例说明:
select * from t2 where i1>=99991840 order by str1;--SQL1
select * from information_schema.OPTIMIZER_TRACE;--SQL2
对于上面例子,如果先执行 SQL 1
再执行 SQL 2
,会发现 optimizer trace 结果是空的,必须同时执行 SQL 1 和 SQL 2,才能得到 SQL 1 的 optimizer trace 结果。
9. 总结
本文我们以文件排序的整体概览开始,抛开实现细节,从全局角度了解了文件排序的整体逻辑。接着介绍了系统变量 sort_buffer_size
用于控制排序缓冲区大小,max_sort_length
用于控制单个排序字段内容长度。
排序模式
小节,介绍了三种排序模式:
- <sort_key, additional_fields> 把排序字段(sort_key)和存储引擎返回给 server 层的字段都写入排序缓冲区或磁盘文件,每个字段都以其最大长度占用存储空间,存在在空间浪费。
- <sort_key, packed_additional_fields> 是 <sort_key, additional_fields> 的改进版,解决了空间浪费的问题。char、varchar 字段只占用实际内容需要的空间,内容为 NULL 的字段除了在 NULL 标记区域占用 1 bit 之外,不会再占用其它空间。
- 前面两种排序模式,以空间换时间,虽然不需要两次访问存储引擎,让文件排序逻辑整体上更简单,但是记录数量多起来之后,需要磁盘文件存储排序结果,而磁盘 IO 会导致排序效率低下。
<sort_key, rowid> 需要两次
访问存储引擎,但只写入排序字段(sort_key)和主键 ID 到排序缓冲区,一定程度上避免了使用磁盘文件存放排序结果,某些情况下可能会比前两种排序模式更优。
提升排序效率
小节,介绍了源码中采用的两个优化方案:
- SQL 语句中包含 limit 的情况下,通过成本评估有可能会使用优先队列来避免磁盘文件排序,提升排序效率。
- 使用 <sort_key, rowid> 排序模式,并且由于记录数量太多需要使用磁盘文件时,通过把主键 ID 缓存在随机读缓冲区(read rnd bufer),缓冲区满后对主键 ID 排序,再通过主键 ID 去存储引擎读取客户端需要的字段,尽可能把随机 IO 变为顺序 IO,提升排序效率。
两类排序
小节,介绍了三种内部排序算法,其使用优先级为:基数排序、快速排序、归并排序。
外部排序,可能会进行 0 ~ N 轮归并排序生成中间结果
,最后进行一轮归并排序得到最终结果
。
生成中间结果的归并排序,排序字段(sort_key)也会写入到磁盘文件,后续生成中间结果和最终结果的归并排序都会用到。生成最终结果的归并排序,磁盘文件只写入存储引擎返回给 server 层的字段,不会包含排序字段(sort_key)。
倒序排序
小节,介绍了倒序排序的实现:先对排序字段(sort_key)逐字节取反,然后对排序字段进行正序排序,最终得到倒序排序的记录。
最后,介绍了如何通过 optimizer trace 窥探文件排序的更多细节。
相关文章:
深入了解MySQL文件排序
数据准备 CREATE TABLE user_info (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT ID,name varchar(20) NOT NULL COMMENT 用户名,age tinyint(4) NOT NULL DEFAULT 0 COMMENT 年龄,sex tinyint(2) NOT NULL DEFAULT 0 COMMENT 状态 0:男 1: 女,creat…...
【JAVA基础】反射
编译期和运行期 首先大家应该先了解两个概念,编译期和运行期,编译期就是编译器帮你把源代码翻译成机器能识别的代码,比如编译器把java代码编译成jvm识别的字节码文件,而运行期指的是将可执行文件交给操作系统去执行, …...
贪心算法(2024/7/16)
1合并区间 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。 示例 1: 输入:inter…...
Python 在Word表格中插入、删除行或列
Word文档中的表格可以用于组织和展示数据。在实际应用过程中,有时为了调整表格的结构或适应不同的数据展示需求,我们可能会需要插入、删除行或列。以下提供了几种使用Python在Word表格中插入或删除行、列的方法供参考: 文章目录 Python 在Wo…...
Java二十三种设计模式-单例模式(1/23)
引言 在软件开发中,设计模式是一套被反复使用的、大家公认的、经过分类编目的代码设计经验的总结。单例模式作为其中一种创建型模式,确保一个类只有一个实例,并提供一个全局访问点。本文将深入探讨单例模式的概念、实现方式、使用场景以及潜…...
Unity动画系统(3)---融合树
6.1 动画系统基础2-6_哔哩哔哩_bilibili Animator类 using System.Collections; using System.Collections.Generic; using UnityEngine; public class EthanController : MonoBehaviour { private Animator ani; private void Awake() { ani GetComponen…...
sqlalchemy.orm中validates对两个字段进行联合校验
版本 sqlalchemy1.4.37 需求说明 有个场景,需要在orm中对两个字段进行联合校验,当 col1 xxx’时,对 col2的长度进行检查,超过限制(500)时,进行截断。 网上找了很久,没找到类似的…...
【ROS2】高级:解锁 Fast DDS 中间件的潜力 [社区贡献]
目标:本教程将展示如何在 ROS 2 中使用 Fast DDS 的扩展配置功能。 教程级别:高级 时间:20 分钟 目录 背景 先决条件在同一个节点中混合同步和异步发布 创建具有发布者的节点创建包含配置文件的 XML 文件执行发布者节点创建一个包含订阅者的节…...
VirtualBox虚拟机与主机互传文件的方法
建立共享文件夹 1.点击设置,点击共享文件夹,添加共享文件夹路径,保存 2.启动虚拟机,点击设备,点击安装增强功能,界面会出现一个光碟图标,点击光碟图标 3.打开光碟图标,出现一个目…...
访问控制系列
目录 一、基本概念 1.客体与主体 2.引用监控器与引用验证机制 3.安全策略与安全模型 4.安全内核 5.可信计算基 二、访问矩阵 三、访问控制策略 1.主体属性 2.客体属性 3.授权者组成 4.访问控制粒度 5.主体、客体状态 6.历史记录和上下文环境 7.数据内容 8.决策…...
【BUG】已解决:ModuleNotFoundError: No module named ‘cv2’
已解决:ModuleNotFoundError: No module named ‘cv2’ 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页,我是博主英杰,211科班出身,就职于医疗科技公司,热衷分享知识,武汉城市开…...
成都亚恒丰创教育科技有限公司 【插画猴子:笔尖下的灵动世界】
在浩瀚的艺术海洋中,每一种创作形式都是人类情感与想象力的独特表达。而插画,作为这一广阔领域中的璀璨明珠,以其独特的视觉语言和丰富的叙事能力,构建了一个又一个令人遐想连篇的梦幻空间。成都亚恒丰创教育科技有限公司 在众多插…...
gite+picgo+typora打造个人免费笔记软件
文章目录 1️⃣个人笔记软件2️⃣ 配置教程2.1 使用软件2.2 node 环境配置2.3 软件安装2.4 gite仓库设置2.5 配置picgo2.6 测试检验2.7 github教程 🎡 完结撒花 1️⃣个人笔记软件 最近换了环境,没有之前的生产环境舒适,写笔记也没有劲头&…...
只用 CSS 能玩出什么花样?
在前端开发领域,CSS 不仅仅是一种样式语言,它更像是一位多才多艺的艺术家,能够创造出令人惊叹的视觉效果。本文将带你探索 CSS 的无限可能,从基本形状到动态动画,从几何艺术到仿生设计,只用 CSS 就能玩出令…...
Linux C++ 056-设计模式之迭代器模式
Linux C 056-设计模式之迭代器模式 本节关键字:Linux、C、设计模式、迭代器模式 相关库函数: 概念 迭代器模式(Iterator Pattern)是一种常用的设计模式。迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而…...
【Elasticsearch7.11】reindex问题
参考博文链接 问题:reindex 时出现如下问题 原因:数据量大,kibana的问题 解决方法: 将DSL命令转化成CURL命令在服务上执行 CURL命令 自动转化 curl -XPOST "http://IP:PORT/_reindex" -H Content-Type: application…...
nginx代理缓存
在服务器架构中,反向代理服务器除了能够起到反向代理的作用之外,还可以缓存一些资源,加速客户端访问,nginx的ngx_http_proxy_module模块不仅包含了反向代理的功能还包含了缓存功能。 1、定义代理缓存规则 参数详解: p…...
[React 进阶系列] useSyncExternalStore hook
[React 进阶系列] useSyncExternalStore hook 前情提要,包括 yup 的实现在这里:yup 基础使用以及 jest 测试 简单的提一下,需要实现的功能是: yup schema 需要访问外部的 storage外部的 storage 是可变的React 内部也需要访问同…...
Linux C++ 055-设计模式之状态模式
Linux C 055-设计模式之状态模式 本节关键字:Linux、C、设计模式、状态模式 相关库函数: 概念 状态模式(State Pattern)是设计模式的一种,属于行为模式。允许一个对象在其内部状态改变时改变它的行为。对象看起来似…...
景联文科技构建高质量心理学系知识图谱,助力大模型成为心理学科专家
心理大模型正处于快速发展阶段,在临床应用、教育、研究等多个领域展现出巨大潜力。 心理学系知识图谱能够丰富心理大模型的认知能力,使其在处理心理学相关问题时更加精确、可靠和有洞察力。这对于提高心理健康服务的质量和效率、促进科学研究以及优化教育…...
【数学建模】——数学规划模型
目录 一、线性规划(Linear Programming) 1.1 线性规划的基本概念 1.2 线性规划的图解法 模型建立: 二、整数规划(Integer Programming) 2.1 整数规划的基本概念 2.2 整数规划的求解方法 三、非线性规划&#x…...
卸载linux 磁盘的内容,磁盘占满
Linux清理磁盘 https://www.cnblogs.com/siyunianhua/p/17981758 当前文件夹下,数量 ls -l | grep "^-" | wc -l ls -lR | grep "^-" | wc -l 找超过100M的大文件 find / -type f -size 100M -exec ls -lh {} \; df -Th /var/lib/docker 查找…...
LeetCode-随机链表的复制
. - 力扣(LeetCode) 本题思路: 首先注意到随机链表含有random的指针,这个random指针指向是随机的;先一个一个节点的拷贝,并且把拷贝的节点放在拷贝对象的后面,再让拷贝节点的next指向原链表拷贝…...
axios 下载大文件时,展示下载进度的组件封装——js技能提升
之前面试的时候,有遇到一个问题:就是下载大文件的时候,如何得知下载进度,当时的回复是没有处理过。。。 现在想到了。axios中本身就有一个下载进度的方法,可以直接拿来使用。 下面记录一下处理步骤: 参考…...
Linux: network: device事件注册机制 chatGPT; notify
ChatGPT 在 Linux 内核中,有关网络设备(net-device)的事件注册机制,允许用户在网络设备的状态发生变化(例如设备被删除、添加或修改)时接收通知。这主要通过 netdev 事件通知机制实现。具体来说,内核提供了一组用于注册和处理网络设备事件的 API。 以下是一些关键组件…...
【ROS2】测试
为什么要进行自动化测试? 以下是我们应该进行自动化测试的许多重要原因之一: 您可以更快地对代码进行增量更新。ROS 有数百个包,具有许多相互依赖关系,因此很难预见一个小变化可能引起的问题。如果您的更改通过了单元测试…...
别卷模型,卷应用:从李彦宏的AI观点谈起
2024年7月4日,世界人工智能大会暨人工智能全球治理高级别会议在上海世博中心隆重召开。百度创始人、董事长兼首席执行官李彦宏在产业发展主论坛上的发言,引起了广泛关注。他提出:“大家不要卷模型,要卷应用!”这一观点…...
数据库(Database,简称DB)介绍
数据库(Database,简称DB)是信息技术领域中一个至关重要的组成部分,它按照数据结构来组织、存储和管理数据。以下是对数据库的详细介绍: 一、定义与基本概念 定义:数据库是按照数据结构来组织、存储和管理…...
Redis五种常用数据类型详解及使用场景
Redis 5 种基本数据类型 Redis 共有 5 种基本数据类型:String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)。 这 5 种数据类型…...
Postman API测试覆盖率:全面评估指南
📊 Postman API测试覆盖率:全面评估指南 在API测试中,测试覆盖率是一个关键指标,它衡量了测试用例对代码的覆盖程度。Postman提供了多种工具和方法来评估API测试覆盖率,帮助开发者和测试人员确保API的质量和稳定性。本…...
wordpress 日志怎么看/凡科建站登录官网
问: 给定由一些正数(代表长度)组成的数组 nums ,返回 由其中三个长度组成的、面积不为零的三角形的最大周长 。如果不能形成任何面积不为零的三角形,返回 0。 原题链接:https://leetcode.cn/problems/large…...
小密圈wordpress/seo网络营销是什么意思
从输入URL到页面加载的过程?如何由一道题完善自己的前端知识体系! http://www.dailichun.com/2018/03/12/whenyouenteraurl.html转载于:https://www.cnblogs.com/zard2008/p/9110586.html...
杭州房产网二手房/南昌seo方案
Vector3(x,y,z)x代表左右,y代表上下,z代表前后 Vector3.magnitude 长度 计算两点之间的距离 。如果只给了一点的话。算出的长度其实就是和Vector3.zero点之间的长度 公式:a2b2c2(勾股定理) 2D:3D: 计算机实现&…...
做网站合同/搜索引擎排名2021
一、web push 使用动机与原理简述 相较于移动端本地应用,web站点常常缺少一项常用的功能:推送通知。此处的推送通知一般指由浏览器实现的消息推送,换个说法,就是用户在打开浏览器时,不需要进入特定的网站,就…...
php做网站很快嘛/网站制作推广电话
新浪微博需要登录才能爬取,这里使用m.weibo.cn这个移动端网站即可实现简化操作,用这个访问可以直接得到的微博id。分析新浪微博的评论获取方式得知,其采用动态加载。所以使用json模块解析json代码单独编写了字符优化函数,解决微博…...
南京 网站建设/互联网营销的十五种方式
php结合layui前端实现多图上传前端html代码请选择图片文件名图片预览大小状态操作开始上传js 代码layui.use(upload, function() {var $ layui.jquery,upload layui.upload;//多文件列表示例var demoListView $(#demoList),uploadListIns upload.render({elem: #testList,u…...