为何咱们要尽可能避免FileSort(文件排序)

故事

如今,假设阅读此文的你穿越回了小学二年级的时光,此时的你正在不断的追求着隔壁班的班长小红,巴不得把家里全部东西都送给TA。那么问题来了,若是你要把家里东西都搬光送给小红,你有几种办法?如下是我想到 mysql

  • 一件一件的搬,若是搬不动那就拆分(不排除你被你父母揍一顿的可能性)
  • 试图经过吃药让本身变成大力士

上述例子看似滑稽,但其实这是一直以来人类解决大规模数量问题的解决方案,即要么提高自身的能力以应付大规模的数量,要么进行拆分,分而治之。git

对应到IT行业,因为传统小型机处理能力有限因而便有了大型机。若是不用大型机那咋办吗?只好拆分服务,因而便有了微服务。github

经典面试题

面试官:假设你只有100M的内存可用,如今有一个大小为1G的文件,里面存放着整数,每一个整数用4个字节来存储,要你对这个这个文件中数据进行排序,你有什么解决方案?web

:打电话找行政的妹子跟她要一条8G的DDR4内存条,为了表示感谢顺便约她去吃饭, 说不定还能顺利脱单。面试

面试官:emmm….., 回去等通知吧算法

解决方案

:要解决这个问题,首先咱们须要分为两种状况:sql

  • 数据不重复 若是数据不重复咱们可使用位图来标记相应的数据,在须要输出结果的时候遍历位图便可(此方案较为简单,不在本文的讨论范围内)数据库

  • 数据重复 因为只有100M的内存可用,彻底利用这100M内存的状况下意味着咱们一次能够对26214400个整数(100 * 1024 * 1024 / 4 ) 进行排序, 这意味着咱们要分次读取文件并对读取的内容进行排序,并将每一次排序的结果保存到文件系统中,以后再对这些文件进行合并。设计模式

面试官:能够用画图表示一下吗?数组

:过程以下图所示

面试官:能够,要不你现场写一下代码吧

解决方案的实现

解决方案的实现总的来讲有如下几步

  • 根据缓冲区的大小读入相应的数据量,并把他们转为整数数组,进行排序,并写入文件,重复这一步直到原始数据文件中没有数据可读。

  • 合并这些已排序的文件直到只剩一个文件

将问题拆分开来看的话,咱们须要解决如下子问题

  • 因为咱们采用4个字节的数据来保存整数,所以咱们须要解决整数按字节存取的问题

你能够考虑一下为何咱们要用四个字节来存取整数?而不是将其转为字符串

  • 合并已排序的文件的算法

方案1、预读取一部分的数据写入缓存中,而后进行归并排序(拆分以后的文件中的数据都是有序的),当数据用完时再去文件中读取,重复此步骤直到没有数据可读

方案2、每次只从两个文件中读取一个整数,进行比较,而后将较大/较小(取决于你要增序仍是降序)的数据写入文件中

方案一,相对来讲比较简单而且速度比较快留给你们实现。

对于方案二,因为最近开发中有涉及状态机,所以对于方案二我采用了状态机的设计模式来实现。

该状态机以下所示

外部排序的实现

给大伙提供个参考,我实现的方案还有进一步优化的空间😄

测试

为了有一个直观的印象,咱们对一个16MB的文件进行排序,缓冲区设置为512kb.

如下为测试结果

  • 文件分割阶段,能够看出文件分割的时候所用时间都是差很少的

  • 合并阶段,能够看出合并已排序的文件所用的耗时是不断递增的由于并合并的文件体积在不断的递增

若是咱们直接将缓冲区设置为16MB呢?如下为测试结果,连合并阶段都不用了。

面试官: 很好,那你能说出应用场景吗?

:利用文件(file)进行排序(sort)工做 = filesort,好像在哪里见过…

面试官: 提示你一个单词explain

FileSort

:想起来了,假设咱们有一张表

CREATE TABLE `users` (
  `id` int(11NOT NULL,
  `account` varchar(45COLLATE utf8mb4_bin DEFAULT NULL,
  `nickname` varchar(45COLLATE utf8mb4_bin DEFAULT NULL,
  `password` varchar(45COLLATE utf8mb4_bin DEFAULT NULL,
  PRIMARY KEY (`id`)
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
复制代码

若是咱们须要根据某一个字段继续排序而且没有添加索引的话,那么使用explain对该SQL进行查询的话就会在Extra中看到filesort

以下图所示

这意味着,MySQL没法根据索引对数据进行排序(若是有索引的话直接取就行了,不须要排序操做)。

只好对要排序字段的进行排序了,可是生产环境中数据量可能会很是大,若是所有加载到内存中,必然会引发内存不足进而致使数据库崩溃,所以必须划出一块专门的内存区域以供排序,而这块内存区域极可能装不下这巨大的数据量,必然要借助外部文件系统进行排序,这就是filesort的由来

面试官:很好,那你知道怎么看这块内存的大小吗?

: 缓冲区 = buffer,根据mysql的一向传统,如下语句应该能够查到

show variables like '%buffer%'
复制代码

(图中蓝色标注的区域,即 sort_buffer_size)

面试官:很好,那你知道怎么优化吗?

: 加索引呗,还能咋样,要不叫运维给服务器再加个内存条?或者把牙膏厂(Intel)的CPU换成农厂的CPU(AMD!YES)

面试官:只要你喜欢AMD,咱们就是异父异母的亲兄弟。哦,不对我是想问该怎么加索引

:咱们知道索引是有顺序的,若是索引上信息已经知足了咱们的需求,那么就不须要使用filsort了。

好比上文中所提到的users表
咱们建立了一个index

alter table users add index(nickname, account)
复制代码

考虑如下语句是否须要filesort

select nickname,account from users order by nickname
复制代码
select * from users order by nickname
复制代码
select * from users order by account
复制代码

答案是

  • 第一条语句不须要filesort,由于索引中已经包含了咱们所须要的信息
  • 第二条语句能够直接使用索引(索引有序存储),在读取到索引对应的主键值后取相应的数据并直接返回给客户端便可,不须要使用到sort_buffer
  • 第三条语句须要filesort, 但因为account和nickname组合成了索引,每个nickname对应的account都是有序,所以不一样的nickname对应的account能够用来作归并排序(如上文所提到的合并阶段)

总结

今天的总结就三张图

附录

Q1: 为何用4个字节来存整数

节省空间,用字符串来存的话,你整数多长就得多少个字节

Q2: 怎么使用本文提供得外部排序DEMO


源码中的三个文件分别是

  • 打印文件中的数据
  • 对指定文件进行排序
  • 生成随机数文件

Q3: 为何要使用状态机来实现归并排序

不得不说,用状态机来梳理逻辑是比较清晰的,建议你也尝试一下。但在本例中若是你使用缓冲区来保存整数数组的话性能会快不少。

参考资料

《高性能MySQL(第三版)》

索引相关的部分

《MySQL王者晋级之路》

3.4节

相关文章
相关标签/搜索