今天遇到一个filesort优化的案例,感觉不错,分享出来。

MySQL中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。

一般来说如果有你的SQL查询语句中有order by且没有合适的索引时,通过EXPLAIN query可以在Extra列查看到Using filesort字样,当然一般来说此时也代表你需要去优化它了,无论是通过优化索引还是改变SQL查询实现方式。

先看表结构信息:

CREATE TABLE `tbxxxx` (
`id` int(10) unsigned NOT NULL auto_increment COMMENT '??id',
`a` varchar(20) default NULL COMMENT '??id',
`base62_id` varchar(10) default NULL COMMENT '??base62_id',
`userid` varchar(20) default NULL COMMENT '??id',
`category` int(5) unsigned default NULL COMMENT '????id',
`rate` decimal(10,2) NOT NULL default '0.00' COMMENT '??',
`status` enum('Y','N') NOT NULL default 'Y' COMMENT '??',
`releaseTime` datetime default NULL COMMENT '??????',
`createTime` datetime default NULL COMMENT '??????',
`content` text,
PRIMARY KEY (`id`),
UNIQUE KEY `a` (`a`),
KEY `releaseTime` (`releaseTime`),
KEY `rate` (`rate`),
KEY `crr` (`category`,`rate`,`releaseTime`),
KEY `idx_c_r_rate` (`category`,`releaseTime`,`rate`),
KEY `idx_status_rt_rate` (`status`,`releaseTime`,`rate`),
KEY `idx_status_rate_release` (`status`,`rate`,`releaseTime`)
) ENGINE=MyISAM AUTO_INCREMENT=3346255 DEFAULT CHARSET=utf8 COMMENT='?????'
1 row in set (0.15 sec)

ps:上面表结构中有些索引并不是线上实际存在的,这是做测试用临时添加的。

然后业务有如下查询:

SELECT a, content FROM tbxxxxx  WHERE `status`='Y' and releaseTime > '2013-07-08 11:00:00' ORDER BY rate DESC LIMIT 0, 10

对于这类型的查询可能第一反应是建立一个(status, releaseTime, rate)的复合索引, 然后通过EXPLAIN发现优化器也是这么选择的:

explain SELECT a, content FROM tbxxxxx  WHERE `status`='Y' and releaseTime > '2013-07-08 11:00:00' ORDER BY rate DESC LIMIT 0, 10;

+----+-------------+-----------+------+--------------------------------------------------------+--------------------+---------+-------+--------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------+------+--------------------------------------------------------+--------------------+---------+-------+--------+-----------------------------+
| 1 | SIMPLE | tbxxxxx | ref | releaseTime,idx_status_rt_rate,idx_status_rate_release | idx_status_rt_rate | 1 | const | 531837 | Using where; Using filesort |
+----+-------------+-----------+------+--------------------------------------------------------+--------------------+---------+-------+--------+-----------------------------+
1 row in set (0.15 sec)

上面的key_len=1是能理解的,因为MySQL 5.6以前没有ICP,所releaseTime这种范围查询是无法利用索引。status占一个字节。但是从Extra中我们可以发现由于order by rate导致了filesort。那么这个索引该怎么调整才能避免filesort呢?其实在一刚开始没想到得一定去优化这个filesort的开销(后来证明这个案例中得filesort占了99%的开销),而是想着怎么去优化扫描的行数,使筛选的得到的行更少,这样可以减少回表带来的开销。但是通过打开profile之后,发现了问题最关键的地方:

上面的图标可以显示结果集排序占了绝大部分的时间开销。那么此时问题就变成了该怎么来优化掉这个可恶的filesort,于是添加了一个(status, rate, releaseTime)字段的索引,测试发现优化器依然默认会走(status, releaseTime, rate)这个复合索引, 于是只能通过用use index()的语法来强制走目标索引:

explain SELECT a, content FROM tbxxxxx use index(idx_status_rate_release) WHERE `status`='Y' and releaseTime > '2013-07-08 11:00:00' ORDER BY rate DESC LIMIT 0, 10;
+----+-------------+-----------+------+-------------------------+-------------------------+---------+-------+---------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------+------+-------------------------+-------------------------+---------+-------+---------+-------------+
| 1 | SIMPLE | tbxxxxxx | ref | idx_status_rate_release | idx_status_rate_release | 1 | const | 1057094 | Using where |
+----+-------------+-----------+------+-------------------------+-------------------------+---------+-------+---------+-------------+
1 row in set (0.00 sec)

从执行计划可以看出, 此时没有了filesort这个阶段,因为默认status, rate这个索引前缀就是按照rate来排序的,因此正好可以利用索引的数据有序来达到最终的order by rate效果。细心的朋友可能会发现在这个执行计划中估算扫描的行数是上一个执行计划的2倍, 多扫描这么多行开销不大吗(这也是为什么当初第一反应优化这条SQL时会考虑优化索引来减少扫描的行数)? 事实上证明这个开销相对于filesort来说已经很小,有两点可以证明:profile的分析显示99%的时间是耗在sorting result,第二,在线上一个从库上测试第二个执行计划查询时间为0.1s左右,而第一个执行计划的查询时间在3s左右。虽然说一般来说并不建议使用use index这种语法,后期的不确定性较大,但是既然提供这种语法就有它自己的理由, 用好了自然有优势,先这么将就着吧。另外这里需要说明的是:(status, rate, releaseTime)这个索引中只有rate是必须得, status由于只有两个取值,筛选效果很不明显, releaseTime用不到(ICP后可能能用到)

最后是两点小感悟:1.MySQL的优化器还是那么有点坑爹, 有时候不靠谱。2.有时候优化一个SQL还是不能完全凭直觉,平常的经验,profile这种命令可以完全将一个SQL查询执行过程中各个阶段的开销都统计出来,这样我们就有了针对一个SQL的优化关键点,这样才能做不到不盲目,从而高效快速的优化。

MySQL filesort优化案例一则的更多相关文章

  1. MySQL参数优化案例

    环境介绍 优化层级与指导思想 优化过程 最小化安装情况下的性能表现 优化innodb_buffer_pool_size 优化innodb_log_files_in_group&innodb_l ...

  2. MySQL索引优化案例

    这里我们分成三种情况进行分析,分别是单表,两表,三表 1.单表 CREATE TABLE IF NOT EXISTS `article`( `id` ) NOT NULL PRIMARY KEY AU ...

  3. MySQL索引优化案例浅析

    MySQL是关系型数据库的一种,查询功能强,数据一致性高,数据安全性高,支持二级索引.但是性能比起非关系型数据库稍弱,特别是百万级以上的数据,很容易出现查询慢的现象.这时候要分析慢的原因,一般情况下是 ...

  4. mysql depended_query 优化案例一则

    月度利息统计sql优化 原因:写的sql语句复杂,理解起来有难度,另一方面,查询性能比较低 原来的语句如下: SELECT tp.year, tp.month, tp.bid_id, b.`title ...

  5. MySQL的索引单表优化案例分析

    建表 建立本次优化案例中所需的数据库及数据表 CREATE DATABASE db0206; USE db0206; CREATE TABLE `db0206`.`article`( `id` INT ...

  6. mysql优化案例

    MySQL优化案例 Mysql5.1大表分区效率测试 Mysql5.1大表分区效率测试MySQL | add at 2009-03-27 12:29:31 by PConline | view:60, ...

  7. 记一次mysql多表查询(left jion)优化案例

    一次mysql多表查询(left jion)优化案例 在新上线的供需模块中,发现某一个查询按钮点击后,出不来结果,找到该按钮对应sql手动执行,发现需要20-30秒才能出结果,所以服务端程序判断超时, ...

  8. Mysql 排序优化与索引使用(转)

    为了优化SQL语句的排序性能,最好的情况是避免排序,合理利用索引是一个不错的方法.因为索引本身也是有序的,如果在需要排序的字段上面建立了合适的索引,那么就可以跳过排序的过程,提高SQL的查询速度.下面 ...

  9. MySQL分页优化中的“INNER JOIN方式优化分页算法”到底在什么情况下会生效?

    本文出处:http://www.cnblogs.com/wy123/p/7003157.html 最近无意间看到一个MySQL分页优化的测试案例,并没有非常具体地说明测试场景的情况下,给出了一种经典的 ...

随机推荐

  1. TCP/IP之TCP交互数据流、成块数据流

    建立在TCP协议上的网络协议有telnet,ssh,ftp,http等等.这些协议根据数据吞吐量来分成两大类: (1)交互数据类型,例如telnet,ssh,这种类型的协议在大多数情况下只是做小流量的 ...

  2. CUDA纹理绑定

    纹理绑定的一般步骤: size_t fea_pitch; texture<unsigned char, 2> features2D; cudaMallocPitch((void**)(&a ...

  3. [置顶] PHP调用move_uploaded_file()提示 failed to open stream: Permission denied(Linxux环境,以Ubuntu12.04为例)

    在使用PHP上传文件之后,我们有时候还需要移动到特定的文件夹,这时候就要调用move_uploaded_file()函数,可是会出现如下错误: Warning: move_uploaded_file( ...

  4. application(expand)--easyui

    一,效果图. 二,源代码. <!DOCTYPE html><html><head> <meta charset="UTF-8"> & ...

  5. HDU 3974 Assign the task 简单搜索

    根据Rex 的思路才知道可以这么写. 题目意思还是很好理解的,就是找到当前雇员最近的任务. 做法是,可以开辟一个 tim 变量,每次有雇员得到昕任务时候 ++tim 然后取寻找最近的任务的时候写一个搜 ...

  6. docker学习笔记15:Dockerfile 指令 USER介绍

    USER指令用于指定容器执行程序的用户身份,默认是 root用户. 在docker run 中可以通过 -u 选项来覆盖USER指令的设置. 举例:docker run -i -t -u mysql ...

  7. MediaPlayer视频播放器

    android视频播放 根据apidemo重写.代码如下: package com.jamdeo.tv.livetv.player; import android.media.AudioManager ...

  8. PHP - mysql使用参数数据

    "SELECT dg_id FROM dg_user WHERE dg_username = '{$clean['username']}' LIMIT 1","该用户已经 ...

  9. boost::share_ptr用法

    boost中提供了几种智能指针方法:scoped_ptr shared_ptr intrusive_ptr weak_ptr,而标准库中提供的智能指针为auto_ptr. 这其中,我最喜欢,使用最多的 ...

  10. VC实现URL编解码器

    //变化UTF8为了中国 void UTF8ToGB(CString& szstr) { WCHAR* strSrc; TCHAR* szRes; int i = MultiByteToWid ...