- ">mysql的文件夹

- 查看字符集
- MySQL配置文件
- 索引优化分析
- SQL执行加载顺序
- 七种JOIN理论
- 索引是什么
- 性能分析前提知识
- explain使用简介
- explain之id介绍
- explain之select_type和table介绍
- explain之type介绍
- explain之possible_keys和key介绍
- explain之key_len介绍
- explain之ref介绍
- explain之rows介绍
- explain之Extra介绍
- explain之热身Case
- 索引单表优化案例
- 索引两表优化案例
- 索引三表优化案例
- 索引失效1-跳过复合索引中间列
- 索引失效2-索引列上做额外操作
- 索引失效3-限定复合索引某列的范围
- 索引失效4-select *
- 索引失效5-!=或者<>
- 索引失效6-is null或者is not null
- 索引失效7-like以通配符%开头字符串
mysql的文件夹
查看字符集
修改:


MySQL配置文件
索引优化分析
SQL执行加载顺序
手写:
SELECT DISTINCT<select_list>FROM<left_table> <join_type>JOIN<right_table>ON<join_condition>WHERE<where_condition>GROUP BY<group_by_list>HAVING<having_condition>ORDER BY<order_by_condition>LIMIT<limit_number>
机读:
1 FROM <left_table>2 ON <join_condition>3 <join_type> JOIN <right_table>4 WHERE <where_condition>5 GROUP BY <group_by_list>6 HAVING <having_condition>7 SELECT8 DISTINCT <select_list>9 ORDER BY <order_by_condition>10 LIMIT <limit_number>

七种JOIN理论

索引是什么
MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。可以得到索引的本质:索引是数据结构。
索引的目的在于提高查询效率,可以类比字典。
如果要查“mysql”这个单词,我们肯定需要定位到m字母,然后从下往下找到y字母,再找到剩下的sql。
如果没有索引,那么你可能需要逐个逐个寻找,如果我想找到Java开头的单词呢?或者Oracle开头的单词呢?
是不是觉得如果没有索引,这个事情根本无法完成?
你可以简单理解为“排好序的快速查找数据结构”。
详解
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。下图就是一种可能的索引方式示例:
左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址。
为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到相应数据,从而快速的检索出符合条件的记录。
数据本身之外,数据库还维护着一个满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法,这种数据结构就是索引。
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。
我们平常所说的索引,如果没有特别指明,都是指B树(多路搜索树,并不一定是二叉的)结构组织的索引。其中聚集索引,次要索引,覆盖索引,复合索引,前缀索引,唯一索引默认都是使用B+树索引,统称索引。当然,除了B+树这种类型的索引之外,还有哈稀索引(hash index)等。
索引优劣势
优势
类似大学图书馆建书目索引,提高数据检索的效率,降低数据库的IO成本。
通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗。
劣势
实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的(占空间)
虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息。()
索引只是提高效率的一个因素,如果你的MysQL有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询。
总结
索引,空间换取时间。
索引分类和建索引命令语句
MySQL索引分类:
单值索引:即一个索引只包含单个列,一个表可以有多个单列索引。
唯一索引:索引列的值必须唯一,但允许有空值。
复合索引:即一个索引包含多个列。
基本语法:
创建
CREATE [UNIQUE] INDEX indexName ON mytable(columnName(length));
ALTER mytable ADD [UNIQUE] INDEX [indexName] ON (columnName(length));
删除
DROP INDEX [indexName] ON mytable;
查看
SHOW INDEX FROM tableName;
使用alter命令 - 有四种方式来添加数据表的索引
ALTER TABLE tbl_name ADD PRIMARY KEY (column_list);:该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL。
ALTER TABLE tbl name ADD UNIQUE index_name (column_list);:这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)。
ALTER TABLE tbl_name ADD INDEX index_name (column_list);:添加普通索引,索引值可出现多次。
ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list);:该语句指定了索引为FULLTEXT,用于全文索引。
索引结构与检索原理
MySQL索引结构
- BTree索引
- Hash索引
- full-text全文索引
- R-Tree索引
BTree索引检索原理
初始化介绍
一颗b+树,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P1、P2、P3,
P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。
真实的数据存在于叶子节点即3、5、9、10、13、15、28、29、36、60、75、79、90、99。
非叶子节点只不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。
查找过程
如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO。在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。
真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。
哪些情况适合建索引
主键自动建立唯一索引
频繁作为查询条件的字段应该创建索引
查询中与其它表关联的字段,外键关系建立索引
频繁更新的字段不适合创建索引,因为每次更新不单单是更新了记录还会更新索引
Where条件里用不到的字段不创建索引
单键/组合索引的选择问题,who?(在高并发下倾向创建组合索引)
查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
查询中统计或者分组字段
哪些情况不适合建索引
表记录太少
经常增删改的表
数据重复且分布平均的表字段,因此应该只为最经常查询和最经常排序的数据列建立索引。注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。
假如一个表有10万行记录,有一个字段A只有T和F两种值,且每个值的分布概率天约为50%,那么对这种表A字段建索引一般不会提高数据库的查询速度。
索引的选择性是指索引列中不同值的数目与表中记录数的比。如果一个表中有2000条记录,表索引列有1980个不同的值,那么这个索引的选择性就是1980/2000=0.99。一个索引的选择性越接近于1,这个索引的效率就越高
性能分析前提知识
MySQL Query Optimizer
Mysql中有专门负责优化SELECT语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的Query提供他认为最优的执行计划(他认为最优的数据检索方式,但不见得是DBA认为是最优的,这部分最耗费时间)
当客户端向MySQL请求一条Query,命令解析器模块完成请求分类,区别出是SELECT并转发给MySQL Query Optimizer时,MySQL Query Optimizer首先会对整条Query进行优化,处理掉一些常量表达式的预算直接换算成常量值。并对Query中的查询条件进行简化和转换,如去掉一些无用或显而易见的条件、结构调整等。然后分析Query 中的 Hint信息(如果有),看显示Hint信息是否可以完全确定该Query的执行计划。如果没有Hint 或Hint信息还不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据Query进行写相应的计算分析,然后再得出最后的执行计划。
MySQL常见瓶颈
- CPU:CPU在饱和的时候一般发生在数据装入内存或从磁盘上读取数据时候
- IO:磁盘I/O瓶颈发生在装入数据远大于内存容量的时候
- 服务器硬件的性能瓶颈:top,free,iostat和vmstat来查看系统的性能状态
explain使用简介
使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈。
官网地址
能干嘛
- 表的读取顺序
- 数据读取操作的操作类型
- 哪些索引可以使用
- 哪些索引被实际使用
- 表之间的引用
- 每张表有多少行被优化器查询
怎么玩
- explain + sql语句
- 执行计划包含的信息
- | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
mysql> select * from tbl_dept;+----+----------+--------+| id | deptName | locAdd |+----+----------+--------+| 1 | RD | 11 || 2 | HR | 12 || 3 | MK | 13 || 4 | MIS | 14 || 5 | FD | 15 |+----+----------+--------+5 rows in set (0.00 sec)mysql> explain select * from tbl_dept;+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------+| 1 | SIMPLE | tbl_dept | NULL | ALL | NULL | NULL | NULL | NULL | 5 | 100.00 | NULL |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------+1 row in set, 1 warning (0.00 sec)
explain之id介绍
select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
三种情况:
- id相同,执行顺序由上至下
- id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
- id相同不同,同时存在
id相同,执行顺序由上至下
id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
id相同不同,同时存在
id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行,
衍生=DERIVED DERIVED后面的数字对应id的值
小结
id越大越先查询
explain之select_type和table介绍
select_type:查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询。
select_type有哪些?
SIMPLE - 简单的select查询,查询中不包含子查询或者UNION。
PRIMARY - 查询中若包含任何复杂的子部分,最外层查询则被标记为。(也就是说最后加载的)
SUBQUERY - 在SELECT或WHERE列表中包含了子查询。
DERIUED - 在FROM列表中包含的子查询被标记为DERIVED(衍生)MySQL会递归执行这些子查询,把结果放在临时表里。
UNION - 若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中外层SELECT将被标记为:DERIVED。
UNION RESULT - 从UNION表获取结果的SELECT。
table:显示这一行的数据是关于哪张表的。
explain之type介绍
访问类型排列
type显示的是访问类型,是较为重要的一个指标,结果值从最好到最坏依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index >ALL
system>const>eq_ref>ref>range>index>ALL
一般来说,得保证查询至少达到range级别,最好能达到ref。
详细说明
system:表只有一行记录(等于系统表),这是const类型的特列,平时不会出现,这个也可以忽略不计。
const:表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快如将主键置于where列表中,MySQL就能将该查询转换为一个常量。
eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描。选择本地图片
ref:非唯一性索引扫描,返回匹配某个单独值的所有行,本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体。
range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引一般就是在你的where语句中出现了between、<、>、in等的查询。这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束语另一点,不用扫描全部索引。
index:Full Index Scan,index与ALL区别为index类型只遍历索引树。这通常比ALL快,因为索引文件通常比数据文件小(也就是说虽然all和Index都是读全表,但index是从索引中读取的,而all是从硬盘中读的)。
all:Full Table Scan,将遍历全表以找到匹配的行。
备注:一般来说,得保证查询至少达到range级别,最好能达到ref。
explain之possible_keys和key介绍
possible_keys
显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段火若存在索引,则该索引将被列出,但不一定被查询实际使用。
key
实际使用的索引。如果为NULL,则没有使用索引
查询中若使用了覆盖索引,则该索引仅出现在key列表中
explain之key_len介绍
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好
key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的
explain之ref介绍
显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。
由key_len可知t1表的idx_col1_col2被充分使用,col1匹配t2表的col1,col2匹配了一个常量,即 ‘ac’。
查询中与其它表关联的字段,外键关系建立索引。
explain之rows介绍
根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数。
explain之Extra介绍
包含不适合在其他列中显示但十分重要的额外信息。
Using filesort
说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成的排序操作称为”文件排序”
Using temporary
使了用临时表保存中间结果,MysQL在对查询结果排序时使用临时表。常见于排序order by和分组查询group by。
Using index
表示相应的select操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错!
如果同时出现using where,表明索引被用来执行索引键值的查找;
如果没有同时出现using where,表明索引用来读取数据而非执行查找动作。
如果同时出现using where,表明索引被用来执行索引键值的查找;
如果没有同时出现using where,表明索引用来读取数据而非执行查找动作。
覆盖索引(Covering Index),一说为索引覆盖。
理解方式一:就是select的数据列只用从索引中就能够取得,不必读取数据行,MySQL可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖。
理解方式二:索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据;当能通过读取索引就可以得到想要的数据,那就不需要读取行了。一个索引包含了(或覆盖了)满足查询结果的数据就叫做覆盖索引。
注意:
如果要使用覆盖索引,一定要注意select列表中只取出需要的列,不可select*,因为
如果将所有字段一起做索引会导致索引文件过大,查询性能下降。
[
](https://blog.csdn.net/u011863024/article/details/115470147)
Using where
表明使用了where过滤。
Using join buffer
使用了连接缓存。
impossible where
where子句的值总是false,不能用来获取任何元组。
select tables optimized away
在没有GROUPBY子句的情况下,基于索引优化MIN/MAX操作,或者对于MyISAM存储引擎优化COUNT()操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
*distinct
优化distinct操作,在找到第一匹配的元组后即停止找同样值的动作。
explain之热身Case

第一行(执行顺序4):id列为1,表示是union里的第一个select,select_type列的primary表示该查询为外层查询,table列被标记为,表示查询结果来自一个衍生表,其中derived3中3代表该查询衍生自第三个select查询,即id为3的select。【select d1.name… 】
第二行(执行顺序2):id为3,是整个查询中第三个select的一部分。因查询包含在from中,所以为derived。【select id,namefrom t1 where other_column=’’】
第三行(执行顺序3):select列表中的子查询select_type为subquery,为整个查询中的第二个select。【select id from t3】
第四行(执行顺序1):select_type为union,说明第四个select是union里的第二个select,最先执行【select name,id from t2】
第五行(执行顺序5):代表从union的临时表中读取行的阶段,table列的
[
](https://blog.csdn.net/u011863024/article/details/115470147)
索引单表优化案例
建表SQL
CREATE TABLE IF NOT EXISTS article(id INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,author_id INT(10) UNSIGNED NOT NULL,category_id INT(10) UNSIGNED NOT NULL,views INT(10) UNSIGNED NOT NULL,comments INT(10) UNSIGNED NOT NULL,title VARCHAR(255) NOT NULL,content TEXT NOT NULL);INSERT INTO article(author_id,category_id,views,comments,title,content)VALUES(1,1,1,1,'1','1'),(2,2,2,2,'2','2'),(1,1,3,3,'3','3');
mysql> select * from article;+----+-----------+-------------+-------+----------+-------+---------+| id | author_id | category_id | views | comments | title | content |+----+-----------+-------------+-------+----------+-------+---------+| 1 | 1 | 1 | 1 | 1 | 1 | 1 || 2 | 2 | 2 | 2 | 2 | 2 | 2 || 3 | 1 | 1 | 3 | 3 | 3 | 3 |+----+-----------+-------------+-------+----------+-------+---------+3 rows in set (0.00 sec)
案例
查询category_id为1且comments 大于1的情况下,views最多的article_id。
mysql> SELECT id, author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;+----+-----------+| id | author_id |+----+-----------+| 3 | 1 |+----+-----------+1 row in set (0.00 sec)
mysql> show index from article;+---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |+---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+| article | 0 | PRIMARY | 1 | id | A | 3 | NULL | NULL | | BTREE | | |+---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+1 row in set (0.00 sec)
mysql> explain SELECT id, author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+| 1 | SIMPLE | article | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 33.33 | Using where; Using filesort |+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+1 row in set, 1 warning (0.00 sec)
结论:很显然,type是ALL,即最坏的情况。Extra里还出现了Using filesort,也是最坏的情况。优化是必须的。
开始优化
新建索引+删除索引
mysql> create index idx_article_ccv on article(category_id,comments,views);Query OK, 0 rows affected (0.03 sec)Records: 0 Duplicates: 0 Warnings: 0mysql> show index from article;+---------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |+---------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+| article | 0 | PRIMARY | 1 | id | A | 3 | NULL | NULL | | BTREE | | || article | 1 | idx_article_ccv | 1 | category_id | A | 2 | NULL | NULL | | BTREE | | || article | 1 | idx_article_ccv | 2 | comments | A | 3 | NULL | NULL | | BTREE | | || article | 1 | idx_article_ccv | 3 | views | A | 3 | NULL | NULL | | BTREE | | |+---------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+4 rows in set (0.00 sec)
或者用下面sql创建索引:
ALTER TABLE 'article' ADD INDEX idx_article_ccv ( 'category_id , 'comments', 'views' );
创建后的效果
mysql> explain SELECT id, author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;+----+-------------+---------+------------+-------+-----------------+-----------------+---------+------+------+----------+---------------------------------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+---------+------------+-------+-----------------+-----------------+---------+------+------+----------+---------------------------------------+| 1 | SIMPLE | article | NULL | range | idx_article_ccv | idx_article_ccv | 8 | NULL | 1 | 100.00 | Using index condition; Using filesort |+----+-------------+---------+------------+-------+-----------------+-----------------+---------+------+------+----------+---------------------------------------+1 row in set, 1 warning (0.00 sec)
Extra里还是出现了Using filesort,创建这索引作用不大。
如果comments > 1换成comments = 1,可以让Using filesort消失,但不符题目要求。
mysql> explain SELECT id, author_id FROM article WHERE category_id = 1 AND comments = 1 ORDER BY views DESC LIMIT 1;+----+-------------+---------+------------+------+-----------------+-----------------+---------+-------------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+---------+------------+------+-----------------+-----------------+---------+-------------+------+----------+-------------+| 1 | SIMPLE | article | NULL | ref | idx_article_ccv | idx_article_ccv | 8 | const,const | 1 | 100.00 | Using where |+----+-------------+---------+------------+------+-----------------+-----------------+---------+-------------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)
既然创建这索引作用不大,删了它吧。
mysql> DROP INDEX idx_article_ccv ON article;Query OK, 0 rows affected (0.01 sec)Records: 0 Duplicates: 0 Warnings: 0
缘由
type变成了range,这是可以忍受的。但是extra里使用Using filesort仍是无法接受的。
但是我们已经建立了索引,为啥没用呢?
这是因为按照BTree索引的工作原理,先排序category_id,如果遇到相同的category_id则再排序comments,如果遇到相同的comments 则再排序views。
当comments字段在联合索引里处于中间位置时,因comments > 1条件是一个范围值(所谓range),MySQL无法利用索引再对后面的views部分进行检索,即range类型查询字段后面的索引无效。
改进
跟上次创建索引相比,这次不为comments字段创建索引。
mysql> create index idx_article_cv on article(category_id, views);Query OK, 0 rows affected (0.01 sec)Records: 0 Duplicates: 0 Warnings: 0
再次explain
mysql> explain SELECT id, author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;+----+-------------+---------+------------+------+----------------+----------------+---------+-------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+---------+------------+------+----------------+----------------+---------+-------+------+----------+-------------+| 1 | SIMPLE | article | NULL | ref | idx_article_cv | idx_article_cv | 4 | const | 2 | 33.33 | Using where |+----+-------------+---------+------------+------+----------------+----------------+---------+-------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)
可以看到,type变为了ref,Extra中的Using filesort也消失了,结果非常理想。
索引两表优化案例
新建SQL
CREATE TABLE IF NOT EXISTS class(id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,card INT(10) UNSIGNED NOT NULL,PRIMARY KEY(id));CREATE TABLE IF NOT EXISTS book(bookid INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,card INT(10) UNSIGNED NOT NULL,PRIMARY KEY(bookid));INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
创建后的结果:
mysql> select * from class;+----+------+| id | card |+----+------+| 1 | 17 || 2 | 2 || 3 | 18 || 4 | 4 || 5 | 4 || 6 | 8 || 7 | 9 || 8 | 1 || 9 | 18 || 10 | 6 || 11 | 15 || 12 | 15 || 13 | 12 || 14 | 15 || 15 | 18 || 16 | 2 || 17 | 18 || 18 | 5 || 19 | 7 || 20 | 1 || 21 | 2 |+----+------+21 rows in set (0.00 sec)mysql> select * from book;+--------+------+| bookid | card |+--------+------+| 1 | 8 || 2 | 14 || 3 | 3 || 4 | 16 || 5 | 8 || 6 | 12 || 7 | 17 || 8 | 8 || 9 | 10 || 10 | 3 || 11 | 4 || 12 | 12 || 13 | 9 || 14 | 7 || 15 | 6 || 16 | 8 || 17 | 3 || 18 | 11 || 19 | 5 || 20 | 11 |+--------+------+20 rows in set (0.00 sec)mysql>
开始explain分析
mysql> EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+| 1 | SIMPLE | class | NULL | ALL | NULL | NULL | NULL | NULL | 21 | 100.00 | NULL || 1 | SIMPLE | book | NULL | ALL | NULL | NULL | NULL | NULL | 20 | 100.00 | Using where; Using join buffer (Block Nested Loop) |+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+2 rows in set, 1 warning (0.00 sec)
type都是all,需要优化。
为book.card创建索引
mysql> ALTER TABLE `book` ADD INDEX Y(`card`);Query OK, 0 rows affected (0.01 sec)Records: 0 Duplicates: 0 Warnings: 0
可以看到第二行的type变为了ref,rows也变少了,优化比较明显。这是由左连接特性决定的。LEFT JOIN条件用于确定如何从右表搜索行,左边一定都有,所以右边是我们的关键点,一定需要在右表建立索引。
删除为book.card创建索引
mysql> drop index y on book;Query OK, 0 rows affected (0.02 sec)Records: 0 Duplicates: 0 Warnings: 0
为class.card创建索引
mysql> ALTER TABLE `class` ADD INDEX Y(`card`);Query OK, 0 rows affected (0.01 sec)Records: 0 Duplicates: 0 Warnings: 0
再次explain分析
mysql> EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+----------------------------------------------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+----------------------------------------------------+| 1 | SIMPLE | class | NULL | index | NULL | Y | 4 | NULL | 21 | 100.00 | Using index || 1 | SIMPLE | book | NULL | ALL | NULL | NULL | NULL | NULL | 20 | 100.00 | Using where; Using join buffer (Block Nested Loop) |+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+----------------------------------------------------+2 rows in set, 1 warning (0.00 sec)
可见右边是我们的关键点,要想优化需要在右表建立索引。
然后我们换用右连接RIGHT JOIN查询
mysql> EXPLAIN SELECT * FROM class right JOIN book ON class.card = book.card;+----+-------------+-------+------------+------+---------------+------+---------+--------------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+-------+------------+------+---------------+------+---------+--------------+------+----------+-------------+| 1 | SIMPLE | book | NULL | ALL | NULL | NULL | NULL | NULL | 20 | 100.00 | NULL || 1 | SIMPLE | class | NULL | ref | Y | Y | 4 | my.book.card | 1 | 100.00 | Using index |+----+-------------+-------+------------+------+---------------+------+---------+--------------+------+----------+-------------+2 rows in set, 1 warning (0.00 sec)
换成左边是我们的关键点,要想优化需要在左表建立索引。
小结
索引两表优化,左连接右表建索引,右连接左表建索引。
索引三表优化案例
新建SQL
CREATE TABLE IF NOT EXISTS phone(phoneid INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,card INT(10) UNSIGNED NOT NULL,PRIMARY KEY(phoneid))ENGINE=INNODB;INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
建后效果
mysql> select * from phone;+---------+------+| phoneid | card |+---------+------+| 1 | 10 || 2 | 13 || 3 | 17 || 4 | 5 || 5 | 12 || 6 | 7 || 7 | 15 || 8 | 17 || 9 | 17 || 10 | 14 || 11 | 19 || 12 | 13 || 13 | 5 || 14 | 8 || 15 | 2 || 16 | 8 || 17 | 11 || 18 | 14 || 19 | 13 || 20 | 5 |+---------+------+20 rows in set (0.00 sec)
复用到上一节book,class两表,移除它们原有的索引。
mysql> show index from class;+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+| class | 0 | PRIMARY | 1 | id | A | 21 | NULL | NULL | | BTREE | | || class | 1 | Y | 1 | card | A | 12 | NULL | NULL | | BTREE | | |+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+2 rows in set (0.01 sec)mysql> drop index y on class;Query OK, 0 rows affected (0.02 sec)Records: 0 Duplicates: 0 Warnings: 0mysql> show index from book;+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+| book | 0 | PRIMARY | 1 | bookid | A | 20 | NULL | NULL | | BTREE | | |+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+1 row in set (0.01 sec)
为phone.card和book.card创建新的索引。
mysql> alter table `phone` add index z(`card`);Query OK, 0 rows affected (0.01 sec)Records: 0 Duplicates: 0 Warnings: 0mysql> alter table `book` add index y(`card`);Query OK, 0 rows affected (0.01 sec)Records: 0 Duplicates: 0 Warnings: 0
explain三表连接
mysql> explain SELECT * FROM class LEFT JOIN book ON class.card = book.card LEFT JOIN phone ON book.card = phone.card;+----+-------------+-------+------------+------+---------------+------+---------+---------------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+-------+------------+------+---------------+------+---------+---------------+------+----------+-------------+| 1 | SIMPLE | class | NULL | ALL | NULL | NULL | NULL | NULL | 21 | 100.00 | NULL || 1 | SIMPLE | book | NULL | ref | y | y | 4 | my.class.card | 1 | 100.00 | Using index || 1 | SIMPLE | phone | NULL | ref | z | z | 4 | my.book.card | 1 | 100.00 | Using index |+----+-------------+-------+------------+------+---------------+------+---------+---------------+------+----------+-------------+3 rows in set, 1 warning (0.00 sec)
后2行的 type 都是ref且总 rows优化很好,效果不错。因此索引最好设置在需要经常查询的字段中。
结论
Join语句的优化
尽可能减少Join语句中的NestedLoop的循环总次数:“永远用小结果集驱动大的结果集”。
优先优化NestedLoop的内层循环,保证Join语句中被驱动表上Join条件字段已经被索引。
当无法保证被驱动表的Join条件字段被索引且内存资源充足的前提下,不要太吝惜JoinBuffer的设置。
索引失效1-跳过复合索引中间列
索引失效(应该避免)
1.最佳左前缀法则 - 如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过复合索引中间列。
2.不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描。
3.存储引擎不能使用索引中范围条件右边的列。
4.尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select *。
5.mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描。
6.is null, is not null 也无法使用索引。
7.like以通配符开头(’%abc…’),mysql索引失效会变成全表扫描的操作。
8.字符串不加单引号索引失效。
9.少用or,用它来连接时会索引失效。
全值匹配我最爱
新建SQL
CREATE TABLE staffs(id INT PRIMARY KEY AUTO_INCREMENT,`name` VARCHAR(24) NOT NULL DEFAULT'' COMMENT'姓名',`age` INT NOT NULL DEFAULT 0 COMMENT'年龄',`pos` VARCHAR(20) NOT NULL DEFAULT'' COMMENT'职位',`add_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT'入职时间')CHARSET utf8 COMMENT'员工记录表';INSERT INTO staffs(`name`,`age`,`pos`,`add_time`) VALUES('z3',22,'manager',NOW());INSERT INTO staffs(`name`,`age`,`pos`,`add_time`) VALUES('July',23,'dev',NOW());INSERT INTO staffs(`name`,`age`,`pos`,`add_time`) VALUES('2000',23,'dev',NOW());ALTER TABLE staffs ADD INDEX index_staffs_nameAgePos(`name`,`age`,`pos`);
mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July';+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-------+| 1 | SIMPLE | staffs | NULL | ref | index_staffs_nameAgePos | index_staffs_nameAgePos | 74 | const | 1 | 100.00 | NULL |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-------+1 row in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July' AND age=25;+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------------+------+----------+-------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------------+------+----------+-------+| 1 | SIMPLE | staffs | NULL | ref | index_staffs_nameAgePos | index_staffs_nameAgePos | 78 | const,const | 1 | 100.00 | NULL |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------------+------+----------+-------+1 row in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July' AND age=25 AND pos='dev';+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------------------+------+----------+-------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------------------+------+----------+-------+| 1 | SIMPLE | staffs | NULL | ref | index_staffs_nameAgePos | index_staffs_nameAgePos | 140 | const,const,const | 1 | 100.00 | NULL |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------------------+------+----------+-------+1 row in set, 1 warning (0.00 sec)
注意下面的explain
mysql> EXPLAIN SELECT * FROM staffs WHERE age=25 AND pos='dev';+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+| 1 | SIMPLE | staffs | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 33.33 | Using where |+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT * FROM staffs WHERE pos='dev';+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+| 1 | SIMPLE | staffs | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 33.33 | Using where |+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)
上面两个索引失效了
最佳左前缀法则 - 如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。
mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July' AND pos='dev';+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-----------------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-----------------------+| 1 | SIMPLE | staffs | NULL | ref | index_staffs_nameAgePos | index_staffs_nameAgePos | 74 | const | 1 | 33.33 | Using index condition |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-----------------------+1 row in set, 1 warning (0.00 sec)
索引失效2-索引列上做额外操作
不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描。
mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July';+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-------+| 1 | SIMPLE | staffs | NULL | ref | index_staffs_nameAgePos | index_staffs_nameAgePos | 74 | const | 1 | 100.00 | NULL |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-------+1 row in set, 1 warning (0.00 sec)mysql> EXPLAIN SELECT * FROM staffs WHERE left(NAME,4)='July';+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+| 1 | SIMPLE | staffs | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 100.00 | Using where |+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)
索引失效3-限定复合索引某列的范围
存储引擎不能使用索引中范围条件右边的列(我理解为限定复合索引某字段的范围会时索引失效,也就是>,<,between…and…谨慎用在复合索引某字段)
mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July';+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-------+| 1 | SIMPLE | staffs | NULL | ref | index_staffs_nameAgePos | index_staffs_nameAgePos | 74 | const | 1 | 100.00 | NULL |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-------+1 row in set, 1 warning (0.00 sec)mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July' AND age=25;+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------------+------+----------+-------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------------+------+----------+-------+| 1 | SIMPLE | staffs | NULL | ref | index_staffs_nameAgePos | index_staffs_nameAgePos | 78 | const,const | 1 | 100.00 | NULL |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------------+------+----------+-------+1 row in set, 1 warning (0.00 sec)mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July' AND age=25 AND pos='dev';+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------------------+------+----------+-------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------------------+------+----------+-------+| 1 | SIMPLE | staffs | NULL | ref | index_staffs_nameAgePos | index_staffs_nameAgePos | 140 | const,const,const | 1 | 100.00 | NULL |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------------------+------+----------+-------+1 row in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July' AND age>25 AND pos='dev';+----+-------------+--------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+-----------------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+-----------------------+| 1 | SIMPLE | staffs | NULL | range | index_staffs_nameAgePos | index_staffs_nameAgePos | 78 | NULL | 1 | 33.33 | Using index condition |+----+-------------+--------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+-----------------------+1 row in set, 1 warning (0.00 sec)
由age=25变成age>25后,type从ref变成range。
mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July' AND pos='dev';+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-----------------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-----------------------+| 1 | SIMPLE | staffs | NULL | ref | index_staffs_nameAgePos | index_staffs_nameAgePos | 74 | const | 1 | 33.33 | Using index condition |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-----------------------+1 row in set, 1 warning (0.00 sec)mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July' AND pos='dev' and age > 25;+----+-------------+--------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+-----------------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+-----------------------+| 1 | SIMPLE | staffs | NULL | range | index_staffs_nameAgePos | index_staffs_nameAgePos | 78 | NULL | 1 | 33.33 | Using index condition |+----+-------------+--------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+-----------------------+1 row in set, 1 warning (0.00 sec)
索引失效4-select *
尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select *
mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July' AND age=25 AND pos='dev';+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------------------+------+----------+-------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------------------+------+----------+-------+| 1 | SIMPLE | staffs | NULL | ref | index_staffs_nameAgePos | index_staffs_nameAgePos | 140 | const,const,const | 1 | 100.00 | NULL |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------------------+------+----------+-------+1 row in set, 1 warning (0.00 sec)mysql> EXPLAIN SELECT name,age,pos FROM staffs WHERE NAME='July' AND age=25 AND pos='dev';+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------------------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------------------+------+----------+-------------+| 1 | SIMPLE | staffs | NULL | ref | index_staffs_nameAgePos | index_staffs_nameAgePos | 140 | const,const,const | 1 | 100.00 | Using index |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------------------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)
我们最好按需索取,少用select *
mysql> EXPLAIN SELECT name,age,pos FROM staffs WHERE NAME='July' AND pos='dev';+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+--------------------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+--------------------------+| 1 | SIMPLE | staffs | NULL | ref | index_staffs_nameAgePos | index_staffs_nameAgePos | 74 | const | 1 | 33.33 | Using where; Using index |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+--------------------------+1 row in set, 1 warning (0.00 sec)mysql> EXPLAIN SELECT name,age,pos FROM staffs WHERE NAME='July' AND age>25 AND pos='dev';+----+-------------+--------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+--------------------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+--------------------------+| 1 | SIMPLE | staffs | NULL | range | index_staffs_nameAgePos | index_staffs_nameAgePos | 78 | NULL | 1 | 33.33 | Using where; Using index |+----+-------------+--------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+--------------------------+1 row in set, 1 warning (0.00 sec)mysql> EXPLAIN SELECT name FROM staffs WHERE NAME='July' AND age=25;+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------------+------+----------+-------------+| 1 | SIMPLE | staffs | NULL | ref | index_staffs_nameAgePos | index_staffs_nameAgePos | 78 | const,const | 1 | 100.00 | Using index |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)
索引失效5-!=或者<>
mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描。
mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July';+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-------+| 1 | SIMPLE | staffs | NULL | ref | index_staffs_nameAgePos | index_staffs_nameAgePos | 74 | const | 1 | 100.00 | NULL |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-------+1 row in set, 1 warning (0.00 sec)mysql> EXPLAIN SELECT * FROM staffs WHERE NAME!='July';+----+-------------+--------+------------+------+-------------------------+------+---------+------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+------+-------------------------+------+---------+------+------+----------+-------------+| 1 | SIMPLE | staffs | NULL | ALL | index_staffs_nameAgePos | NULL | NULL | NULL | 3 | 66.67 | Using where |+----+-------------+--------+------------+------+-------------------------+------+---------+------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)mysql> EXPLAIN SELECT * FROM staffs WHERE NAME<>'July';+----+-------------+--------+------------+------+-------------------------+------+---------+------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+------+-------------------------+------+---------+------+------+----------+-------------+| 1 | SIMPLE | staffs | NULL | ALL | index_staffs_nameAgePos | NULL | NULL | NULL | 3 | 66.67 | Using where |+----+-------------+--------+------------+------+-------------------------+------+---------+------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)
索引失效6-is null或者is not null
is null, is not null 也无法使用索引
mysql> EXPLAIN SELECT * FROM staffs WHERE NAME is null;+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Impossible WHERE |+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+1 row in set, 1 warning (0.00 sec)mysql> EXPLAIN SELECT * FROM staffs WHERE NAME is not null;+----+-------------+--------+------------+------+-------------------------+------+---------+------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+------+-------------------------+------+---------+------+------+----------+-------------+| 1 | SIMPLE | staffs | NULL | ALL | index_staffs_nameAgePos | NULL | NULL | NULL | 3 | 66.67 | Using where |+----+-------------+--------+------------+------+-------------------------+------+---------+------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)
Extra打印为Impossible WHERE,是因为我们在创建staffs表,设置name字段的属性为not null。
下面额外演示Extra为Impossible WHERE情况。
mysql> EXPLAIN SELECT * FROM staffs WHERE 1=1;+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------+| 1 | SIMPLE | staffs | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 100.00 | NULL |+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------+1 row in set, 1 warning (0.00 sec)mysql> EXPLAIN SELECT * FROM staffs WHERE 1!=1;+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Impossible WHERE |+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+1 row in set, 1 warning (0.00 sec)
索引失效7-like以通配符%开头字符串
like以通配符%开头(’%abc…’),mysql索引失效会变成全表扫描的操作。
mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July';+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-------+| 1 | SIMPLE | staffs | NULL | ref | index_staffs_nameAgePos | index_staffs_nameAgePos | 74 | const | 1 | 100.00 | NULL |+----+-------------+--------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-------+1 row in set, 1 warning (0.00 sec)mysql> EXPLAIN SELECT * FROM staffs WHERE NAME like '%July%';+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+| 1 | SIMPLE | staffs | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 33.33 | Using where |+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)mysql> EXPLAIN SELECT * FROM staffs WHERE NAME like '%July';+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+| 1 | SIMPLE | staffs | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 33.33 | Using where |+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)mysql> EXPLAIN SELECT * FROM staffs WHERE NAME like 'July%';+----+-------------+--------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+-----------------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+--------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+-----------------------+| 1 | SIMPLE | staffs | NULL | range | index_staffs_nameAgePos | index_staffs_nameAgePos | 74 | NULL | 1 | 100.00 | Using index condition |+----+-------------+--------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+-----------------------+1 row in set, 1 warning (0.00 sec)
问题:解决like ‘%字符串%’时索引不被使用的方法?
新建SQL
CREATE TABLE `tbl_user`(`id` INT(11) NOT NULL AUTO_INCREMENT,`name` VARCHAR(20) DEFAULT NULL,`age`INT(11) DEFAULT NULL,`email` VARCHAR(20) DEFAULT NULL,PRIMARY KEY(`id`))ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;INSERT INTO tbl_user(`name`,`age`,`email`)VALUES('1aa1',21,'a@163.com');INSERT INTO tbl_user(`name`,`age`,`email`)VALUES('2bb2',23,'b@163.com');INSERT INTO tbl_user(`name`,`age`,`email`)VALUES('3cc3',24,'c@163.com');INSERT INTO tbl_user(`name`,`age`,`email`)VALUES('4dd4',26,'d@163.com');
mysql> select * from tbl_user;+----+------+------+-----------+| id | name | age | email |+----+------+------+-----------+| 1 | 1aa1 | 21 | a@163.com || 2 | 2bb2 | 23 | b@163.com || 3 | 3cc3 | 24 | c@163.com || 4 | 4dd4 | 26 | d@163.com |+----+------+------+-----------+4 rows in set (0.00 sec)
创建索引前,先看看以下explain:
EXPLAIN SELECT NAME,age FROM tbl_user WHERE NAME LIKE '%aa%';EXPLAIN SELECT id FROM tbl_user WHERE NAME LIKE '%aa%';EXPLAIN SELECT NAME FROM tbl_user WHERE NAME LIKE '%aa%';EXPLAIN SELECT age FROM tbl_user WHERE NAME LIKE '%aa%';EXPLAIN SELECT id,NAME FROM tbl_user WHERE NAME LIKE '%aa%';EXPLAIN SELECT id,NAME,age FROM tbl_user WHERE NAME LIKE '%aa%';EXPLAIN SELECT NAME,age FROM tbl_user WHERE NAME LIKE '%aa%';EXPLAIN SELECT * FROM tbl_user WHERE NAME LIKE '%aa%';EXPLAIN SELECT id,NAME,age,email FROM tbl_user WHERE NAME LIKE '%aa%';
mysql> EXPLAIN SELECT NAME,age FROM tbl_user WHERE NAME LIKE '%aa%';+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+| 1 | SIMPLE | tbl_user | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 25.00 | Using where |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT id FROM tbl_user WHERE NAME LIKE '%aa%';+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+| 1 | SIMPLE | tbl_user | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 25.00 | Using where |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)mysql> EXPLAIN SELECT NAME FROM tbl_user WHERE NAME LIKE '%aa%';+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+| 1 | SIMPLE | tbl_user | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 25.00 | Using where |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)mysql> EXPLAIN SELECT age FROM tbl_user WHERE NAME LIKE '%aa%';+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+| 1 | SIMPLE | tbl_user | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 25.00 | Using where |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT id,NAME FROM tbl_user WHERE NAME LIKE '%aa%';+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+| 1 | SIMPLE | tbl_user | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 25.00 | Using where |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)mysql> EXPLAIN SELECT id,NAME,age FROM tbl_user WHERE NAME LIKE '%aa%';+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+| 1 | SIMPLE | tbl_user | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 25.00 | Using where |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)mysql> EXPLAIN SELECT NAME,age FROM tbl_user WHERE NAME LIKE '%aa%';+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+| 1 | SIMPLE | tbl_user | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 25.00 | Using where |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT * FROM tbl_user WHERE NAME LIKE '%aa%';+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+| 1 | SIMPLE | tbl_user | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 25.00 | Using where |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)mysql> EXPLAIN SELECT id,NAME,age,email FROM tbl_user WHERE NAME LIKE '%aa%';+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+| 1 | SIMPLE | tbl_user | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 25.00 | Using where |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)
没有创建索引,都全表查找。
现在创建索引
mysql> CREATE INDEX idx_user_nameAge ON tbl_user(NAME,age);Query OK, 0 rows affected (0.02 sec)Records: 0 Duplicates: 0 Warnings: 0
再执行上述一系列explain
mysql> EXPLAIN SELECT NAME,age FROM tbl_user WHERE NAME LIKE '%aa%';+----+-------------+----------+------------+-------+---------------+------------------+---------+------+------+----------+--------------------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+----------+------------+-------+---------------+------------------+---------+------+------+----------+--------------------------+| 1 | SIMPLE | tbl_user | NULL | index | NULL | idx_user_nameAge | 68 | NULL | 4 | 25.00 | Using where; Using index |+----+-------------+----------+------------+-------+---------------+------------------+---------+------+------+----------+--------------------------+1 row in set, 1 warning (0.00 sec)
看得出,用上索引(覆盖索引)
mysql> EXPLAIN SELECT id FROM tbl_user WHERE NAME LIKE '%aa%';+----+-------------+----------+------------+-------+---------------+------------------+---------+------+------+----------+--------------------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+----------+------------+-------+---------------+------------------+---------+------+------+----------+--------------------------+| 1 | SIMPLE | tbl_user | NULL | index | NULL | idx_user_nameAge | 68 | NULL | 4 | 25.00 | Using where; Using index |+----+-------------+----------+------------+-------+---------------+------------------+---------+------+------+----------+--------------------------+1 row in set, 1 warning (0.00 sec)mysql> EXPLAIN SELECT NAME FROM tbl_user WHERE NAME LIKE '%aa%';+----+-------------+----------+------------+-------+---------------+------------------+---------+------+------+----------+--------------------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+----------+------------+-------+---------------+------------------+---------+------+------+----------+--------------------------+| 1 | SIMPLE | tbl_user | NULL | index | NULL | idx_user_nameAge | 68 | NULL | 4 | 25.00 | Using where; Using index |+----+-------------+----------+------------+-------+---------------+------------------+---------+------+------+----------+--------------------------+1 row in set, 1 warning (0.00 sec)mysql> EXPLAIN SELECT age FROM tbl_user WHERE NAME LIKE '%aa%';+----+-------------+----------+------------+-------+---------------+------------------+---------+------+------+----------+--------------------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+----------+------------+-------+---------------+------------------+---------+------+------+----------+--------------------------+| 1 | SIMPLE | tbl_user | NULL | index | NULL | idx_user_nameAge | 68 | NULL | 4 | 25.00 | Using where; Using index |+----+-------------+----------+------------+-------+---------------+------------------+---------+------+------+----------+--------------------------+1 row in set, 1 warning (0.00 sec)
看得出,都用上索引(覆盖索引)
mysql> EXPLAIN SELECT id,NAME FROM tbl_user WHERE NAME LIKE '%aa%';+----+-------------+----------+------------+-------+---------------+------------------+---------+------+------+----------+--------------------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+----------+------------+-------+---------------+------------------+---------+------+------+----------+--------------------------+| 1 | SIMPLE | tbl_user | NULL | index | NULL | idx_user_nameAge | 68 | NULL | 4 | 25.00 | Using where; Using index |+----+-------------+----------+------------+-------+---------------+------------------+---------+------+------+----------+--------------------------+1 row in set, 1 warning (0.00 sec)mysql> EXPLAIN SELECT id,NAME,age FROM tbl_user WHERE NAME LIKE '%aa%';+----+-------------+----------+------------+-------+---------------+------------------+---------+------+------+----------+--------------------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+----------+------------+-------+---------------+------------------+---------+------+------+----------+--------------------------+| 1 | SIMPLE | tbl_user | NULL | index | NULL | idx_user_nameAge | 68 | NULL | 4 | 25.00 | Using where; Using index |+----+-------------+----------+------------+-------+---------------+------------------+---------+------+------+----------+--------------------------+1 row in set, 1 warning (0.00 sec)mysql> EXPLAIN SELECT NAME,age FROM tbl_user WHERE NAME LIKE '%aa%';+----+-------------+----------+------------+-------+---------------+------------------+---------+------+------+----------+--------------------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+----------+------------+-------+---------------+------------------+---------+------+------+----------+--------------------------+| 1 | SIMPLE | tbl_user | NULL | index | NULL | idx_user_nameAge | 68 | NULL | 4 | 25.00 | Using where; Using index |+----+-------------+----------+------------+-------+---------------+------------------+---------+------+------+----------+--------------------------+1 row in set, 1 warning (0.00 sec)
看得出,都用上索引(覆盖索引)
mysql> EXPLAIN SELECT * FROM tbl_user WHERE NAME LIKE '%aa%';+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+| 1 | SIMPLE | tbl_user | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 25.00 | Using where |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)mysql> EXPLAIN SELECT id,NAME,age,email FROM tbl_user WHERE NAME LIKE '%aa%';+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+| 1 | SIMPLE | tbl_user | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 25.00 | Using where |+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)
看得出,都没用上索引了,有email字段再,只能全表搜索。
覆盖索引(Covering Index),一说为索引覆盖。
理解方式一:就是select的数据列只用从索引中就能够取得,不必读取数据行,MySQL可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖。
理解方式二:索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据;当能通过读取索引就可以得到想要的数据,那就不需要读取行了。一个索引包含了(或覆盖了)满足查询结果的数据就叫做覆盖索引。
注意:
如果要使用覆盖索引,一定要注意select列表中只取出需要的列,不可select,因为
如果将所有字段一起做索引会导致索引文件过大,查询性能下降。
*小结
解决like ‘%字符串%’时索引不被使用的方法?复合索引,然后覆盖索引。


