我们在使用数据库的时候,为了增加访问速度,经常会给某张表增加一些索引。单列索引我之前已经写过一篇文章提过,今天我们主要讲一下怎么使用多列索引,和多列索引的最左前缀原则。

关于最左前缀的误解

早些年读高性能mysql这本书的时候,当年感觉书中对于最左前缀的描述相对来说晦涩一点,并且没有详细的例子来说明具体什么是最左前缀,所以在一段时间内我都以为where还要按照顺序写才能触发最左前缀,其实并不是这样。

假设表结构如下

CREATE TABLE `testindex` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `name` varchar(25) NOT NULL DEFAULT '',
  `age` int(10) NOT NULL DEFAULT '0',
  `sex` tinyint(1) NOT NULL DEFAULT '1',
  PRIMARY KEY (`id`),
  KEY `multi_index` (`name`,`age`,`sex`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

测试数据:

INSERT INTO `test`.`testindex` (`id`, `name`, `age`, `sex`) VALUES ('1', 'zhangsan', '20', '1');
INSERT INTO `test`.`testindex` (`id`, `name`, `age`, `sex`) VALUES ('2', 'zhangsan', '20', '0');
INSERT INTO `test`.`testindex` (`id`, `name`, `age`, `sex`) VALUES ('3', 'zhangsan', '19', '1');
INSERT INTO `test`.`testindex` (`id`, `name`, `age`, `sex`) VALUES ('4', 'zhangsan', '19', '0');
INSERT INTO `test`.`testindex` (`id`, `name`, `age`, `sex`) VALUES ('5', 'lisi', '20', '1');
INSERT INTO `test`.`testindex` (`id`, `name`, `age`, `sex`) VALUES ('6', 'lisi', '20', '0');
INSERT INTO `test`.`testindex` (`id`, `name`, `age`, `sex`) VALUES ('7', 'lisi', '19', '1');
INSERT INTO `test`.`testindex` (`id`, `name`, `age`, `sex`) VALUES ('8', 'lisi', '19', '0');

这个表中,定义了一个多列索引(组合索引)名字为multi_index,分别由name,age,sex三个字段组成,那么如下几条语句是否会有差异呢:

select id from testindex where name ='zhangsan' and age = 20 and sex = 1;
select id from testindex where name ='zhangsan' and sex = 1 and age = 20;
select id from testindex where age = 20 and name ='zhangsan' and sex = 1;

使用explain来查看一下

EXPLAIN select id from testindex where name ='zhangsan' and age = 20 and sex = 1;
EXPLAIN select id from testindex where name ='zhangsan' and sex = 1 and age = 20;
EXPLAIN select id from testindex where age = 20 and name ='zhangsan' and sex = 1;

三条语句输出的结果一模一样,都是使用了multi_index,事实证明,mysql并不以字段在where中出现的先后顺序来判断是否触发多列索引。实际上我们想一下,mysql也不会做那么不灵活的功能出来。

最左前缀是说的什么顺序

最左前缀其实说的是,在where条件中出现的字段,如果只有组合索引中的部分列,则这部分列的触发索引顺序,是按照定义索引的时候的顺序从前到后触发,最左面一个列触发不了,之后的所有列索引都无法触发。这句话有点绕,我们来以上面三列的组合索引举例子,其索引中包含了三列

  • name
  • age
  • sex

mysql在多列索引的时候不按照where的先后顺序,那么排列组合只有三种 如下:

# 1.where中包含name age(谁放前面无所谓)
EXPLAIN select id from testindex where name ='zhangsan' and age = 20;
# 2.where中包含name sex(谁放前面无所谓)
EXPLAIN select id from testindex where name ='zhangsan' and sex = 1;
# 3.where中包含age sex(谁放前面无所谓)
EXPLAIN select id from testindex where age =20 and sex = 1;

其中3就没有使用索引了,也就是说索引顺序是(name,age,sex)的手,where条件中如果没有name,即使有age和sex字段也不能触发索引。其扫描的条数是8行

那应该会有人想问,那如果有name和sex,中间跨过了age,是能触发索引呢,还是不能触发索引呢。答案是能!但是只能使用name部分的索引来过滤数据。

在explain中的rows中证明了这一点,其中1的扫描行数是2行,2的扫描行数是4行。就是因为没有使用到sex这一列。

最左前缀的后置模糊搜索
EXPLAIN select id from testindex where name like 'zhang%' and age = 20;

explain的结果证明,扫描行数为4行。也就是说如果使用了like这种不完全匹配的,后面的索引就失效了。为了针对这种类似的情况,mysql5.6之后,除了一种新功能叫索引下推。这个以后再进行介绍。

索引覆盖

有那么一种情况下,你想要查询的所有列,已经包含在你的组合索引的字段中,本来二级索引是只存储了索引数据和主键,如果需要的数据索引中不能满足,就需要回表查询数据。而这种索引覆盖了所有想要的列的情况,就就不需要再回表查询具体的数据。这就是索引覆盖。