四、联合索引最左前缀原理

在这里插入图片描述

B+Tree联合索引底层数据结构

单列索引:是指由一个字段(单个列)组成的索引;
联合索引:联合索引又叫复合索引,是指由多个字段(字段是有顺序的)组成的索引,比如:
CREATE TABLE users (
id int(11) NOT NULL AUTO_INCREMENT,
nick varchar(30) DEFAULT NULL,
phone varchar(25) NOT NULL,
password varchar(64) DEFAULT NULL,
email varchar(50) DEFAULT NULL,
account varchar(15) DEFAULT NULL,
create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_nick_phone (nick,phone,create_time) USING BTREE --联合索引
) ENGINE=InnoDB AUTO_INCREMENT=9980018 DEFAULT CHARSET=utf8mb4;

单列索引是特殊的联合索引,是联合索引字段列个数为1的特例;
联合索引叶子节点没有存储数据,节省磁盘空间,其数据结构如下:

在这里插入图片描述


索引键index(col1, col2, col3)如何排序?
先根据第一个字段col1排序,然后根据第二个字段col2排序,如果前两个一样,则根据第三个字段col3排序;
B+Tree索引适用于全键值匹配查询,键值范围匹配查询,键值前缀匹配查询;
其中键值前缀查询只适用于根据键值的最左前缀查询

联合索引的最左前缀原理

CREATE TABLE users (
id int(11) NOT NULL AUTO_INCREMENT,
nick varchar(30) DEFAULT NULL,
phone varchar(25) NOT NULL,
password varchar(64) DEFAULT NULL,
email varchar(50) DEFAULT NULL,
account varchar(15) DEFAULT NULL,
create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_nick_phone (nick,phone,create_time) USING BTREE --联合索引
) ENGINE=InnoDB AUTO_INCREMENT=9980018 DEFAULT CHARSET=utf8mb4;

1、全值匹配(全列匹配、全键值匹配)

联合索引中的每一个索引字段都是where查询的条件;
select * from users where nick =‘cat’ and phone = ‘13700000000’;
select * from users where phone = ‘13700000000’ and nick =‘cat’;
select * from users where phone = ‘13700000000’ and nick in(‘cat’);
select * from users where phone in (‘13700000000’) and nick in (‘cat’);
select * from users where nick = ‘cat’ or phone = ‘13700000000’;
select * from users where phone in (‘13700000000’) or nick in (‘cat’);

当按照索引中所有列进行精确匹配(这里精确匹配指“=”或“IN”匹配,每一列都能精确匹配)时,索引可以被用到;
注意,理论上索引对顺序是敏感的,但是由于MySQL的查询优化器会自动调整where子句的条件顺序以使用适合的索引,例如我们将where中的条件顺序颠倒,也用到了索引,效果一样;

2、最左前缀匹配
select * from users where nick = ‘cat’;
select * from users where nick = ‘cat’ and create_time= ‘2020-02-09’
select * from users where phone = ‘13700000000’

这种情况指的是只使用索引的第一列进行匹配,如果使用索引的第二列查询,无法命中索引;
如果where条件没有使用到最左列,无法使用索引,根据phone 、create_time查询无法使用索引,因为这两列不是最左列。

3、查询条件使用索引列的精确匹配,中间某个条件未提供
select * from users where nick = ‘cat’ and create_time= ‘2020-02-09’
用到索引了

4、查询条件中没有索引第一列
select * from users where phone = ‘13700000000’ create_time= ‘2020-02-09’
由于不是最左前缀,索引这样的查询显然不能使用索引,where条件中没最左列;

5、匹配列前缀字符串
这种情况指的是只匹配一个列值的开头部分,比如nick=’cat1129’,采用like过滤匹配 cat11%;
select * from users where nick like ‘cat11%’; --命中索引
注:这里匹配越精确,效率也越高,如果匹配 cat%,mysql查询优化器可能会进行全表扫描,比如所有的nick都是cat开头的,所以要尽可能的精确like的前缀;
如果where条件没有使用到最左列,无法使用索引,根据phone、create_time查询无法使用到索引,这两列不是最左列,也无法查询 nick like %at001 以结尾匹配查询也不行;
如果查询中有某个列的范围查询,则右边所有列都无法使用索引查找,比如
where nick = ‘cat’and phone like ‘13720010%’ and create_time = ‘2020-02-09’,该查询只能使用索引的前两列,优化的话,如果like的结果较少,可以用in;
假设phone 只有两个:13720000000 13720000001,或者只有10几个;
where nick = ‘cat’and phone in (13720000000, 13720000001) and create_time = ‘2020-02-09’;(全键值匹配的查询,更精准的定位,读取的数据更少,效率更好)

6、匹配范围值
select * from users where nick in (‘cat’, ‘cat10’); --走索引
select * from users where phone in (‘13720010000’, ‘13700040000’); --不走索引

匹配索引键的第一列范围值(必须是最左前缀),只能命中第一列的范围,范围列后面的列无法用到索引;
范围包括 >、<、!=、in、is null、between … and 等;

7、精确匹配某一列并范围匹配另一列
select * from users where nick = ‘cat’ and phone in (‘13720010000’, ‘13700040000’);
第一列精确匹配,第二列范围匹配;

8、查询条件中有函数或表达式
如果查询条件中有函数或表达式,则MySQL不会为该列使用索引;
explain select * from users where id + 1 = 2;
explain select * from users where left(nick, 5) = ‘cat99’;

9、只访问索引列的查询
只查询有索引的字段,这种查询叫“覆盖索引”查询,在索引键中就找到了数据,不需要获取数据行,也就是不需要回表中获取数据,性能得到提高,所以我们对于值需要查询一两个字段的业务,就不要采用select * from;
select nick, phone, create_time from users where phone = ‘1372001000’;走索引,覆盖索引
select * from users where phone = ‘1372001000’; 不走索引

10、排序分组查询
B+Tree索引是有序的数据结构,所以除了按索引键查询外,还可以按索引键进行order by排序;

11、分页limit查询
SELECT a.* FROM users where nick like ‘cat52%’ LIMIT 3000000, 15;
优化如下:覆盖索引查询 id
SELECT a.* FROM users a, (select id from users where nick like ‘cat52%’ LIMIT 3000000,15) b where a.id = b.id

索引离散性

既然索引可以加快查询速度,那么是不是只要是查询语句条件都建上索引?
答案是否定的,索引虽然加快了查询速度,但索引也有代价:
索引文件本身要消耗存储空间,同时索引会加重插入、删除和修改记录时的负担,MySQL在运行时也要消耗资源维护索引,因此索引并不是越多越好;
表记录比较少,例如一两千条甚至只有几百条记录的表,没必要建索引,让查询做全表扫描即可,一般记录在2000条以上可以酌情考虑建立索引;
列的离散性

列的离散性
公式:count(distinct col)/ count(col)
比值越大离散性越好,离散性越好则选择性越好,

在这里插入图片描述


索引的离散性较低不建议建索引,离散性较低也就是选择性较低(B+Tree要走很多个分叉去找数据),所谓索引的选择性(Selectivity),是指不重复的索引值(也叫基数,Cardinality)与表记录数(#T)的比值:
Index Selectivity = Cardinality / #T
选择性的取值范围为(0, 1],选择性越高的索引价值越大,这是由B+Tree的性质决定的;
。 如果列的离散性很差,一般创建索引的价值也不大;
可以采用联合索引提高列的离散性;
SELECT count(DISTINCT(concat(nick, phone)))/count() AS selectivity FROM users;
SELECT count(DISTINCT(concat(nick, right(phone,4))))/count(
) AS selectivity FROM users;

我们把这个前缀索引建上:
ALTER TABLE employees.employees ADD INDEX idx_nick_phone (nick, phone(4));
使用前缀索引兼顾了索引大小和查询速度,但其缺点是不能用于ORDER BY和GROUP BY操作,也不能用于覆盖索引Covering index(即当索引本身包含查询所需全部数据时,不再回表访问数据文件本身);

覆盖索引

如果查询列可通过索引节点中的关键字直接返回,则该索引称之为覆盖索引;
覆盖索引可减少数据库IO,可提高查询性能;
创建联合索引原则
1,经常用的列优先 【最左匹配原则】
2,选择性(离散性)高的列优先【离散性高原则】
3,宽度小的列优先【最少空间原则】 varchr 3 nchar(15)
explain select * from users where nick = ‘cat’;
explain select * from users where nick = ‘cat’ and phone = ‘13700000000’;

InnoDB的主键选择

在使用InnoDB存储引擎时,请永远使用一个与业务无关的自增字段作为主键;
经常也有人采用像手机号、身份证号、uuid这种唯一字段作为主键;
从数据库索引优化角度看,使用InnoDB引擎而不使用自增主键绝对不是一个好主意;
InnoDB使用聚集索引,数据记录本身被存于主键索引(一颗B+Tree)的叶子节点上,这就要求同一个叶子节点内的各条数据记录按主键顺序存放,当有一条新的记录插入时,MySQL会根据其主键将其插入适当的节点位置,如果使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页;

在这里插入图片描述


这样就会形成一个紧凑的索引结构,近似顺序填满,每次插入也不需要移动已有数据,效率较高,索引维护开销小;
如果使用非自增主键(如手机号、身份证号等),每次插入主键的值近似于随机,因此每次新纪录都要被插到现有索引页得中间某个位置:

在这里插入图片描述


此时MySQL不得不为了将新记录插到合适位置而移动数据,增加了开销,同时频繁的移动、分页操作造成了大量的碎片,得到了不紧凑的索引结构;
所以尽量在InnoDB上采用自增字段做主键;

索引优化小结

正确的创建合适的索引是提升数据库查询性能的基础
索引能极大的减少存储引擎需要扫描的数据量;
索引可以把随机IO变成顺序IO;
索引可以帮助我们在进行排序、分组等操作时,避免使用临时表;
索引列的数据长度能少则少(长度太长占空间,会让树高度变高);
索引一定不是越多越好,一定是创建合适的索引,一张表一般不超过6个;
匹配列前缀可用到索引,像like 9999%可以用到索引,而like %9999%、like %9999用不到索引;
匹配范围值可用用索引,order by 也可用到索引;
多用指定列查询,只返回自己想到的数据列,少用select *,因为有可能命中覆盖索引;
联合索引中如果不是按照索引最左列开始查找,无法使用索引;
联合索引中精确匹配最左前列并范围匹配另外一列可以用到索引;
联合索引中如果查询中有某个列的范围查询,则其右边的所有列都无法使用索引;
索引在数据量比较小的情况下,看不出明显的效果,当数据量较大时,恰当的索引将极大提升查询性能;

其实数据库索引调优是一项技术活,因为实际情况千变万化,而且MySQL本身存在很复杂的机制,如查询优化策略和各种引擎的实现差异等都会使情况变得更加复杂,但是理解并掌握以上索引底层数据结构的理论基础(B+Tree),才能对调优策略进行合理推断并了解其背后的原理,然后结合实践不断实验和摸索,从而真正达到高效使用MySQL索引的目的;

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

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340