mysql 索引分析以及using filesort 问题

本文主要通过创建一个abcd 四字段的联合索引来分析各个组合查询场景下索引的使用情况,同时之前在做xqueue 项目时,在压测中,发现因为order by后的字段没有使用索引,使得explain 中 ref 包含using filesort,性能一直压不上去,进而分析为什么会出现using filesort以及如何避免using filesort问题。

准备

1.创建test表(测试表)。

drop table if exists test;
create table test(
id int primary key auto_increment,
c1 varchar(10),
c2 varchar(10),
c3 varchar(10),
c4 varchar(10),
c5 varchar(10)
) ENGINE=INNODB default CHARSET=utf8;

insert into test(c1,c2,c3,c4,c5) values('a1','a2','a3','a4','a5');
insert into test(c1,c2,c3,c4,c5) values('b1','b2','b3','b4','b5');
insert into test(c1,c2,c3,c4,c5) values('c1','c2','c3','c4','c5');
insert into test(c1,c2,c3,c4,c5) values('d1','d2','d3','d4','d5');
insert into test(c1,c2,c3,c4,c5) values('e1','e2','e3','e4','e5');

2.创建索引。

1.根据以下Case分析索引的使用情况

Case 1:

分析:

①创建复合索引的顺序为c1,c2,c3,c4。

②上述四组explain执行的结果都一样:type=ref,key_len=132,ref=const,const,const,const。

结论:在执行常量等值查询时,改变索引列的顺序并不会更改explain的执行结果,因为mysql底层优化器会进行优化,但是推荐按照索引顺序列编写sql语句。

Case 2:

分析:

当出现范围的时候,type=range,key_len=99,比不用范围key_len=66增加了,说明使用上了索引,但对比Case1中执行结果,说明c4上索引失效。

结论:范围右边索引列失效,但是范围当前位置(c3)的索引是有效的,从key_len=99可证明。

Case 2.1:

分析:

与上面explain执行结果对比,key_len=132说明索引用到了4个,因为对此sql语句mysql底层优化器会进行优化:范围右边索引列失效(c4右边已经没有索引列了),注意索引的顺序(c1,c2,c3,c4),所以c4右边不会出现失效的索引列,因此4个索引全部用上。

结论:范围右边索引列失效,是有顺序的:c1,c2,c3,c4,如果c3有范围,则c4失效;如果c4有范围,则没有失效的索引列,从而会使用全部索引。

Case 2.2:

分析:

如果在c1处使用范围,则type=ALL,key=Null,索引失效,全表扫描,这里违背了最佳左前缀法则,带头大哥已死,因为c1主要用于范围,而不是查询。

解决方式使用覆盖索引。

结论:在最佳左前缀法则中,如果最左前列(带头大哥)的索引失效,则后面的索引都失效。

Case 3:

分析:

利用最佳左前缀法则:中间兄弟不能断,因此用到了c1和c2索引(查找),从key_len=66,ref=const,const,c3索引列用在排序过程中。

Case 3.1:

分析:

从explain的执行结果来看:key_len=66,ref=const,const,从而查找只用到c1和c2索引,c3索引用于排序。

Case 3.2:

分析:

从explain的执行结果来看:key_len=66,ref=const,const,查询使用了c1和c2索引,由于用了c4进行排序,跳过了c3,出现了Using filesort。

Case 4:

分析:

查找只用到索引c1,c2和c3用于排序,无Using filesort。

Case 4.1:

分析:

和Case 4中explain的执行结果一样,但是出现了Using filesort,因为索引的创建顺序为c1,c2,c3,c4,但是排序的时候c2和c3颠倒位置了。

Case 4.2:

分析:

在查询时增加了c5,但是explain的执行结果一样,因为c5并未创建索引。

Case 4.3:

分析:

与Case 4.1对比,在Extra中并未出现Using filesort,因为c2为常量,在排序中被优化,所以索引未颠倒,不会出现Using filesort。

Case 5:

分析:

只用到c1上的索引,因为c4中间间断了,根据最佳左前缀法则,所以key_len=33,ref=const,表示只用到一个索引。

Case 5.1:

分析:

对比Case 5,在group by时交换了c2和c3的位置,结果出现Using temporary和Using filesort,极度恶劣。原因:c3和c2与索引创建顺序相反。

总结:

通过以上Case的分析,进行如下总结:

①最佳左前缀法则。

  1. 在等值查询时,更改索引列顺序,并不会影响explain的执行结果,因为mysql底层会进行优化。

  2. 在使用order by时,注意索引顺序、常量,以及可能会导致Using filesort的情况。

②group by容易产生Using temporary。

③通俗理解口诀:

全值匹配我最爱,最左前缀要遵守;
带头大哥不能死,中间兄弟不能断;
索引列上少计算,范围之后全失效;
LIKE百分写最右,覆盖索引不写星;
不等空值还有or,索引失效要少用。

using filesort 分析

我们先建一个user表,其中有自增主键、user_id 也建立索引,create_date暂时不建索引,省略其他字段。

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `user_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '员工id',
  `create_date` datetime NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT '创建日期',
   省略其他字段
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`)

当分页查询的时候,打印执行计划,Extra 一栏出现了 Using filesort。

explain SELECT * FROM user ORDER BY create_date DESC limit 20.40;
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows  | Extra          |
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+
|  1 | SIMPLE      | user  | ALL  | NULL          | NULL | NULL    | NULL | 22686 | Using filesort |
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+

Using filesort 是什么意思?

官方的定义是,MySQL must do an extra pass to find out how to retrieve the rows in sorted order. The sort is done by going through all rows according to the join type and storing the sort key and pointer to the row for all rows that match the WHERE clause . The keys then are sorted and the rows are retrieved in sorted order。

MySQL需要额外的一次传递,以找出如何按排序顺序检索行。通过根据联接类型浏览所有行并为所有匹配WHERE子句的行保存排序关键字和行的指针来完成排序。然后关键字被排序,并按排序顺序检索行。标红,重点。

filesort 有两种排序方式

  1. 对需要排序的记录生成 <sort_key,rowid> 的元数据进行排序,该元数据仅包含排序字段和rowid。排序完成后只有按字段排序的rowid,因此还需要通过rowid进行回表操作获取所需要的列的值,可能会导致大量的随机IO读消耗
  2. 对需要排序的记录生成 <sort_key,additional_fields> 的元数据,该元数据包含排序字段和需要返回的所有列。排序完后不需要回表,但是元数据要比第一种方法长得多,需要更多的空间用于排序

优化方法

filesort 使用的算法是QuickSort,即对需要排序的记录生成元数据进行分块排序,然后再使用mergesort方法合并块。其中filesort可以使用的内存空间大小为参数 sort_buffer_size 的值,默认为2M。当排序记录太多 sort_buffer_size 不够用时,mysql会使用临时文件来存放各个分块,然后各个分块排序后再多次合并分块最终全局完成排序。可以增大 **sort_buffer_size **来解决 filesort 慢问题,也就是上面的第二种排序。

当 排序元组中的extra列的总大小不超过 max_length_for_sort_data 系统变量值的时候,我们如何优化 Using filesort 中的 回表操作 呢?

文件排序优化不仅用于记录排序关键字和行的位置,并且还记录查询需要的列。这样可以避免两次读取行。

我们都知道,Mysql Innodb 下使用的是聚集索引。PRIMARY KEY 的叶子节点存储的是数据,其他索引的叶子节点存储的是PRIMARY KEY.

当我们使用非PRIMARY KEY 查询的时候,查询1会进行回表操作,也就是额外的一次查询,去查询表中的其他数据,而查询2会直接返回id和user_id。

查询1:select * from user where user_id=1;
查询2:select user_id,id from user where user_id=1;

好,下面我们通过例子来理解一下Using filesort 的形成

我们先来看第一条查询,根据create_date 排序,由于create_date 没有建索引,

explain SELECT create_date,id FROM user ORDER BY create_date DESC;
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows  | Extra          |
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+
|  1 | SIMPLE      | user  | ALL  | NULL          | NULL | NULL    | NULL | 22686 | Using filesort |
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+

当使用没有索引字段的时候,会出现 Using filesort。那我们建立 create_date 索引之后呢,看下结果。

explain SELECT create_date,id FROM user ORDER BY create_date DESC;
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows  | Extra          |
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+
|  1 | SIMPLE      | user  | ALL  | NULL          | NULL | NULL    | NULL | 22686 | Using index    |
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+

如果我想把 user_id 也查询出来呢,看下结果。聚集索引,想查询user_id 还是要进行回表操作的。

explain SELECT create_date,user_id FROM user ORDER BY create_date DESC;
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows  | Extra          |
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+
|  1 | SIMPLE      | user  | ALL  | NULL          | NULL | NULL    | NULL | 22686 | Using filesort |
+----+-------------+-------+------+---------------+------+---------+------+-------+----------------+

回到开篇,我们如何优化这个查询呢。

首先是尽量不要让sql使用using filesort。

select * from (select id from user order by create_date DESC limit 20.40) a left join user b on a.id=b.id;

利用mysql聚集索引的性质,分页查询id,避免了Using filesort,这个查询是很快的。而在分页的数据量上,再去查询所有数据,性能就很高了。

如果必须使用using filesort,无法避免排序操作时,很显然应该尽可能让 MySQL 选择使用第二种单路算法来进行排序。这样可以减少大量的随机IO操作,很大幅度地提高排序工作的效率。

  1. 加大 max_length_for_sort_data 参数的设置
    当所有返回字段的最大长度小于这个参数值时,MySQL 就会选择改进后的单路排序,反之,则选择老式的双路排序。所以,如果有充足的内存让MySQL 存放须要返回的非排序字段,就可以加大这个参数的值来让 MySQL 选择使用改进版的排序算法。
  2. 去掉不必要的返回字段
    当内存不是很充裕时,不能简单地通过强行加大上面的参数来强迫 MySQL 去使用改进版的排序算法,否则可能会造成 MySQL 不得不将数据分成很多段,然后进行排序,这样可能会得不偿失。此时就须要去掉不必要的返回字段,让返回结果长度适应 max_length_for_sort_data 参数的限制。
  3. 增大 sort_buffer_size 参数
    设置增大 sort_buffer_size 并不是为了让 MySQL选择改进版的排序算法,而是为了让MySQL尽量减少在排序过程中对需要排序的数据进行分段,因为分段会造成 MySQL 使用临时表来进行交换排序。

参考链接:
mysql索引常见面试题

Mysql 查询优化之 Using filesort

MySQL 中 Using filesort 问题的优化方法

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • 1.索引使用测试 1.1创建test表(测试表) 1.2.创建索引 1.3.分析以下Case索引使用情况 Case...
    香沙小熊阅读 348评论 0 1
  • 索引是帮助MySQL高效获取数据的排好序的数据结构。在学习索引优化之前建议先学习 mysql索引数据结构在分析SQ...
    董二弯阅读 401评论 0 1
  • 1、单表索引优化 单表索引优化分析 创建表 建表 SQL 表中的测试数据 查询案例 查询category_id为1...
    L_又不是不能用阅读 595评论 1 2
  • 1.索引的基本语法 2.那些情况需要创建索引 主键自动建立唯一索引 频繁作为查询条件的字段应该创建索引 查询中与其...
    _不想翻身的咸鱼阅读 408评论 0 0
  • 索引:排好序利于快速查找的数据结构(Btree) explain/show profile1、typeall - ...
    换煤气哥哥阅读 527评论 0 0