当前位置: 代码网 > it编程>数据库>Mysql > MySql索引原理之联合索引与最左前缀原则、覆盖索引及索引条件下推详解

MySql索引原理之联合索引与最左前缀原则、覆盖索引及索引条件下推详解

2025年08月12日 Mysql 我要评论
准备工作,下面的演示都是基于user_innodb表:drop table if exists `user_innodb`;create table `user_innodb` ( `id` big

准备工作,下面的演示都是基于user_innodb表:

drop table if exists `user_innodb`;
create table `user_innodb` (
  `id` bigint(64) not null auto_increment,
  `name` varchar(255) not null,
  `gender` tinyint(1) not null,
  `phone` varchar(11) not null,
  primary key (`id`)
) engine=innodb default charset=utf8mb4;

1.联合索引与最左前缀原则

在平时开发中,我们最常见的是单列索引(比如主键primary key),但在我们需要多条件查询的时候,也会建立联合索引。单列索引可以看成是特殊的联合索引。比如我们在user表上面,给name和phone建立了一个联合索引。

alter table 可以用来创建索引,包括普通索引、unique索引或primary key索引:

alter table table_name add index index_name (column_list)
alter table table_name add unique (column_list)
alter table table_name add primary key (column_list)
alter table user_innodb add index comidx_name_phone(name,phone); -- 创建联合索引

1.1 联合索引是怎么组织的?

下图是b+tree的索引结构,是一个单值索引:

我们可以看到,所有非叶子结点的构成都是由两部分组成,索引值+指针,而单值索引说的就是在索引值这里就只有一个值(比如id),而联合索引在索引值可能会有多个值(比如name和phone)

相比于单值索引:

  1. 联合索引在 b+tree 中是复合的数据结构
  2. 由于 b+树本身是有序的,所以联合索引是从左到右的顺序来建立搜索树的(name在左边,phone在右边)。从上图可以看出来,name是有序的,phone是无序的。当name相等的时候,phone才是有序的。
  3. 当存储引擎是innodb时,叶节点存储的是数据/主键

问题一:联合索引是怎么查找数据的?

比如,我们使用 where name=‘bob’ and phone = ‘132xx’ 去查询数据的时候

  1. b+tree 会优先比较 name 来确定下一步应该搜索的方向,往左还是往右
  2. 如果 name相同的时候再比较 phone

但是如果查询条件没有name,就不知道第一步应该查哪个节点,因为建立搜索树的时候name是第一个比较因子,所以用不到索引。

问题二:联合索引与单值索引什么关系?

假设我们的项目里面有两个查询很慢:

select * from user_innodb where name= ?;
select * from user_innodb where name= ? and phone=?;

按照我们的想法,一个查询创建一个索引,所以我们针对这两条sql创建了两个索引,这种做法觉得正确吗?

create index idx_name on user_innodb(name); 
create index idx_name_phoneonuser_innodb(name,phone);

当我们创建一个联合索引的时候,用左边的字段name去查询的时候,也能用到索引,所以单独为name创建一个索引完全没必要。相当于建立了两个联合索引(name)、(name,phone)。

如果我们创建三个字段的索引index(a,b,c),相当于创建三个索引:index(a)、index(a,b)、index(a,b,c)。用 where b=? 和 where b=? and c=? 和where a=? and c=?是不能使用到索引的。因为不能不用第一个字段,不能中断。

1.2 最左前缀原则

因为联合索引中包含了多个字段,所以不能像单值索引那样直接使用就行。那需要遵守什么规则呢?
答:最左前缀原则:带头大哥不能死,中间兄弟不能断。

我们在建立联合索引的时候,一定要把最常用的列放在最左边。比如下面的三条语句,能用到联合索引吗?

  1. 使用两个字段,可以用到联合索引(注:两个字段的顺序颠倒并不影响,因为全值匹配时mysql会优化字段顺序)
explain select * from user_innodb where name= '张三' and phone='12345678910'

  1. 使用左边的name字段,可以用到联合索引:
explain select * from user_innodb where name= '张三'

  1. 使用右边的phone字段,无法使用索引,全表扫描:
explain select * from user_innodb where name= '12345678910'

从联合索引的结构中,我们看到了索引是已经排好序的,那我们如在遵守最左前缀原则的前提下,order by时用到索引(避免 filesort)?

  • 使用形式一:order by 索引最前列 ==> 整体有序。
  • 使用形式二:where + order by索引列 ==> 局部有序(为了最左前缀原则,尽量order by索引列(用where 保证中间不断开))

再说一句,分组(group by)的实质是先排序后分组,原则类似order by。where 高于 having,where中能限定但条件不要去having中限定

另外,不要在索引上做任何操作,因为可能会导致索引失效,转而全表扫描。

1.3 什么情况下会索引失效?

当索引列出现以下六种操作时常常出现索引失效:

  1. 使用函数(replace\substr\concat\sum count avg)、表达式、计算(+ - * /)。因为当前值改变后就无法与索引存的值匹配上。
select * from user_innodb where left(name, 3)='张三';-- left函数是一个字符串函数,它返回具有指定长度的字符串的左边部分
  1. 使用范围查询(!=,<=>,in)会导致右边列失效。因为二叉树的查找是 = 查找,若是一个范围的话无法继续下探。
    • 最左列用范围,该列也不会使用索引,全部索引列失效
    • 其余列用范围,当前列仍会使用索引,但右边索引列失效
select * from user_innodb where name='张三' and age > 22;
  1. like以通配符开头(‘%abc…’),mysql索引失效会变成全表扫描操作。因为无法判断%代表多少字符。
    • 方案一:like (‘abc%’)
    • 方案二:覆盖索引
select * from user_innodb where name like '%三';
  1. 字符串不加’ '索引失效。因为会出现出现隐式转换,相当于给索引列做了操作。
select * from user_innodb where name = 007;-- "007"从字符串变成了数字007
  1. 少用or,用它连接时很多情况下索引会失效
select * from user_innodb where name = '张三' or name = '李四';
  1. is null,is not null 无法使用索引
select * from user_innodb where name is null;

==> 对这一部分内容通过一首打油诗做个总结:

2.覆盖索引

回表:非主键索引,我们先通过索引找到主键索引的键值,再通过主键值查出索引里面没有的数据,它比基于主键索引的查询多扫描了一棵索引树,这个过程就叫回表。例如:select * from user_innodb where name = ‘青山’;

在辅助索引里面,不管是单列索引还是联合索引,如果select的数据列只用从索引中就能够取得,不必从数据区中读取,这时候使用的索引就叫做覆盖索引,这样就避免了回表。

我们先来创建一个联合索引:

create index idx_name_phoneonuser_innodb(name,phone);

这三个查询语句都用到了覆盖索引:

explain select name,phone from user_innodb where name='青山' and phone='13666666666';
explain select name fromuser_innodb where name='青山' and phone='13666666666'; 
explain select phone from user_innodb where name='青山' and phone='13666666666';

extra里面值为“using index”代表使用了覆盖索引。另外,select * ,用不到覆盖索引。很明显,因为覆盖索引减少了io次数,减少了数据的访问量,可以大大地提升查询效率。

3.索引条件下推(icp)

在讲icp前,我们再创建一张数据表(员工表),并且在last_name和first_name上面创建联合索引。

create table `employees`( 
    `emp_no`int(11)notnull,
    `birth_date`date null,
    `first_name`varchar(14)notnull,
    `last_name`varchar(16)notnull, 
    `gender`enum('m','f')notnull, 
    `hire_date`date null, 
    primarykey(`emp_no`)
)engine=innodbdefaultcharset=latin1;
alter table employees add index idx_lastname_firstname(last_name,first_name); -- 创建联合索引
insert into `employees`(`emp_no`,`birth_date`,`first_name`,`last_name`,`gender`,`hire_date`)values(1, null,'698','liu','f',null); 
insert into `employees`(`emp_no`,`birth_date`,`first_name`,`last_name`,`gender`,`hire_date`)values(2, null,'d99','zheng','f',null); 
insert into `employees`(`emp_no`,`birth_date`,`first_name`,`last_name`,`gender`,`hire_date`)values(3, null,'e08','huang','f',null); 
insert into `employees`(`emp_no`,`birth_date`,`first_name`,`last_name`,`gender`,`hire_date`)values(4, null,'59d','lu','f',null); 
insert into` employees`(`emp_no`,`birth_date`,`first_name`,`last_name`,`gender`,`hire_date`)values(5, null,'0dc','yu','f',null); 
insert into` employees`(`emp_no`,`birth_date`,`first_name`,`last_name`,`gender`,`hire_date`)values(6, null,'989','wang','f',null); 
insert into` employees`(`emp_no`,`birth_date`,`first_name`,`last_name`,`gender`,`hire_date`)values(7, null,'e38','wang','f',null); 
insert into` employees`(`emp_no`,`birth_date`,`first_name`,`last_name`,`gender`,`hire_date`)values(8, null,'0zi','wang','f',null); 
insert into` employees`(`emp_no`,`birth_date`,`first_name`,`last_name`,`gender`,`hire_date`)values(9, null,'dc9','xie','f',null); 
insert into` employees`(`emp_no`,`birth_date`,`first_name`,`last_name`,`gender`,`hire_date`)values(10, null,'5ba','zhou','f',null);

3.1 icp是干什么用的?

现在我们要查询所有姓wang,并且名字最后一个字是zi的员工,比如王胖子,王瘦子。查询的sql如下:

select * from employees where last_name='wang' and first_name like '%zi';

这条sql有两种执行方式:

根据联合索引查出所有姓wang的二级索引数据,然后回表,到主键索引上查询全部符合条件的数据(3 条数据)。然后返回给 server 层,在 server 层过滤出名字以zi结尾的员工。

注:索引的比较是在存储引擎进行的,数据记录的比较是在server层进行的。而当first_name的条件不能用于索引过滤时,server 层不会把first_name的条件传递给存储引擎,所以读取了两条没有必要的记录。如果将数据规模扩大,比如满足last_name='wang’的记录有100000条,就会有99999条没有必要读取的记录。

根据联合索引查出所有姓wang的二级索引数据(3个索引),然后从二级索引中筛选出first_name以zi结尾的索引(1个索引),然后再回表,到主键索引上查询全部符合条件的数据(1条数据),返回给server 层。

很明显,第二种方式到主键索引上查询的数据更少,但mysql在没开启icp前使用的都是第一种。

explain select * from employees where last_name='wang' and first_name like '%zi';

using where代表从存储引擎取回的数据不全部满足条件,需要在server 层过滤。先用last_name 条件进行索引范围扫描,读取数据表记录,然后进行比较,检查是否符合first_name like ‘%zi’ 的条件。此时3条中只有1条符合条件。

3.2 怎么开启icp?

开启命令如下:

set optimizer_switch='index_condition_pushdown=on';

开启后再查看此时的执行计划,using index condition:

把first_name like '%zi’下推给存储引擎后,只会从数据表读取所需的1条记录。索引条件下推(indexconditionpushdown),5.6以后完善的功能。只适用于二级索引。icp 的目标是减少访问表的完整行的读数量从而减少 i/o 操作。

到此这篇关于mysql索引原理之联合索引与最左前缀原则、覆盖索引及索引条件下推详解的文章就介绍到这了,更多相关mysql最左前缀原则内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

  • Mysql中的图形化界面方式

    一、安装软件navicat,sqlyog 这些软件都不错,不过都需要收费,当然也有破解版。下面用mysqlworkbench,它是官方提供的工具。二、使用操作这个软件本质是一个客户…

    2025年08月11日 数据库
  • MySQL在Centos7环境安装MySQL教程

    MySQL在Centos7环境安装MySQL教程

    说明:一、卸载内置环境1、卸载不要的环境[whb@vm-0-3-centos ~]$ ps ajx |grep mariadb # 先检查是否有mariadb存... [阅读全文]
  • MySQL中表的内连和外连详解

    一、内连接内连接实际上就是利用where子句对两种表形成的笛卡儿积进行筛选,前面学习的查询都是内连接,也是在开发过程中使用的最多的连接查询。select 字段 from 表1 in…

    2025年08月11日 数据库
  • MySQL基本查询方式(表的增删查改)

    一、createinsert [into] table_name [(column [, column] ...)] values (value_list) [, (value_l…

    2025年08月11日 数据库
  • MySQL之复合查询解读

    一、基本查询练习回顾1、查询工资高于500或岗位为manager的雇员,同时还要满足他们的姓名首字母为大写的j2、按照部门号升序而雇员的工资降序排序3、使用年薪进行降序排序4、显示…

    2025年08月11日 数据库
  • MySQL慢查询工具的使用小结

    MySQL慢查询工具的使用小结

    使用mysql的慢查询工具可以帮助开发者识别和优化性能不佳的sql查询。以下是详细深入的步骤和代码示例,帮助你使用mysql的慢查询工具来进行查询分析和优化。一... [阅读全文]

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com