在编程中,我们很多场景都会用到groupby关键字。比如分页读取数据时,为了避免重复扫描记录,就需要使用groupby。例如,我们使用如下DDL创建表:CREATETABLE`user_info`(`id`int(11)NOTNULLAUTO_INCREMENTCOMMENT'主键ID',`city`varchar(16)NOTNULLCOMMENT'city',`name`varchar(16)NOTNULLCOMMENT'name',`age`int(11)NOTNULLCOMMENT'age',`addr`varchar(128)DEFAULTNULLCOMMENT'address',PRIMARYKEY(`id`),KEY`city`(`city`))ENGINE=InnoDBDEFAULTCHARSET=utf8我们将执行如下查询语句SELECTcity,`name`,ageFROMuser_infoWHEREcity='Shanghai'ORDERBY`name`LIMIT1000;对所有字段进行排序因为上面的建表语句已经在city字段上创建了索引,所以当我们使用EXPLAIN命令时,会得到如下结果:上面Extra字段中的“Usingfilesort”表示需要排序,MySQL会为每个线程分配一块内存用于排序,成为sort_buffer。我们先看一下index(city)的结构图。执行过程如下:初始化sort_buffer,并确保放入citynameage这三个字段;从城市索引中获取city='Shanghai'的第一条记录,即id_x;获取主键索引中对应的记录,并取出namecityage的值放入sort_buffer;取下一条符合条件的记录,重复34的操作,直到不满足条件;根据name快速对sort_buffer中的数据进行排序;取出前1000条记录返回。我们暂且称这个排序过程为“全域排序”,如下图:图中的“按名称排序”可能是在内存中排序,也可能是使用磁盘文件,具体取决于排序所需的内存和sort_buffer_size。sort_buffer_size是mysql开辟的用于排序的内存大小。当所需内存小于sort_buffer_size时,将直接在内存中进行排序。如果所需内存大于sort_buffer_size,则需要额外的磁盘空间来辅助排序。上面的rowid排序算法在数据量比较大的时候可能会出现一些问题。因为在排序的时候,把返回的字段全部存储起来,增加了排序空间(sort_buffer)的压力。SETmax_length_for_sort_data=16;max_length_for_sort_data是MySQL限制排序行大小的参数。意思是,如果排序的行大小超过这个值,将选择另一种排序算法。上面namecityage三个字段的大小都是36,大于16。在新算法中,只有name(排序字段)和id会参与sort_buffer中的排序。过程如下,初始化sort_buffer,并确保放入nameid两个字段;从城市索引中获取city='Shanghai'的第一条记录,即id_x;从主键索引中取出对应的记录,取出nameid值放入sort_buffer;取下一条符合条件的记录,重复34的操作,直到不满足条件;将sort_buffer中的数据按照name快速排序;取出前1000条数据,然后根据idnamecityage3个字段取出对应的记录并返回结果。这种排序过程称为rowid排序,过程如下:全字段排序VSrowid排序从以上两个过程来看,如果内存足够,MySQL会将返回值中的所有字段存储在排序空间中。当MySQL内存太小时,会考虑使用rowid排序。但是从上面的过程来看,rowid排序在返回结果之前,会再次对表进行排序。因此,当MySQL认为内存足够时,会优先按所有字段排序。上面的场景是:city字段过滤后,name字段没有排序。其实我们可以通过联合索引来避免name字段的排序。altertableuser_infoaddindexidx_city_user(城市,名称);我们来看一下联合索引的示意图:从上面的流程图可以看出,当我们取出city='Shanghai'的记录时,name的字段也是有序的。过程如下:从(city,name)索引中获取第一条city='Shanghai'的记录id_x;从主键索引中获取对应的记录,取出namecityage的值作为结果集的一部分直接返回;对于下一条符合条件的记录,重复2和3的操作,直到不满足条件或达到1000条;从联合索引中,我们可以不进行排序操作,那么是否可以直接通过索引返回结果呢?即不进行回表操作。答案是肯定的,那就是覆盖索引。altertableuser_infoaddindexidx_city_user_age(城市、姓名、年龄);在执行查询语句时,不仅name中的字段是有序的,而且结果集中的所有字段都已经被索引了,过程如下:从(city,name,age)索引中得到先记录city='Shanghai',将namecityage的值作为结果集的一部分取出,直接返回;取下一条满足条件的记录,重复1和2的操作,直到不满足条件或者达到1000为止
