MySql 索引底层数据结构和索引优化原则

本文详细探讨了MySQL索引优化的最佳实践,包括全值匹配、最左前缀法则、覆盖索引、避免计算和函数、范围查询的影响等。此外,还介绍了在JOIN操作中的索引优化原则。在并发处理方面,讲解了如何利用各种锁解决更新和插入的重复问题,以及在事务(MVCC)和锁之间如何选择。
摘要由CSDN通过智能技术生成

索引优化有很作最佳实践原则,下面对常用原则进行分析。我觉得在看本博文之前一定要先了解MySQL底层的索引原理,下面给大家转载两篇博文
MySql索引底层数据结构和算法
MySql explan执行计划详解
优化原则实例sql准备

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(24) NOT NULL DEFAULT '' COMMENT '用户姓名',
  `user_age` int(11) NOT NULL DEFAULT 0 COMMENT '用户年龄',
  `user_level` varchar(20) NOT NULL DEFAULT '' COMMENT '用户等级',
  `register_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间',
  PRIMARY KEY (`id`),
  KEY `idx_userName_userAge_userLevel` (`user_name`,`user_age`,`user_level`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';
 
INSERT INTO user(user_name, user_age, user_level, register_time)
VALUES('ZhangSan', 30, 'A', NOW()), ('LiSi', 31, 'B', NOW()), ('WangWu', 31, 'C', NOW());

1、全值匹配
按索引字段顺序匹配使用

mysql> explain select * from user where user_name = 'ZhangSan';

在这里插入图片描述

通过explain分析,type为ref,使用索引,效率高。key_len为74,根据key_len计算规则,如果字段类型为varchar(n),

并且是utf-8编码格式,则key_len=3n+2,where后使用了user_name,则key_len=3*24+2=74,证明索引用到了联合索引的

第一个字段user_name,从ref也可以看到一个const。

使用联合索引两个字段时:

mysql> explain select * from user where user_name = 'ZhangSan' and user_age = 30;

在这里插入图片描述

使用联合索引三个字段时:

mysql> explain select * from user where user_name = 'ZhangSan'and user_age = 30 and user_level = 'A';

在这里插入图片描述

2、最佳左前缀法则
如果建的是联合索引,要遵循最左前缀法则。

要想使用索引,where后面的条件需要从索引的最左前列开始并且不跳过索引中的列使用。

mysql> explain select * from user where user_name = 'ZhangSan'and user_age = 30 and user_level = 'A';

在这里插入图片描述

按照索引字段顺序使用,三个字段都使用了索引。

mysql> explain select * from user where user_age = 30 and user_level = 'A';

在这里插入图片描述

直接跳过user_name使用索引字段,索引无效,未使用到索引。

mysql> explain select * from user where user_age = 30 and user_level = 'A' and user_name = 'ZhangSan';

在这里插入图片描述

where后面查询条件顺序是user_age、user_level、user_name与我们建的索引顺序user_name、user_age、user_level

不一致,为什么还是使用了索引,这是因为MySql底层优化器给咱们做了优化。但是,咱们最好还是按顺序使用索引

PS: 所谓的最左原则只是建议最好按照这个顺序来,但是有时候你不按照这个来 也会走索引 因为最终决定要不要走索引是由mysql优化器之后进行决定,就和上面说的一样;但是上面的说的最多最左原则都是select * 这样的语句 ,如果你是selec user_age,user_level,user_name where 后面就算不是按照组合的顺序来使用的 都会走的 这种是索引覆盖,因为虽然你后面不是按照组合索引顺序来执行的,顶多就是做全索引扫描,但是你查询的字段已经在这个二级索引树上已经可以查询到了,所以和全表扫描相比还是很快的 ,所以就走索引了,总之,只要你selec 查询的是组合索引中其中一个或者组合索引都在,无论你where后面怎么写都会走的

3、不要在索引列上做任何操作,比如计算、使用函数、自动或手动进行类型转换,
会导致索引失效,从而使查询转向全表扫描

mysql> explain select * from user where user_name = 'ZhangSan';

在这里插入图片描述

where条件直接使用索引字段user_name用到了索引。

mysql> explain select * from user where right(user_name, 3) = 'San';

在这里插入图片描述

where条件使用计算后的索引字段user_name,没有使用索引,索引失效。

4、存储引擎不能使用范围条件右边的索引列。

mysql> explain select * from user where user_name = 'ZhangSan' and user_age = 30 and user_level = 'A';

在这里插入图片描述

三个列都使用“=”号,顺序使用三个字段,三个字段都使用了索引。

mysql> explain select * from user where user_name = 'ZhangSan' and user_age > 30 and user_level = 'A';

在这里插入图片描述

将user_age修改为">"之后,Extra为Using index condition,表明索引没有被完全使用,

并且key_len由140降为78,说明最后一个字段user_level没有使用索引。

即范围之后索引全无效。

但是如果我们把“>”变为">="呢?

mysql> explain select * from user where user_name = 'ZhangSan' and user_age >= 30 and user_level = 'A';

在这里插入图片描述

加上等号后,key_len变为了140,但是Using index condition确又表明索引没有被完全使用,

只能说明在满足user_age为“=”号条件时全部索引使用,否则,范围之后的索引失效

*5、尽量使用覆盖索引(只访问索引的查询(索引列包含查询列)),减少select 语句

mysql> explain select * from user where user_name = 'ZhangSan' and user_age  = 30 and user_level = 'A';

在这里插入图片描述

Extra显示null,表示查询的列未被索引列覆盖,并且where筛选条件是索引的前导列,说明用到了索引,

但是部分字段未被索引列覆盖,必须通过“回表”来实现,所以不是纯粹地用到了索引,也不是完全没用到索引。

mysql> explain select user_name, user_age from user where user_name = 'ZhangSan' and user_age  = 30 and user_level = 'A';

在这里插入图片描述

换成索引列,查询时使用了索引,用索引列覆盖查询的, 叫做覆盖索引。

6、mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描。

mysql> explain select user_name, user_age from user where user_name = 'ZhangSan';

在这里插入图片描述

mysql> explain select user_name, user_age from user where  user_name != 'ZhangSan';

在这里插入图片描述

Extra显示Using whre Using index,表示查询的列被索引列覆盖,但是where后面条件未使用索引,
说明无法直接通过索引查找查询到符合条件的数据。

7、is null,is not null 也无法使用索引

mysql> explain select * from user where user_name = 'ZhangSan';

在这里插入图片描述
使用了索引。

mysql> explain select * from user where user_name is not null;

在这里插入图片描述

未使用索引

8、like以通配符开头(like ‘%aaa’)mysql索引失效会变成全表扫描操作。

mysql> explain select * from user where user_name like 'Zhang%';

在这里插入图片描述

%结尾,使用了索引

mysql> explain select * from user where user_name like '%Zhang%';

在这里插入图片描述
%aaa%前后都使用%号,也未使用索引。
like使用总结:aaa%可以使用索引,但是%aaa或%aaa%相当于范围查询,
无法使用索引,以及在之后的索引列也会索引失效。

8、JOIN ON关联表索引优化原则

首先看下不加索引的情况

EXPLAIN SELECT
	dept.*, login.login_id
FROM
	sys_department dept
LEFT JOIN sys_user login ON dept.id = login.dept_id
WHERE
	dept.is_enabled = 1
ORDER BY
	CONVERT (dept.id, SIGNED)

其实上面什么索引都不加的话,很慢,很慢,因为dept表有2万个单位的时候

第一种情况: 给where条件后面的is_enable加个索引,的确能起到一定的效果,但是还是慢,无法解决本质问题

第二种情况:给主表dept.id加个主键索引(如果这个表没有任何索引的话,会自动给id加个索引,如果有索引就不会默认加索引了),没有起到一点效果

第三种情况:给副表也就是user表的dept_id加个索引,关键所在,速度提升百分之80

上面无论是叠加加索引还是单独分情况加都是差不多 ,主要索引在主表上 结论:join on
优化顺序是先主表+where条件进行扫描一次,然后再和副表进行匹配,所以主表加任何索引都失效,只要加where和副表索引
尽量小表放在左边,大表放在left join 后面

总结
关于索引优化原则,可以做如下总结。

全值匹配心上人(这是基本原则),最左前缀要遵行(联合索引一般都围绕最左前缀优化);

带头大哥活才行(联合索引从最左边字段开始使用),中间兄弟规矩行(不能跳过中间的字段,跳过后索引无效);

索引列上少计算(索引列上尽量不要进行计算),范围之后全完蛋(where后面使用范围查询的之后的索引无效);

like百分最右写(%号写最右边,写左边会导致索引失效),覆盖索引别写星(尽量避免select*这样的语句,能写索引列最好);

空值不等还有or,索引失效最无情(is null,is not null,!=,<>,or会导致索引无效);

关于索引优化原则,不同的sql版本会有不同,并且需要结合explain的各项参数分析,需要不断体会,

explain博大精深。
如果大家按照博主的指示看完了底层索引原理和数据结构应该知道一些实战中的优化了,但是不仅仅如此,比如实战中很多东西在上面还是没有提及到的接下来我将自己实战的优化给大家分享下,

1.大家都知道平时我们where后面都会有多个条件,这个时候大家第一想到的是加索引,那么问题来了,是创建多个单列索引还是组合索引呢?

答案:多个单列索引在多条件查询时只会生效第一个索引!所以多条件联合查询时最好建联合索引!
解释:与其说是“数据库查询只能用到一个索引”,倒不是说是 和全表扫描/只使用一个索引的速度比起来,去分析两个索引二叉树更加耗费时间,所以绝大多数情况下数据库都是是用一个索引。
如这条语句:

select count(1) from table1 where column1 = 1 and column2 = ‘foo’ and
column3 = ‘bar’ 我们来想象一下当数据库有N个索引并且查询中分别都要用上他们的情况:
查询优化器(用大白话说就是生成执行计划的那个东西)需要进行N次主二叉树查找[这里主二叉树的意思是最外层的索引节点],此处的查找流程大概如下:
查出第一条column1主二叉树等于1的值,然后去第二条column2主二叉树查出foo的值并且当前行的coumn1必须等于1,最后去column主二叉树查找bar的值并且column1必须等于1和column2必须等于foo。
如果这样的流程被查询优化器执行一遍,就算不死也半条命了,查询优化器可等不及把以上计划都执行一遍,贪婪算法(最近邻居算法)可不允许这种情况的发生,所以当遇到以下语句的时候,数据库只要用到第一个筛选列的索引(column1),就会直接去进行表扫描了。

select count(1) from table1 where column1 = 1 and column2 = ‘foo’ and
column3 = ‘bar’

所以与其说是数据库只支持一条查询语句只使用一个索引,倒不如说N条独立索引同时在一条语句使用的消耗比只使用一个索引还要慢。
所以如上条的情况,最佳推荐是使用index(column1,column2,column3)
这种联合索引,此联合索引可以把b+tree结构的优势发挥得淋漓尽致:
一条主二叉树(column=1),查询到column=1节点后基于当前节点进行二级二叉树column2=foo的查询,在二级二叉树查询到column2=foo后,去三级二叉树column3=bar查找。

注意点: 注:如果第一个字段是范围查询需要单独建一个索引
注:在创建联合索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。这样的话扩展性较好,比如 userid
经常需要作为查询条件,而 mobile 不常常用,则需要把 userid 放在联合索引的第一位置,即最左边

同时存在联合索引和单列索引(字段有重复的),这个时候查询mysql会怎么用索引呢?

这个涉及到mysql本身的查询优化器策略了,当一个表有多条索引可走时, Mysql 根据查询语句的成本来选择走哪条索引;

有人说where查询是按照从左到右的顺序,所以筛选力度大的条件尽量放前面。网上百度过,很多都是这种说法,但是据我研究,mysql执行优化器会对其进行优化,当不考虑索引时,where条件顺序对效率没有影响,真正有影响的是是否用到了索引!

联合索引本质

当创建(a,b,c)联合索引时,相当于创建了(a)单列索引,(a,b)联合索引以及(a,b,c)联合索引 想要索引生效的话,只能使用
a和a,b和a,b,c三种组合;当然,我们上面测试过,a,c组合也可以,但实际上只用到了a的索引,c并没有用到! 注:这个可以结合上边的
通俗理解 来思考!

其他知识点:

1、需要加索引的字段,要在where条件中 2、数据量少的字段不需要加索引;因为建索引有一定开销,如果数据量小则没必要建索引(速度反而慢)
3、如果where条件中是OR关系,加索引不起作用
4、联合索引比对每个列分别建索引更有优势,因为索引建立得越多就越占磁盘空间,在更新数据的时候速度会更慢。另外建立多列索引时,顺序也是需要注意的,应该将严格的索引放在前面,这样筛选的力度会更大,效率更高。
5.主键索引和非主键索引有什么区别?
例如对于下面这个表(其实就是上面的表中增加了一个k字段),且ID是主键
在这里插入图片描述
主键索引和非主键索引的示意图如下:
在这里插入图片描述
其中R代表一整行的值。
从图中不难看出,主键索引和非主键索引的区别是:非主键索引的叶子节点存放的是主键的值,而主键索引的叶子节点存放的是整行数据,其中非主键索引也被称为二级索引,而主键索引也被称为聚簇索引。
根据这两种结构我们来进行下查询,看看他们在查询上有什么区别。
1、如果查询语句是 select * from table where ID = 100,即主键查询的方式,则只需要搜索 ID 这棵 B+树。
2、如果查询语句是 select * from table where k = 1,即非主键的查询方式,则先搜索k索引树,得到ID=100,再到ID索引树搜索一次,这个过程也被称为回表
现在,知道他们的区别了吧?
6. 有时候我们要保证相同的数据只插入一次成功,我们怎么办呢?如何取选择创建索引呢?
这个时候我们可以选择唯一索引,为什么呢?因为如果第一个insert插入成功的话,那么你根据索引的列去匹配如果存在了这条记录那么就会失败,所以你在代码中也需要进行try。。。catch捕获
那么如果给某一列设置唯一索引的话无法进行唯一缺的话,就选择组合唯一索引,选择多个列进行组合唯一索引,保证唯一性,那么顺序一般都是id都在第一位,后面其实无所谓,因为id已经筛选了大多数了,说到这里,大家是不是疑惑和组合索引是不是有冲突啊?答案就是:当然不会了,因为每个索引直接都是独立的 ,就算组合索引中有id了,你的组合唯一索引也包含了id也是可以的,但是如何只是给id创建唯一索引而不是组合的话 就会失效,因为大家都知道mysql会自动给id创建了普调索引了,那么就会有冲突,切记:组合唯一索引不是为了查询效率而生了 只是为了快速定位的,所以id在第一位,而组合索引是为了提高查询效率的,所以根据区分度高的排在前面,一般都是id,但是如果你的where id in()这样的写法 ,id可能会失效,这样就会放在最后一位

上面说了这么多?都是关于实战经历的总结,这是大家看多少博客或者视频所获取不了的,这里我可能在排版上面就没那么严谨了,主要是细节在大家看完我上面的文章之后应该会有所了解了,那么今天博主又要更新正文啦……
1.因为在这里涉及到了mysql底层原理,就不得不提到并发处理了,当然这里只总结关于并发如何利用mysql的各种锁来处理的,并发博大精深可不是几句话可以说的清楚的,首先呢?在博主看来所谓的并发分两种:并行和并发,并行就是说真正意义上的并发比如2个cpu同时执行某个线程获取到的时间碎片;而并发指的就是大家常说的各个行程去争夺cpu分配的时间碎片,这种是没有真正意义的并发处理了。
2.如何处理mysql中可重复update(更新)问题呢?(这里只介绍mysql的innodb存储引擎,因为不同的数据库解决方式不同)
答案:① 可以通过加状态值,比如审核中、处理中或者其他表示正在处理过程中的状态值,当你主线程执行值首先去库里通过id查询最新的数据然后执行update table status = 审核中,然后在子线程中释放状态,比如重新update为最后状态(审核通过或者下单成功),执行update table status = 审核中 where status = 上面查询最新到的状态值
② 上面的①方案有个场景解决不了那就是关于库存问题(比如一直点击页面按钮,这个时候库存如会减1),就是没有状态这个字段,有人会说update不是默认有排它锁吗,但是这里不行,加锁只是阻塞,但是还是会执行的
那么这种如何去解决重复更新呢?----可以加版本号,比如你现在查询获取到这条需要修改的数据的修改时间,然后把这个修改时间作为where后面的条件去执行更新,如果别的线程已执行了,那么这条update肯定会执行失败的 返回0
3. 如何解决重复insert(插入)问题呢?
答案:①加唯一索引用来确定数据性的唯一,如果你查询条件的有主键就不需要加索引了 因为主键默认有唯一性,如果没有 主键,那就加组合唯一所以(id肯定放在第一个,后面的条件顺序其实无所谓,因为id已经差不多确定了大多数了)
②通过select 。。。 for update 排它锁(也可以说行锁)如何查询没有数据那么就形成了行锁+间隙锁(锁范围),当然如果你的条件包括有id主键的话就不需要间隙锁了,因为确定了直接锁定了id=xx这条数据,其他事物无法操作这条id=xx数据,如果条件非主键的索引就锁范围,如果条件的字段都不是索引,那么既然不是索引就无法排序,无法确定范围,就直接锁定整个表(锁表)
线程1 select 。。。 for update —间隙锁1
线程2 select 。。。 for update —间隙锁2
上面如果锁的范围有交集的数据就不会生效,线程2只能等线程1事务结束释放锁才可以执行,只有两个范围没有交集 那么这两把锁都生效了,
线程1 insert 。。。 发现上面有线程2的锁
线程2 insert 。。。 发现上面有线程1的锁
这样就行程了死锁,就会抛出异常,所以在我们的业务代码中只需要try。。catch就行,在catch里面执行接下来的业务
这种方案不建议,需要慎重
4.重复删除不需要考虑

上面都是通过mysql各种锁来解决并发问题的,但是加锁就意味了阻塞,但是很多查询不需要阻塞了,这种情况,我们可以使用事务处理(MVCC机制实现的)
MVCC 就是为了处理读多写少的情况,说白了就是处理select(查询)
5.那么如何选择MVCC(事务)和 MySQL锁来处理并发呢?
那么就分及时读和快照读mvcc就是快照读)如果你的业务需要最新的数据那么就需要及时读,比如
if(list .size > 0) {执行业务逻辑} 这种情况就需要及时多

上面说的所有综合和处理并发方案都是利用了mysql锁(基于单机部署的),如果涉及到了集群,可以使用分布式锁,当然你在单机版本也可以使用分布式锁,所以上面说的方案又多了一种:使用分布式锁解决并发问题

MySQL 索引底层数据结构主要有 B-Tree 和 Hash 结构两大类。 ### B-Tree B-Tree 是一种自平衡的树形数据结构,主要用于数据库和其他需要快速查找、插入和删除操作的数据存储系统中。它有以下几个关键特征: 1. **节点层次**:每个节点可以有多个子节点,并允许包含多个键值对,使得数据可以在树的较高层存储,提高查询效率。 2. **最大值限制**:节点中包含的最大键的数量是由节点的最大度数(分支因子)决定的。这意味着在同一级的节点之间存在某种形式的均匀分布。 3. **排序**:所有键都按升序排列,同时其左右子节点分别存储比当前节点小和大的键值部分。 4. **平衡**:通过调整内部结点的高度,保持整棵树的平衡状态,确保所有的路径长度大致相等。 ### Hash 结构 Hash 结构用于快速定位特定键对应的值。其核心在于利用哈希函数将键转换成一个哈希码,然后用这个哈希码作为索引来直接访问存储位置。 1. **哈希表**:基本的 hash 数据结构就是一个数组,每个元素对应着一个桶。当插入新元素时,使用哈希函数计算出该元素应该存放的位置,即哈希码对应的数组下标。 2. **冲突解决**:由于不同的键可能会得到相同的哈希码,因此需要策略处理这种冲突情况,常见的解决办法包括线性探测、链地址法和二次探查等。 3. **动态调整**:为了维持性能,哈希表通常会通过调整大小或重新哈希函数等方式来应对负载增加的情况。 ### MySQL 中的索引应用 MySQL 使用 B-Tree 结构来构建其默认类型的索引(如BTREE),这使得索引具有高效搜索、插入和删除的特点。对于 Hash 索引,则在某些场景下提供更快的查找速度,尤其是在单个列上使用并且数据集不是非常庞大时。 了解索引底层数据结构有助于优化查询性能,合理设计数据库结构和查询语句,以及更好地理解和管理数据库的运行状况。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值