本文主要介绍“MySQL索引面试题有哪些”。在日常操作中,相信很多人对MySQL索引面试题中的问题都有疑问。边肖查阅了各种资料,整理出简单易用的操作方法,希望能帮你解答“MySQL索引面试题有哪些”的疑惑!接下来,请和边肖一起学习!
1、面试真题
能介绍一下MySQ索引的原理和数据结构吗?
B树和B树有什么区别?
MySQL聚集索引和非聚集索引有什么区别?
它们是如何分开存放的?
使用MySQL索引的原则是什么?
如何使用MySQL复合索引?
2、面试官心理分析
对于离数据库30k以内的工程师来说,这是一个必须问的问题,如果你问数据库的问题,你必须问mysql。n年前,java工程师可能会出去面试。神谕的技能是杀手。现在没人说了解甲骨文是一件好事。现在,熟悉大数据hadoop、hbase等技术是加分项。
3、面试题剖析
3.1 索引的数据结构是什么
其实就是让你说说mysql的索引底层的数据结构。如果不好,会要求你在现场画出索引的数据结构,然后再询问mysql索引的常用使用原则。如果不好,会用一个SQL问你,如何基于这个SQL建立索引?
指数是多少?这个问题太基础了。众所周知,mysql index简单来说就是用一个数据结构来组织某一列的数据,然后如果要根据那一列的数据进行查询,不需要扫描整个表,只需要根据那个特定的数据结构找到那一列的值,然后找到对应行的物理地址就可以了。
然后,回答一个面试官的问题,mysql索引是如何实现的?
答案是,它不是二叉树,也不是乱树,而是B树。很多人会这样回答,然后面试官肯定会问问题,那你能说说B树吗?
但是在讨论b树之前,我们先来讨论一下b树是什么。从数据结构来看,B-树应满足以下条件:
(1)d是大于1的正整数,称为B-Tree的度。
(2)h为正整数,称为B-Tree的高度。
(3)每个非叶节点由n-1个键和n个指针组成,其中d=n=2d。
(4)每个叶节点至少包含一个键和两个指针,最多包含2d-1键和2d指针,全部为空。
(5)所有叶节点深度相同,等于树的高度h。
(6)键和指针相互间隔,节点的两端都是指针。
(7)节点中的键从左到右按非递减顺序排列。
(8)所有节点形成树形结构。
(9)每个指针要么为空,要么指向另一个节点。
(10)如果指针位于节点的最左侧且不为空,则它指向该节点的所有键都小于v(key1),其中v(key1)是该节点第一个键的值。
(11)如果一个指针位于节点的最右侧,并且不为空,则它指向该节点的所有键都大于v(keym),其中v(keym)是该节点最后一个键的值。
(12)如果节点节点中指针的左右相邻键分别为V(易科和易科1),且不为空,则它指向该节点的所有键都小于V(易科1)且大于v(keyi 1)。
我也是从网上找到了上面的规律。老实说,很少有java程序员能耐心地阅读或记忆它。知道它是一棵树就好了。让我们给你看一张在线地图:
例如,我们现在有一个表:
我们现在为id: 15,56,77,20,49选择*从表中选择=49选择*从表中选择=15
反正大概就是上面那个样子。搜索时,是从根节点开始的二进制搜索。很高兴知道这是件大事。我没有足够的时间深入讲里面的数学和算法问题,面试官也没想到你会讲里面的数学和算法问题,因为我觉得他自己都记不住。
好了,b树到此为止。让我们看看下一个,B树。b树是b树的变种。什么是变体?也就是说,有些事情在原则上是不一样的,有点
有点变化,同样的一套数据,放b-树和b+树看着排列不太一样的。而mysql里面一般就是b+树来实现索引,所以b+树很重要。
b+树跟b-树不太一样的地方在于:
-
鸿蒙官方战略合作共建——HarmonyOS技术社区
-
每个节点的指针上限为2d而不是2d+1。
-
内节点不存储data,只存储key;
叶子节点不存储指针。
这图我就不自己画了,网上弄个图给大家瞅一眼:
select * from table where id = 15 select * from table where id>=18 and id<=49
但是一般数据库的索引都对b+树进行了优化,加了顺序访问的指针,如网上弄的一个图,这样在查找范围的时候,就很方便,比如查找18~49之间的数据:
其实到这里,你就差不多了,你自己仔细看看上面两个图,b-树和b+树都现场画一下,然后给说说区别,和通过b+树查找的原理即可。
接着来聊点稍微高级点的,因为上面说的只不过都是最基础和通用的b-树和b+树罢了,但是mysql里不同的存储引擎对索引的实现是不同的。
3.2 myism存储引擎的索引实现
先来看看myisam存储引擎的索引实现。就拿上面那个图,咱们来现场手画一下这个myisam存储的索引实现,在myisam存储引擎的索引中,每个叶子节点的data存放的是数据行的物理地址,比如0x07之类的东西,然后我们可以画一个数据表出来,一行一行的,每行对应一个物理地址。
索引文件
id=15,data:0x07,0a89,数据行的物理地址
数据文件单独放一个文件
select * from table where id = 15 -> 0x07物理地址 -> 15,张三,22
myisam最大的特点是数据文件和索引文件是分开的,大家看到了么,先是索引文件里搜索,然后到数据文件里定位一个行的。
3.3 innodb存储引擎的索引
好了,再来看看innodb存储引擎的索引实现,跟myisam最大的区别在于说,innodb的数据文件本身就是个索引文件,就是主键key,然后叶子节点的data就是那个数据的所在行。我们还是用上面那个索引起来现场手画一下这个索引好了,给大家来感受一下。
innodb存储引擎,要求必须有主键,会根据主键建立一个默认索引,叫做聚簇索引,innodb的数据文件本身同时也是个索引文件,索引存储结构大致如下:
15,data:0x07,完整的一行数据,(15,张三,22) 22,data:完整的一行数据,(22,李四,30)
就是因为这个原因,innodb表是要求必须有主键的,但是myisam表不要求必须有主键。另外一个是,innodb存储引擎下,如果对某个非主键的字段创建个索引,那么最后那个叶子节点的值就是主键的值,因为可以用主键的值到聚簇索引里根据主键值再次查找到数据,即所谓的回表,例如:
select * from table where name = ‘张三’
先到name的索引里去找,找到张三对应的叶子节点,叶子节点的data就是那一行的主键,id=15,然后再根据id=15,到数据文件里面的聚簇索引(根据主键组织的索引)根据id=15去定位出来id=15这一行的完整的数据
所以这里就明白了一个道理,为啥innodb下不要用UUID生成的超长字符串作为主键?因为这么玩儿会导致所有的索引的data都是那个主键值,最终导致索引会变得过大,浪费很多磁盘空间。
还有一个道理,一般innodb表里,建议统一用auto_increment自增值作为主键值,因为这样可以保持聚簇索引直接加记录就可以,如果用那种不是单调递增的主键值,可能会导致b+树分裂后重新组织,会浪费时间。
3.4 索引的使用规则
一般来说跳槽时候,索引这块必问,b+树索引的结构,一般是怎么存放的,出个题,针对这个SQL,索引应该怎么来建立
select * from table where a=1 and b=2 and c=3,你知道不知道,你要怎么建立索引,才可以确保这个SQL使用索引来查询
好了,各位同学,聊到这里,你应该知道具体的myisam和innodb索引的区别了,同时也知道什么是聚簇索引了,现场手画画,应该都ok了。然后我们再来说几个最最基本的使用索引的基本规则。
其实最基本的,作为一个java码农,你得知道最左前缀匹配原则,这个东西是跟联合索引(复合索引)相关联的,就是说,你很多时候不是对一个一个的字段分别搞一个一个的索引,而是针对几个索引建立一个联合索引的。
给大家举个例子,你如果要对一个商品表按照店铺、商品、创建时间三个维度来查询,那么就可以创建一个联合索引:shop_id、product_id、gmt_create
一般来说,你有一个表(product):shop_id、product_id、gmt_create,你的SQL语句要根据这3个字段来查询,所以你一般来说不是就建立3个索引,一般来说会针对平时要查询的几个字段,建立一个联合索引
后面在java系统里写的SQL,都必须符合最左前缀匹配原则,确保你所有的sql都可以使用上这个联合索引,通过索引来查询
create index (shop_id,product_id,gmt_create)
(1)全列匹配
这个就是说,你的一个sql里,正好where条件里就用了这3个字段,那么就一定可以用到这个联合索引的:
select * from product where shop_id=1 and product_id=1 and gmt_create=’2018-01-01 10:00:00’
(2)最左前缀匹配
这个就是说,如果你的sql里,正好就用到了联合索引最左边的一个或者几个列表,那么也可以用上这个索引,在索引里查找的时候就用最左边的几个列就行了:
select * from product where shop_id=1 and product_id=1,这个是没问题的,可以用上这个索引的
(3)最左前缀匹配了,但是中间某个值没匹配
这个是说,如果你的sql里,就用了联合索引的第一个列和第三个列,那么会按照第一个列值在索引里找,找完以后对结果集扫描一遍根据第三个列来过滤,第三个列是不走索引去搜索的,就是有一个额外的过滤的工作,但是还能用到索引,所以也还好,例如:
select * from product where shop_id=1 and gmt_create=’2018-01-01 10:00:00’
就是先根据shop_id=1在索引里找,找到比如100行记录,然后对这100行记录再次扫描一遍,过滤出来gmt_create=’2018-01-01 10:00:00’的行
这个我们在线上系统经常遇到这种情况,就是根据联合索引的前一两个列按索引查,然后后面跟一堆复杂的条件,还有函数啥的,但是只要对索引查找结果过滤就好了,根据线上实践,单表几百万数据量的时候,性能也还不错的,简单SQL也就几ms,复杂SQL也就几百ms。可以接受的。
(4)没有最左前缀匹配
那就不行了,那就在搞笑了,一定不会用索引,所以这个错误千万别犯
select * from product where product_id=1,这个肯定不行
(5)前缀匹配
这个就是说,如果你不是等值的,比如=,>=,<=的操作,而是like操作,那么必须要是like ‘XX%’这种才可以用上索引,比如说
select * from product where shop_id=1 and product_id=1 and gmt_create like ‘2018%’
(6)范围列匹配
如果你是范围查询,比如>=,<=,between操作,你只能是符合最左前缀的规则才可以范围,范围之后的列就不用索引了
select * from product where shop_id>=1 and product_id=1
这里就在联合索引中根据shop_id来查询了
(7)包含函数
如果你对某个列用了函数,比如substring之类的东西,那么那一列不用索引
select * from product where shop_id=1 and 函数(product_id) = 2
上面就根据shop_id在联合索引中查询
3.5 索引的缺点以及使用注意
索引是有缺点的,比如常见的就是会增加磁盘消耗,因为要占用磁盘文件,同时高并发的时候频繁插入和修改索引,会导致性能损耗的。
我们给的建议,尽量创建少的索引,比如说一个表一两个索引,两三个索引,十来个,20个索引,高并发场景下还可以。
字段,status,100行,status就2个值,0和1
你觉得你建立索引还有意义吗?几乎跟全表扫描都差不多了
select * from table where status=1,相当于是把100行里的50行都扫一遍
你有个id字段,每个id都不太一样,建立个索引,这个时候其实用索引效果就很好,你比如为了定位到某个id的行,其实通过索引二分查找,可以大大减少要扫描的数据量,性能是非常好的
在创建索引的时候,要注意一个选择性的问题,select count(discount(col)) / count(*),就可以看看选择性,就是这个列的唯一值在总行数的占比,如果过低,就代表这个字段的值其实都差不多,或者很多行的这个值都类似的,那创建索引几乎没什么意义,你搜一个值定位到一大坨行,还得重新扫描。
就是要一个字段的值几乎都不太一样,此时用索引的效果才是最好的
还有一种特殊的索引叫做前缀索引,就是说,某个字段是字符串,很长,如果你要建立索引,最好就对这个字符串的前缀来创建,比如前10个字符这样子,要用前多少位的字符串创建前缀索引,就对不同长度的前缀看看选择性就好了,一般前缀长度越长选择性的值越高。
好了,各位同学,索引这块能聊到这个程度,或者掌握到这个程度,其实普通的互联网系统中,80%的活儿都可以干了,因为在互联网系统中,一般就是尽量降低SQL的复杂度,让SQL非常简单就可以了,然后搭配上非常简单的一个主键索引(聚簇索引)+ 少数几个联合索引,就可以覆盖一个表的所有SQL查询需求了。更加复杂的业务逻辑,让java代码里来实现就ok了。
大家要明白,SQL达到95%都是单表增删改查,如果你有一些join等逻辑,就放在java代码里来做。SQL越简单,后续迁移分库分表、读写分离的时候,成本越低,几乎都不用怎么改造SQL。
我这里给大家说下,互联网公司而言,用MySQL当最牛的在线即时的存储,存数据,简单的取出来;不要用MySQL来计算,不要写join、子查询、函数放MySQL里来计算,高并发场景下;计算放java内存里,通过写java代码来做;可以合理利用mysql的事务支持
到此,关于“MySQL索引面试题有哪些”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!
内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/133173.html