MySQL查询语句EXPLAIN解析

  从MySQL服务器获取正确结果可能会有不止一种方法,但是这些方法所花费的代价很可能是不同的,在MySQL中为了找出这种花费最少、效率最高的方法肯定是大家最希望的。相比于传统基于规则的优化,MySQL和现代绝大多数数据库都是实现基于代价的优化方式,他们在处理查询的时候会创建多种可用方法,然后参考各种处理的消耗信息以及目标数据库表的统计分布信息,计算各个执行计划的代价,从中选择代价最小的计划去执行,所以找到最优的执行计划就是优化器所要负责的功能。
  在以前的版本中,数据库相关统计信息是由各存储引擎在内存中进行管理的,由于这些信息是动态的,很容易造成主从服务器对相同的查询选择不用执行计划的情况。从MySQL5.6开始,InnoDB采用表的形式管理相关统计信息,这样就可以将统计信息同步给从服务器,防止主、从服务器的执行计划出现差异。
  通过EXPLAIN命令可以查看SQL语句的执行计划,后面直接跟SELECT语句即可,如果想看DML语句的执行计划,则可以先将其转换成带WHERE条件的SELECT语句,再使用EXPLAIN分析即可,然后执行完EXPLAIN后会返回一个包含执行计划信息的相关表格,不过据称新版本的MySQL已经提供了直接通过EXPLAIN分析INSERT、UPDATE等操作的执行计划了。

1. id (query id)

  如果一个SELECT查询包含有多个子查询语句,则EXPLAIN返回的信息表会将每个子查询都赋予不同的ID列并显示出来,同时还包括子查询需要创建临时表的情况。如果一个SELECT语句需要连接多个数据表,执行计划把连接的表都进行显示,并且把他们都赋予相同的ID列值,即ID值不会增加。

1
2
SELECT e.emp_no, e.first_name, s.from_date, s.salary FROM employees e, salaries s
WHERE e.emp_no = s.emp_no LIMIT 10;

  信息表越显示在上方的结果,也就是ID值越小的结果,表示越为查询的外部、或越先访问的数据表;相反越显示在下方的记录,即ID值越大的结果,则为越是查询的内部、或越是后访问的数据表。

2. select_type (type of statement)

  (a) SIMPLE
  表示不需要UNION操作或者不包含子查询的简单SELECT查询情形。无论整个查询语句有多么的复杂,返回的执行计划中SIMPLE类型的查询只能有1个,而且通常是外侧SELECT查询。
  (b) PRIMARY
  在一个需要UNION操作或者包含子查询的SELECT查询执行计划中,位于最外层的单位查询为PRIMARY,同样PRIMARY类型的查询只能存在一个。
  (c) UNION
  由UNION操作联合而成的所有单位SELECT查询中,除了一个单位查询为DERIVED之外,所有其他所有单位查询都为UNION,而第一个单位查询的select_type为DERIVED,它是一个用于存储UNION后查询结果的临时表。

1
2
3
SELECT * FROM ( ( SELECT emp_no FROM employees e1 LIMIT 10 ) UNION ALL
( SELECT emp_no FROM employees e2 LIMIT 10 ) UNION ALL
( SELECT emp_no FROM employees e3 LIMIT 10 ) ) tbl_name;

  上面的tbl_name是必须的,否则会提示”Every derived table must have its own alias”。
  (d) DEPENDENT UNION
  此处表示UNION或者UNION ALL联合而成的单位查询受外部影响。
  我们将单位SELECT查询中还包含其他单位SELECT的时候,内部包含的SELECT会被称为子查询,在通常情况下包含子查询的时候子查询会比外部查询优先执行,而且这种处理方式的速度会非常快。不过对于select_type包含DEPENDENT关键字的子查询,由于它需要依赖外部查询,所以子查询绝对不会比外部查询先执行,而是通过依次迭代外部查询来执行子查询,所以包含DEPENDENT关键字的子查询执行往往比较低效。

1
2
3
SELECT * FROM employees e1 WHERE e1.emp_no IN 
( SELECT e2.emp_no FROM employees e2 WHERE e2.first_name='Matt'
UNION SELECT e3.emp_no FROM employees e3 WHERE e3.last_name='Matt' ) ;

  其实上面的查询语句,MySQL会默认进行IN-to-EXISTS优化,他总是先读取外部的employees表,然后再迭代执行子查询,所以employees的数据会对子查询产生影响,形成了DEPENDENT SUBQUERY和DEPENDENT UNION。既上面的查询和下面的查询是等价的:

1
2
3
SELECT * FROM employees e1 WHERE EXISTS
( SELECT e2.emp_no FROM employees e2 WHERE e2.first_name='Matt' AND e1.emp_no = e2.emp_no
UNION SELECT e3.emp_no FROM employees e3 WHERE e3.last_name='Matt' AND e1.emp_no = e3.emp_no ) ;

  (e) UNION RESULT
  为UNION或者UNION ALL查询结果的临时表,因为UNION RESULT在实际查询中不是单位查询,所以没有单独的ID值(该列为NULL)。
  (f) SUBQUERY
  此处的SUBQUERY仅指除FROM子句之外所使用的子查询,因为FROM子句所使用的子查询的select_type标示为DERIVED类型,其他子查询(通常处于WHERE子句中)被标识为SUBQUERY。
  (g) DEPENDENT SUBQUERY
  如果子查需要使用外部SELECT查询中定义的列,则此时该子查询为DEPENDENT SUBQUERY,同样的这种查询处理速度较慢。如上面(d)列子,这个时候最好想办法去除子查询和外部查询的依赖。
  (h) DERIVED
  DERIVED类型的子查询意味着要为单位查询的执行结果在内存或者磁盘上创建临时数据表保存(称为派生表),新版本的数据库会为派生表创建索引以优化和其他表连接时的性能问题。当为查询调优而查看执行计划的时候,首先要看select_type是否为DERIVED,如果可能话应当尽量使用连接处理来代替子查询。
  该表较小时可能存在于内存中(而不会对性能造成很大影响),不过如果数据量较大就会动用磁盘,会导致查询的性能下降。
  (i) UNCACHEABLE SUBQUERY
  注意的是此处的子查询缓存功能和系统的查询缓存无关。从查询语句表面看来即使子查询只出现一次,但也不一定意味着该子查询实际上只执行一次,通常情况下如果可以,在执行相同条件的子查询时前一次子查询的结果会被存储到内部缓存空间,后序相同条件的子查询可以直接使用前一次执行结果。如果子查询是DEPENDENT SUBQUERY,那么也会根据所依赖外部查询的列值为单位进行缓存,而在内部,数据库会将参数、子查询的结果一起保存到内部临时表中,将这些数据聚拢在一起后创建唯一索引,然后方便在新的子查询到来时可以快速的检索。
  此处的UNCACHEABLE SUBQUERY主要指子查询中含有用户变量、含有每次调用都有不同结果值的函数时候(比如UUID、RAND)、或者调动了非确定性的存储例程的时候,select_type会被标记为UNCACHEABLE SUBQUERY从而导致子查询无法被缓存。在写语句的时候要注意到这一点,通常要引导MySQL优化器最大可能地进行缓存。
  (j) UNCACHEABLE UNION
  跟UNCACHEABLE SUBQUERY相类似的情形。
  (k) MATERIALIZED
  主要用于优化FROM子句或IN(subquery)中的子查询,即某些查询会先将子查询内容执行后成为具体化的临时表,然后再将临时表和外部查询的数据表连接起来,从而避免先查询外部表,然后依据外部表的每一条记录再执行一次子查询这种低效率的DEPENDENT QUERY。

1
2
SELECT * FROM employees e WHERE e.emp_no IN
( SELECT emp_no FROM salaries WHERE salary BETWEEN 100 AND 1000 ) ;

  上述表就是将括号中的子查询先具体化,然后通过emp_no和外部表进行连接来优化查询的。当在执行计划中table列显示的形式,则表示该子查询已经被具体化为临时表了。另外需要说明的是,在MySQL中FROM中的子查询必须要带有别名,否则会报错。

3. table (table referenced)

  执行计划在显式的时候是以数据表为基准,对应表会显示在table列的。如果数据表有别名,则会选择使用数据表别名显示,如果是不涉及数据表的查询,该列会显示为NULL。table列还经常会出现或者类似的名字,他们表示对应的表是临时表(派生表),而其中的数字则对应为查询ID的引用。

4. type (access type)

  type列表示数据库以何种方式读取各数据表的记录,常常观察该字段确定索引是否被高效使用了。下面列出了12种访问数据的方式,除了最后的ALL为全表扫描之外,其他方式都使用到了索引。
  (a) system
  访问只有1条记录的数据表、或没有记录的空表的时候,访问方法显示为system。该方法只在MyISAM或MEMORY表,InnoDB不会使用。
  (b) const
  查询中含有使用主键或者唯一键列的WHERE条件子句时,通常采用const方式处理这类查询,这类查询对主键或者唯一键的所有列使用相同条件检索确保,因此能够确保只返回1行记录。对于多列生成的组合主键或组合唯一索引,需要所有的列都存在于WHERE中构成同等条件的时候,才会使用const访问方法,否则就是普通的索引检索ref。
  这里显示const的原因,还在于如果这类查询作为某个子查询存在的时候,数据库优化器会直接使用常量代替这个子查询进行优化。
  (c) eq_ref
  只会出现在需要连接多个表的查询执行计划中,在连接中先读取的数据表(也叫做驱动表)的列值作接下来要读取数据表的主键或唯一NOT NULL列的检索条件的时候,这种方法为eq_ref,使用eq_ref方法能保证第二个数据表中必有一条记录的情形。同样如果是组合主键或组合唯一NOT NULL辅助索引,则必须全部的列都参与进来确保只有一条记录的时候才可以。

1
2
SELECT * FROM dept_emp de, employees e
WHERE e.emp_no = de.emp_no AND de.dept_no = 'd005';

  (d) ref
  对于非主键或者没有唯一键约束的索引使用时使用相等条件进行检索,则会显示为ref,该类型允许返回满足多于1条记录的情况。相等比较运算符包括=、<=>。
  (e) ref_or_null
  该访问方法和ref类似,不过该方法增加了NULL的比较,比如to_date=’1985-02-03’ OR to_date is NULL。
  (f) range
  表示索引范围扫描访问方法,在检索条件为<、>、IS NULL、BETWEEN、IN、LIKE等运算符的时候而非相等条件检索。
  (g) index_merge
  这是唯一需要访问操作多个索引的访问方式,它会先使用两个以上的索引分别获得搜索结果后,然后再将他们合并。比如某个查询由WHERE中两个查询条件OR形式的连接,而且两个查询使用了不同索引的情况下,会使用index_merge的访问方式。

1
SELECT * FROM employees WHERE emp_no BETWEEN 10001 AND 10010 OR first_name='Smith';

  不过上述查询却使用了全表扫描。
  (h) index
  index是一种从头到尾执行索引全扫描的方法,所以它的效率是比较低的,不过相对于主键全表扫描的形式,因为索引条目通常要比主键条目小很多,所以这种顺序访问的效率也会比后者快一些。当查询不能通过range、const、ref方式使用索引的时候,如果满足任一条件:(a) 仅使用index中的列就可以处理查询请求,不需要读取主键获取其他数据信息;(b) 可以使用索引进行排序或者编组的情况,避免额外的排序操作。那么优化器就可以选择使用index索引全扫描的访问方式。

1
SELECT * FROM departments GROUP BY dept_name DESC LIMIT 10;

  上述语句使用到了dept_name进行分组,所以依次逆序访问10个索引就可以满足条件了。
  (i) ALL
  全表扫描的方式,会从头到尾读取数据表中的全部记录,然后删除不需要的记录后返回符合要求的记录。在整个表记录较少(比如1个页构成)、或WHERE|ON中没有索引可用、或者优化器觉得使用索引满足条件的行太多的时候,会考虑使用全表扫描的方式访问数据。InnoDB提供了一次读取多页的“预读”功能,可能最开始的读取是前台线程主动发起的,然后存储引擎后台线程会接收执行预先读取页面的操作,从最开始的每次读取4~8个页,然后数量不断增加,最大可以一次读取64个数据页,从而加快了全表扫描、全索引扫描类型的访问效率。
  注意:当产生ALL、index的查询,他们表示主键、索引的全表扫描,对于OLTP环境性能影响加大,考虑添加适当的索引或者更改查询条件。

5. possible_keys (which keys could have been used)

  罗列了可能使用的候选索引条目,通常会把数据表的所有索引都罗列在该目录里面。

6. key (key that was used)

  显示的是最终在执行计划中选用的索引,在type不为index_merge的时候,该列最多只能显示1个索引。

7. key_len (length of used key)

  表示用于处理查询使用到的索引是用几个字节创建的,某些数据类型需要考虑字符集,而对于utf8字符统一按照每字符使用3字节计算。对于可为NULLABLE的列,数据库会使用额外的1字节存储表示该列值是否为NULL,所以索引用对于该类型列时候需要增加1字节。
  在新版本支持索引条件下推的情况下,不管是范围限制条件还是检测条件,只要用到索引的情况下这些条件就会被传递给存储引擎,此时key_len的计算方法有有差异了。

8. ref (columns compared to index)

  表示在type为ref类型的访问方法时候,该列提示了哪种类型的值被作为equal比较条件:如果指定了常数值则ref列值显示为const,若为其他数据表的列值,则该字段显示为对应的数据表名.列名。
  通常情况下我们不需要关注这一列,不过如果ref列显示为func,则表示数据库不是原封不动的使用参考值,而是先经过排序变换或者数值运算后再引用的,这种变换不一定是用户显式指定的,而且在数据库内部自动转换的时候也会在这里提示出来,比如连接不同字符集的字符串时候,或者将数值类型的数据列和字符串类型的数据列进行连接的时候。

9. rows (amount of rows searched)

  对于某个查询可能有多种执行计划可供选择实施,数据库会比较各种处理方式的代价,并最终选择一个代价最小的执行计划作为最终执行计划。执行计划的rows列用于判断执行计划效率预测的记录数,为优化器根据存储引擎的统计值预估的数值,其意思为处理查询时需要从磁盘读取与检查的记录数目。
  当Rows中显示的行数笔查询实际获取的记录数多的时候,最好检查查询是否可以正常使用索引,以及索引的质量是否可以充分缩小处理范围的数据构成。

10. Extra (additional information)

  查询计划中和性能相关的重要信息经常会显示在Extra列中。
  (a) const row not found
  表示查询执行计划中虽然使用const方式读取数据表,但是实际数据表中没有1条符合条件的记录。
  (b) Distinct
  表示对查询的结果进行DISTINCT处理。

1
SELECT DISTINCT d.dept_no FROM departments d, dept_emp de WHERE de.dept_no = d.dept_no;

  需要针对DISTINCT注意的是,如果SELECT列包含了DISTINCT,那么该操作将是对所有选择列而言的,而不仅仅是和DISTINCT紧邻的列。所以下面两个操作实质上是等价的,括号将被忽略:

1
2
SELECT DISTINCT first_name, last_name FROM employees;
SELECT DISTINCT (first_name), last_name FROM employees;

  在DISTINCT操作的时候如果操作列没有索引,那么数据库会创建临时表并在临时表上创建唯一索引实现去重的;如果操作列具有索引,那么数据库就会执行索引全扫描或者范围扫描,这种优化不会使用临时表,因而代价也小的多。
  (c) Full scan on NULL key
  这种情况通常出现在类似col1 IN(SELECT col2 FROM …)的查询中,当col1的值为NULL的时候,最终条件变为NULL IN(SELECT col2 FROM …),因为NULL为UNKNOWN,对于NULL IN(subquery):如果子查询有至少一条查询记录,则最终的比较结果为NULL;如果子查询未拥有任结果记录,则最终比较的结果为FALSE。这种情况下需要进行全表扫描操作。

1
SELECT d.dept_no, NULL IN (SELECT id.dept_name FROM departments id) FROM departments d;

  针对这种问题的优化,需要显式告诉优化器col1不可能为NULL就可以了。针对IN或NOT IN运算符,如果左侧值为NULL时,且子查询中没有做出对应的WHERE col1 IS NOT NUL作为限制条件,就会造成严重的性能问题。
  (d) Impossible HAVING
  对于HAVING子句一定不可能满足的情况会显示该信息,比如主键IS NULL的情况。
  (e) Impossible WHERE
  和上面情况类似,当WHERE总是为FALSE的时候。
  (f) Impossible WHERE nonticed after reading const tables | no matching row in const table
  上面一条的判断是在不实际读取数据的情况下就得知条件不可能了,这里可以看出数据库在选择执行计划的时候可能会执行一部分查询的,比如查询中有使用const形式访问方法,那么在制定执行计划阶段优化器会执行这种快速的查询,并将查询结果替换为原查询中的常数。
  这种情况就是在执行少量查询后再做判断,发现WHERE为FALSE的情况下的提示信息,比如给主键相等条件指定一个不存在的值。
  (g) NO matching min/max row
  当所有记录都不满足查询中的WHERE条件,并且查询中有类似MIN、MAX的集合函数时候,则会提示上述信息,并且该查询返回NULL作为聚合函数的结果。
  (h) Not exists
  在业务开发的时候时常会遇到访问一些表A中存在,但是表B中不存在的值,这主要通过NOT IN(subquery)或者NOT EXISTS运算符实现,称这种操作为反连接,其实这种需求也可以通过左外连接(left OUTER JOIN)变通实现,当数据量大的时候使用左外连接会比前面的方式更具有性能优势。比如下面将查询存在于dept_emp但是不存在于departments数据表的dept_no,就可以将两个数据表先外连接,再通过WHERE子句中查询外部表中dept_no列为NULL的记录,以获得通过INNER JOIN不存在的在被连接表中的数据:

1
2
3
SELECT count(*) FROM dept_emp de WHERE de.dept_no NOT IN (SELECT dept_no FROM departments);
SELECT * FROM dept_emp de LEFT OUTER JOIN departments d ON de.dept_no = d.dept_no
WHERE d.dept_no IS NULL;

  通过上面查询方式,数据库内部会做相关运算方面的优化,显示为Not exists,但是这里并不表示将上述查询转换成了NOT EXISTS。
  (i) Range checked for each record(index map:N)

1
SELECT * FROM employees e1, employees e2 WHERE e2.emp_no >= e1.emp_no;

  对于上述语句,在条件中使用了非相等连接的时候,每次从驱动表选取一条记录,相对应的判断的条件就会变化。这个时候优化器有全表扫描和索引范围读取两种执行方式(这个例子不太好,因为唯一的索引为主键),在e1.emp_no值不同的时候对应的代价也是有差异的,此时优化器在e1.emp_no比较小的时候采用全表扫描,而e1.emp_no很大的时候采用索引范围扫描,根据不同的条件优化数据的访问方式,而index map为N二进制中位为1代表的索引作为候选,上述候选索引只有主键。
  (j) Select tables optimizer away
当SELECT查询语句中只使用MIN()、MAX(),或者通过GROUP BY再访问MIN()、MAX()的时候,如果有合适的索引,就可以按照索引的有序性只读取索引第1个或者最后一个值就可以得到结果了。
  (k) Using filesort
  当处理ORDER BY排序需求的时候是可以使用索引的,但是当无合适索引可用的时候,服务器会再次对访问的记录进行排序,此时就会显示这个Using filesort标识。在执行排序的时候,MySQL会分配排序缓冲(Sort buffer)内存空间执行排序,如果待排序的数据量比较少,那么直接使用排序缓冲在内存中执行快速排序就可以完成;如果待排序的数量大于分配的排序缓冲,MySQL就会将排序记录分成多个片段并保存在磁盘,待每个片段执行完排序后,再进行多路合并生成最终的有序结果。排序缓冲的大小是可以设置的,通常是256k~512k之间,因为排序缓冲属于会话级的内存,无法被多个客户所共享,所以当并发量大并且排序操作多的时候,排序所占用的内存空间也会越大。
  在需要执行排序的时候,分为两种情况:
  如果Extra列只显示Using filesort,则表示查询没有连接,或者连接时候只对驱动表执行排序,这通常是比先连接再排序更加有效的排序方式。比如下面的连接,因为employees表的主键范围检索更加高效,而且被驱动表salaries上的连接列emp_no有索引,但是employees的主键和ORDER BY的last_name没有关系,所以需要执行排序操作,而排序列是驱动表上的列,所以优化器可以先排序再执行连接。这种操作是不涉及临时表的。

1
2
3
SELECT * FROM employees e, salaries s
WHERE e.emp_no = s.emp_no AND e.emp_no BETWEEN 100002 AND 100010
ORDER BY e.last_name;

  如果Extra列同时显示了Using temporary和Using filesort,则表示先执行连接并将连接后的数据放到临时表中,再对临时表中的结果执行排序操作,这是效率最差的排序方式。比如上面查询中排序列使用了被驱动表的数据列,就只能先执行连接,然后对连接后的结果进行排序了。

1
2
3
SELECT * FROM employees e, salaries s
WHERE e.emp_no = s.emp_no AND e.emp_no BETWEEN 100002 AND 100010
ORDER BY s.salary;

  在新版的数据库中,针对ORDER BY xxx LIMIT n这类查询,数据库优化成不是对整个表进行排序,而是创建优先级队列,然后使用队列执行排序,排序的成本就会降低很多。
  如果执行计划有Using filesort,通常会对系统带来比较高的负荷,如果可能应该对查询进行调优,或者创建索引。
  (l) Using index
  如果查询时不需要读取整行数据,只读取索引就可以满足查询需求,则会显示该标识(覆盖索引),这里还需要提醒的是InnoDB在每个辅助索引中是会包含主键值的,所以即使主键没有显式列在辅助索引中,实际上也是包含的。使用覆盖索引能够极大地提高处理查询的性能,但是滥用覆盖索引可能会导致索引列中包含太多的数据列,索引会变大而占用大量的内存,同时也使得存储或者修改记录变的很慢。
  这里的Using index和上面的type的index不同,后者表示的是索引全扫描。
  (m) Using index for group-by
  数据库处理GROUP BY都是先使用分组基准列进行排序,然后将排序结果进行分组操作,不过如果GROUP BY的分组列使用了BTree索引,就会按照已经排列好的顺序依次读取索引项,这样就可以直接进行分组处理即可,操作起来相当的高效。如果此时进行松散索引扫描,比如对于MIN()、MAX()请求,那么就只需要读取特定列(比如组合索引某个起始列对应最开始和最末尾的记录),而不用读取所有的索引记录,就会显示上述标识。
  带有GROUP BY子句的查询中如果含有AVG()、SUM()、COUNT()等函数的时候,处理查询时就需要读取所有索引,即使GROUP BY的列有了索引,但因为不是松散索引扫描,这些情况下也不会显示上述标识。
  **
(n) Using join buffer*
  对于进行连接的两个表,如果被驱动表连接列没有索引的话,按照常规情况应该是主动表每读取一条记录,被驱动表就需要执行全表扫面,这种形式的效率很低。这种情况下,数据库会将之前表读取的记录保存在临时的内存空间,每当需要的时候就重新使用,该临时保存记录的空间即为join buffer。

1
2
SELECT * FROM dept_emp de, employees e 
WHERE de.from_date >'2015-01-01' AND e.emp_no < 10900;

  (o) Using temporary
  表示查询时会使用临时表存储中间结果,临时表可以根据情况在内存也可以在磁盘上创建,比如无法使用索引的GROUP BY查询就会使用临时表进行排序。
  (p) Using where
  数据库的内部是由MySQL引擎和InnoDB引擎构成的,InnoDB引擎负责从磁盘或者内存读取或写入所需记录,MySQL引擎负责加工或者运算从InnoDB存储引擎得到的记录,仅当MySQL引擎需要进行数据加工并进行过滤处理时,才会显示Using where标识,而当存储引擎的数据原封不动地传递给客户端的时候,才不会显示这个标识。

1
SELECT * FROM employees WHERE emp_no BETWEEN 10001 AND 10100 AND gender='F';

  上式中emp_no为该表的主键,所以存储引擎使用主键上的范围条件取了100条记录,然后通过在MySQL引擎层再经过gender的限制条件对记录进行过滤操作,因此显示了Using where,此时如果存在(emp_no+gender)索引,那么存储引擎就可以直接使用索引得到最终的记录了。
  (q) FirstMatch(tbl_name)
  从MySQL 5.5开始当执行IN(subquery)类型查询的时候,优化器会将其转换为EXISTS(subquery)类型的查询,该操作被称之为IN-to-EXIST优化。如果在Extra字段中显示了FirstMatch(tbl_name),则表示在处理查询前已经将IN(subquery)转换成了EXISTS(subquery),而其中的tbl_name是外部数据表。

1
SELECT * FROM departments d WHERE d.dept_no IN (SELECT de.dept_no FROM dept_emp de);

  上述查询会先从d数据表中读取一条记录,然后执行子查询检索dept_emp数据表中的记录,一直到检索到符合条件的记录为止。
  (r) Using index condition

1
explain SELECT * FROM employees WHERE first_name LIKE 'Lee%' AND first_name LIKE '%matt';

  新版本的MySQL中如果使用了索引条件下推,对于索引列所有相关的范围限制条件和检查条件都会传递给搜索引擎,如果上表中对first_name创建了索引,则会显示这个索引下推的标识。
  (s) Using MRR
  该标识代表使用了Multi Range Read的优化功能。对于非覆盖索引,常常需要先通过辅助索引范围查找得到符合WHERE条件的记录,然后再通过主键读取查询所需其余列字段,不过后者将是通过随机访问的形式访问磁盘,非常的耗费资源加重系统负担。引入MRR的时候会先通过辅助索引读取一定量符合WHERE条件的记录,然后使用主键RowId进行全部排序,在按照主键排序的顺序从实际数据文件中读取剩余的数据列,从而减少随机读取数据文件的次数。

本文完!

参考