从原理到优化,深入浅出数据库索引

MYSQL使用SPATIAL关键字进行扩展,使得能够用于创建正规索引类型的语法创建空间索引。创建空间索引的列,必须将其声明为NOT NULL,空间索引只能在存储引擎为MYISAM的表中创建.

二、索引创建方式


CREATE TABLE table_name[col_name data type]

[unique|fulltext|spatial][index|key]index_name[asc|desc]

  • unique|fulltext|spatial为可选参数,分别表示唯一索引、全文索引和空间索引;

  • index和key为同义词,两者作用相同,用来指定创建索引

  • col_name为需要创建索引的字段列,该列必须从数据表中该定义的多个列中选择;

  • index_name指定索引的名称,为可选参数,如果不指定,MYSQL默认col_name为索引值;length为可选参数,表示索引的长度,只有字符串类型的字段才能指定索引长度;

  • asc或desc指定升序或降序的索引值存储

1、创建表时建立索引

(1)创建普通索引

create table table_name(

id int(11),

name varchar(20),

sex boolean,

INDEX(id)

);

查看表结构

show create table table_name;

可以使 EXPLAIN 语句查看索引是否被使用

explain select * from table_name where id = 1\G

(2)创建唯一索引

create table index2(

id int unique,

name varchar(20),

unique INDEX index_2(id asc)

);

(3)创建全文索引

全文索引只能在char,varchar或者text 类型的字段上。而且,只有MyISAM 储存引擎支持全文索引。

create table idnex3(

id int,

info varchar(20),

FULLTEXT INDEX index3_info(info)

)ENGINE=MyISAM;

(4)创建单列索引

create table index4(

id int,

subject varchar(255),

index index4_st(subject(10))

);

这里需要注意的,subject 的长度为255,但是index4_st索引只有10。这样做的目的还是为了提高查询速度。对于字符型的数据,可以不用查询全部信息,只查询其前面的若干字符信息。

(5)创建多列索引

create table index5(

id int,

name varchar(20),

sex char(4),

index index5_ns(name.sex)

);

这是我们可以看到,name 和sex字段上已经创建了index_ns索引。

2、在已经存在的表中创建索引

(1)创建普通索引

在example0() 表中的id 创建名为index7_id 的索引。

create index index7_id on example0(id);

(2)创建唯一索引

create UNIQUE index index_name on table_name(name);

(3)创建全文索引

create FULLTEXT index index_name on table_name(info);

(4)创建单列索引

create INDEX index_name ON table_name(name(10));

(5)创建多列索引

create INDEX index_name ON table_name(name,sex);

3、用alter table 语句来创建索引

(1)创建普通索引

在name字段上创建名为indx_name 的索引

alter table table_name ADD INDEX index_name(name(20));

(2)创建唯一性索引

alter table table_name ADD UNIQUE INDEX index_name(id);

(3)创建全文索引

alter table table_name ADD FULLTEXT INDEX index_name(info);

(4)创建单列索引

alter table table_name ADD INDEX index_name(name(4));

(5)创建多列索引

alter tabel table_name ADD INDEX index_name(name.sex);

4、删除索引

DROP INDEX index_name ON table_name;

三、索引树是如何维护的


目前大部分数据库系统及文件系统都采用B-Tree或其变种B+Tree作为索引结构,那么索引树是如何维护的?

1、查找结构进化史

查找是数据结构和算法中一个非常重要的概念。

  • 线性查找:一个个找;实现简单;太慢

  • 二分查找:有序;简单;要求是有序的,插入特别慢

  • HASH查找:查询快;占用空间;不太适合存储大规模数据

  • 二叉查找树:插入和查询很快(log(n));无法存大规模数据,复杂度退化

  • 平衡树:解决 BST 退化问题,树是平衡的;节点非常多的时候,依然树高很高

  • 多路查找树:一个父亲多个孩子节点(度);节点过多树高不会特别深

  • 多路平衡查找树:B-Tree

2、B-Tree

B-Tree是一种多路搜索树(并不是二叉的):

  1. 定义任意非叶子结点最多只有M个儿子;且M>2;

  2. 根结点的儿子数为[2, M];

  3. 除根结点以外的非叶子结点的儿子数为[M/2, M];

  4. 每个结点存放至少M/2-1(取上整)和至多M-1个关键字;(至少2个关键字)

  5. 非叶子结点的关键字个数=指向儿子的指针个数-1;

  6. 非叶子结点的关键字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];

  7. 非叶子结点的指针:P[1], P[2], …, P[M];其中P[1]指向关键字小于K[1]的

子树,P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树;

  1. 所有叶子结点位于同一层;

  2. 每个k对应一个data。

如:(M=3)相当于一个2–3树,2–3树是一个这样的一棵树, 它的每个节点要么有2个孩子和1个数据元素,要么有3个孩子和2个数据元素,叶子节点没有孩子,并且有1个或2个数据元素。

11561958-70d9644b9ed87713.jpg

B-树的搜索,从根结点开始,对结点内的关键字(有序)序列进行二分查找,如果命中则结束,否则进入查询关键字所属范围的儿子结点;重复,直到所对应的儿子指针为空,或已经是叶子结点;B-Tree上查找算法的伪代码如下:

BTree_Search(node, key) { if(node == null) return null; foreach(node.key) { if(node.key[i] == key) return node.data[i]; if(node.key[i] > key) return BTree_Search(point[i]->node); } return BTree_Search(point[i+1]->node); } data = BTree_Search(root, my_key);

(1)B-树的特性
  1. 关键字集合分布在整颗树中;

  2. 任何一个关键字出现且只出现在一个结点中;

  3. 搜索有可能在非叶子结点结束;

  4. 其搜索性能等价于在关键字全集内做一次二分查找;

  5. 自动层次控制;

(2)B-树的自控制

B树中每一个内部节点会包含一定数量的键值。通常,键值的数量被选定在d和2d之间。在实际中,键值占用了节点中大部分的空间。因数2将保证节点可以被拆分或组合。如果一个内部节点有2d个键值,那么添加一个键值给此节点的过程,将会拆分2d键值为2个d键值的节点,并把此键值添加给父节点。每一个拆分的节点需要最小数目的键值。相似地,如果一个内部节点和他的邻居两者都有d个键值,那么将通过它与邻居的合并来删除一个键值。删除此键值将导致此节点拥有d-1个键值;与邻居的合并则加上d个键值,再加上从邻居节点的父节点移来的一个键值。结果为完全填充的2d个键值。

(3)B-树的构造过程

下面是往B树中依次插入

6 10 4 14 5 11 15 3 2 12 1 7 8 8 6 3 6 21 5 15 15 6 32 23 45 65 7 8 6 5 4

11561958-8466f9fb4882eb06.png

3、B+Tree

B-Tree有许多变种,其中最常见的是B+Tree,MySQL就普遍使用B+Tree实现其索引结构。

与B-Tree相比,B+Tree有以下不同点:

  1. 非叶子结点的子树指针与关键字个数相同;

  2. 非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(B-树是开区间);

  3. 为所有叶子结点增加一个链指针;

  4. 所有关键字都在叶子结点出现;

  5. 内节点不存储data,只存储key

如:(M=3)

11561958-2c6cf94c85a33fd7.jpg

B+的搜索与B-树也基本相同,区别是B+树只有达到叶子结点才命中(B-树可以在非叶子结点命中),其性能也等价于在关键字全集做一次二分查找;

(1)B+的特性
  1. 所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的;

  2. 不可能在非叶子结点命中;

  3. 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层;

  4. 更适合文件索引系统;

(2)B+树的构造过程

下面是往B+树中依次插入

6 10 4 14 5 11 15 3 2 12 1 7 8 8 6 3 6 21 5 15 15 6 32 23 45 65 7 8 6 5 4

11561958-644bd842b71b19db.png

3、索引的物理存储

一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。

这样的话,索引查找过程中就要产生磁盘I/O消耗,相对于内存存取,I/O存取的消耗要高几个数量级,所以评价一个数据结构作为索引的优劣最重要的指标就是在查找过程中磁盘I/O操作次数的渐进复杂度。换句话说,索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数。

假如每个盘块可以正好存放一个B树的结点(正好存放2个文件名)。那么一个BTNODE结点就代表一个盘块,而子树指针就是存放另外一个盘块的地址。

(1)模拟B+树查找过程

下面,咱们来模拟下查找文件29的过程:

11561958-a8dc3531b0f53412.jpg

  1. 根据根结点指针找到文件目录的根磁盘块1,将其中的信息导入内存。【磁盘IO操作 1次】

  2. 此时内存中有两个文件名17、35和三个存储其他磁盘页面地址的数据。根据算法我们发现:17<29<35,因此我们找到指针p2。

  3. 根据p2指针,我们定位到磁盘块3,并将其中的信息导入内存。【磁盘IO操作 2次】

  4. 此时内存中有两个文件名26,30和三个存储其他磁盘页面地址的数据。根据算法我们发现:26<29<30,因此我们找到指针p2。

  5. 根据p2指针,我们定位到磁盘块8,并将其中的信息导入内存。【磁盘IO操作 3次】

  6. 此时内存中有两个文件名28,29。根据算法我们查找到文件名29,并定位了该文件内存的磁盘地址。

分析上面的过程,发现需要3次磁盘IO操作和3次内存查找操作。关于内存中的文件名查找,由于是一个有序表结构,可以利用折半查找提高效率。至于IO操作是影响整个B树查找效率的决定因素。

当然,如果我们使用平衡二叉树的磁盘存储结构来进行查找,磁盘4次,最多5次,而且文件越多,B树比平衡二叉树所用的磁盘IO操作次数将越少,效率也越高。

4、B+tree的优点

(1)B±tree的磁盘读写代价更低

B±tree的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。

举个例子,假设磁盘中的一个盘块容纳16bytes,而一个关键字2bytes,一个关键字具体信息指针2bytes。一棵9阶B-tree(一个结点最多8个关键字)的内部结点需要2个盘快。而B+

树内部结点只需要1个盘快。当需要把内部结点读入内存中的时候,B 树就比B+树多一次盘块查找时间(在磁盘中就是盘片旋转的时间)。

(2)B±tree的查询效率更加稳定

由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

四、索引创建有哪些原则


索引查询是数据库中重要的记录查询方法,要不要进入索引以及在那些字段上建立索引都要和实际数据库系统的查询要求结合来考虑,下面给出实际中的一些通用的原则:

  1. 在经常用作过滤器的字段上建立索引;

  2. 在SQL语句中经常进行GROUP BY、ORDER BY的字段上建立索引;

  3. 在不同值较少的字段上不必要建立索引,如性别字段;

  4. 对于经常存取的列避免建立索引;

  5. 用于联接的列(主健/外健)上建立索引;

  6. 在经常存取的多个列上建立复合索引,但要注意复合索引的建立顺序要按照使用的频度来确定;

  7. 缺省情况下建立的是非簇集索引,但在以下情况下最好考虑簇集索引,如:含有有限数目(不是很少)唯一的列;进行大范围的查询;充分的利用索引可以减少表扫描I/0的次数,有效的避免对整表的搜索。

  8. 经常用在WHERE子句中的数据列;

  9. 经常出现在关键字order by、group by、distinct后面的字段,建立索引。如果建立的是复合索引,索引的字段顺序要和这些关键字后面的字段顺序一致,否则索引不会被使用;

  10. 对于那些查询中很少涉及的列,重复值比较多的列不要建立索引;

  11. 对于定义为text、image和bit的数据类型的列不要建立索引;

  12. 对于经常存取的列避免建立索引;

  13. 限制表上的索引数目。对一个存在大量更新操作的表,所建索引的数目一般不要超过3个,最多不要超过5个。索引虽说提高了访问速度,但太多索引会影响数据的更新操作。

  14. 对复合索引,按照字段在查询条件中出现的频度建立索引。在复合索引中,记录首先按照第一个字段排序。对于在第一个字段上取值相同的记录,系统再按照第二个字段的取值排序,以此类推。因此只有复合索引的第一个字段出现在查询条件中,该索引才可能被使用,因此将应用频度高的字段,放置在复合索引的前面,会使系统最大可能地使用此索引,发挥索引的作用。

1、组合多个索引

一个单独的索引扫描只能用于这样的条件子句:使用被索引字段和索引操作符类中的操作符, 并且这些条件以AND连接。

假设在(a, b)上有一个索引, 那么类似WHERE a = 5 AND b = 6的条件可以使用索引,但是像WHERE a = 5 OR b = 6的条件就不能直接使用索引。

一个类似WHERE x =42 OR x = 47 OR x = 53 OR x = 99 这样的查询可以分解成四个在x上的独立扫描,每个扫描使用一个条件, 最后将这些扫描的结果OR 在一起,生成最终结果。

另外一个例子是,如果我们在x 和y上有独立的索引,一个类似WHERE x = 5 AND y = 6 这样的查询可以分解为几个使用独立索引的子句,然后把这几个结果AND 在一起,生成最终结果。

五、索引失效有哪几种情况


  1. 如果条件中有or,即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)

  2. 对于多列索引,不是使用的第一部分(第一个),则不会使用索引

  3. like查询是以%开头

  4. 如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引

  5. 如果mysql估计使用全表扫描要比使用索引快,则不使用索引

1、联合索引失效的条件

联合索引又叫复合索引。两个或更多个列上的索引被称作复合索引。

对于复合索引:Mysql从左到右的使用索引中的字段,一个查询可以只使用索引中的一部份,但只能是最左侧部分。例如索引是key index (a,b,c)。 可以支持a | a,b| a,b,c 3种组合进行查找,但不支持 b,c进行查找 .当最左侧字段是常量引用时,索引就十分有效。

所以说创建复合索引时,应该仔细考虑列的顺序。对索引中的所有列执行搜索或仅对前几列执行搜索时,复合索引非常有用;仅对后面的任意列执行搜索时,复合索引则没有用处。

六、如何查看索引的使用情况


这里记录两种方式,分别是

1、使用Handler_read查看索引的使用情况

show status like ‘Handler_read%';

大家可以注意:

  • handler_read_key:这个值越高越好,越高表示使用索引查询到的次数

  • handler_read_rnd_next:这个值越高,说明查询低效

±----------------------±-------------+

| Variable_name | Value |

±----------------------±-------------+

| Handler_read_first | 153 |

| Handler_read_key | 364 |

| Handler_read_next | 425 |

| Handler_read_prev | 598 |

| Handler_read_rnd | 605 |

| Handler_read_rnd_next | 860571 |

±----------------------±-------------+

6 rows in set (0.00 sec)

————————————————

分析这几个值,我们可以查看当前索引的使用情况:

  • Handler_read_first:索引中第一条被读的次数。如果较高,它表示服务器正执行大量全索引扫描;例如,SELECT col1 FROM foo,假定col1有索引(这个值越低越好)。

  • Handler_read_key:如果索引正在工作,这个值代表一个行被索引值读的次数,如果值越低,表示索引得到的性能改善不高,因为索引不经常使用(这个值越高越好)。

  • Handler_read_next :按照键顺序读下一行的请求数。如果你用范围约束或如果执行索引扫描来查询索引列,该值增加。

  • Handler_read_prev:按照键顺序读前一行的请求数。该读方法主要用于优化ORDER BY … DESC。

  • Handler_read_rnd :根据固定位置读一行的请求数。如果你正执行大量查询并需要对结果进行排序该值较高。你可能使用了大量需要MySQL扫描整个表的查询或你的连接没有正确使用键。这个值较高,意味着运行效率低,应该建立索引来补救。

  • Handler_read_rnd_next:在数据文件中读下一行的请求数。如果你正进行大量的表扫描,该值较高。通常说明你的表索引不正确或写入的查询没有利用索引。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

写在最后

为了这次面试,也收集了很多的面试题!

以下是部分面试题截图

Java程序员秋招三面蚂蚁金服,我总结了所有面试题,也不过如此
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-Rxt98ysq-1713474473334)]

[外链图片转存中…(img-xsHwW7iu-1713474473334)]

[外链图片转存中…(img-T3hYR38G-1713474473335)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

写在最后

为了这次面试,也收集了很多的面试题!

以下是部分面试题截图

[外链图片转存中…(img-TxsWlQwk-1713474473335)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 27
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值