索引的建立是为了更加高效的进行数据查询。如何正确的使用数据库索引,如何使索引不失效。以下几点是我们遵循的基本原则。
我们先回顾下MySQL语句的执行顺序。
SQL 执行顺序
想要优化SQL,必须清楚知道SQL的执行顺序,这样再配合explain才能事半功倍!
完整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>
- 3.2 SQL执行顺序
执行顺序
- 1、from <left_table><join_type>
- 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、select
- 8、distinct <select_list>
- 9、order by <order_by_condition>
- 10、limit <limit_number>

由SQL的执行顺序可知,首选会通过主体进行条件筛选,尽可能的减低查询的数据量,在通过select进行查询统计。也就是说我们所能优化的东西集中在第7条之前。
准备工作
2.1 创建表
- REATE TABLE `staffs` (
- `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
- `name` varchar(24) NOT NULL DEFAULT '' COMMENT '姓名',
- `age` int(11) NOT NULL DEFAULT '0' COMMENT '年龄',
- `pos` varchar(20) NOT NULL COMMENT '职位',
- `add_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间',
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2.2 插入基础数据
- INSERT INTO `test`.`staffs` (`id`, `name`, `age`, `pos`, `add_time`) VALUES ('1', '王二', '25', '总经理', '2018-05-22 09:45:44');
- INSERT INTO `test`.`staffs` (`id`, `name`, `age`, `pos`, `add_time`) VALUES ('2', 'July', '25', '实习生', '2018-05-22 09:45:58');
- INSERT INTO `test`.`staffs` (`id`, `name`, `age`, `pos`, `add_time`) VALUES ('3', '李四', '20', '实习生', '2018-05-22 09:46:04');
- INSERT INTO `test`.`staffs` (`id`, `name`, `age`, `pos`, `add_time`) VALUES ('4', '王玉', '21', '老板娘', '2018-05-22 09:46:17');
- INSERT INTO `test`.`staffs` (`id`, `name`, `age`, `pos`, `add_time`) VALUES ('5', '王五', '22', '服务员', '2018-05-22 09:46:26');
- INSERT INTO `test`.`staffs` (`id`, `name`, `age`, `pos`, `add_time`) VALUES ('6', '赵六', '80', '传菜生', '2018-05-22 09:46:45');
-
2.3 创建索引
- ALTER TABLE staffs ADD INDEX idx_staffs_nameAgePos (NAME, age, pos);
-
2.4 查询索引
SHOW INDEX FROM staffs

实战分析,结合前一篇来看 MySQL —— (MySQL优化 一)Explain 详解
MYSQL EXPLAIN解析一 EXTRA中的USING INDEX,USING WHERE,USING INDEX CONDITION
using index 和using where只要使用了索引我们基本都能经常看到,而using index condition则是在mysql5.6后新加的新特性
using index :使用覆盖索引的时候就会出现
using where:在查找使用索引的情况下,需要回表去查询所需的数据
using index condition:查找使用了索引,但是需要回表查询数据
using index & using where:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据
全值匹配指的是我要查询的语句的字段和顺序恰好和建立的索引的字段和顺序一致,否则索引失效。
正确的使用方式
EXPLAIN SELECT * FROM staffs WHERE NAME = 'July';

EXPLAIN SELECT * FROM staffs WHERE NAME = 'July' AND age = 25;

EXPLAIN SELECT * FROM staffs WHERE NAME = 'July' AND age = 25 AND pos = '实习生'

索引失效
EXPLAIN SELECT * FROM staffs WHERE age = 25 AND pos = '实习生';

EXPLAIN SELECT * FROM staffs WHERE pos = '实习生';

总结:由上面的结果我们可以看出,当索引是按照name,age,pos顺序建立的时候,如果查询条件不是以name开头,就会导致索引失效。总结一句话就是:带头大哥不能死!
3.2 最佳左前缀法则如果索引了多列,要遵守最左前缀法则,指的是查询从索引的最左前列开始并且不跳过索引中的列。
EXPLAIN SELECT * FROM staffs WHERE NAME = 'July' AND pos = '实习生';

如上图所示,如果是匹配了两个索引字段,那么key_len应该至少大于74,而且ref应该是两个const。由此可见,该查询语句只使用了name字段的索引,后面的索引就失效了。
✈ 总结:由上面的结果我们可以看出,当索引是按照name,age,pos顺序建立的时候,如果查询条件是以name开头,但是没有按顺序,就会导致后面的索引失效。总结一句话就是:中间兄弟不能断!
这里的任何操作包括计算、函数、(自动or手动)类型转换,会导致索引失效而转向全表扫描。
EXPLAIN SELECT * FROM staffs WHERE NAME = 'July';

EXPLAIN SELECT * FROM staffs WHERE left(NAME,4) = 'July';

EXPLAIN SELECT * FROM staffs WHERE NAME = 'July' AND age = 25 AND pos = '实习生';

EXPLAIN SELECT * FROM staffs WHERE NAME = 'July' AND age > 25 AND pos = '实习生';

总结:当我们使用age > 25这种范围查找的时候,type变为了range,并且key_len等于78,说明已经使用了name,和age的索引,但是,age的索引变为了范围排序,而并不是精确查找,导致后面的pos索引失效。所以总结就是:范围之后全失效!
尽量使用索引的查询即索引列和查询列一致,减少select *
EXPLAIN SELECT * FROM staffs WHERE NAME = 'July' AND age = 25 AND pos = '实习生';

EXPLAIN SELECT name,age,pos FROM staffs WHERE NAME = 'July' AND age = 25 AND pos = '实习生';

EXPLAIN SELECT name,age,pos FROM staffs WHERE NAME = 'July' AND age > 25 AND pos = '实习生';

EXPLAIN SELECT * FROM staffs WHERE NAME = 'July';

EXPLAIN SELECT * FROM staffs WHERE name != 'July';

EXPLAIN SELECT * FROM staffs WHERE name <> 'July';

EXPLAIN SELECT * FROM staffs WHERE name = 'July' or age = 25

3.3 一般性建议
对于单键索引,尽量选择针对当前query过滤性更好的索引
在选择组合索引的时候,当前query中过滤性最好的字段在索引字段顺序中,位置越靠前越好
在选择组合索引的时候,尽量选择可以能够包含当前query中的where子句中更多字段的索引
尽可能通过分析统计信息和调整query的写法来达到选择合适索引的目的
3.4 优化总结口诀
- 全值匹配我最爱,最左前缀要遵守;
- 带头大哥不能死,中间兄弟不能断;
- 索引列上少计算,范围之后全失效;
- LIKE百分写最右,覆盖索引不写星;
- 不能空值还有OR,索引失效要少用;
- VAR引号不可丢,SQL高级也不难;