面试|MYSQL中的索引与事务———javaweb(8)(面试必考)


虽然涉及数据库,但没有设计数据库操作,全是文字内容;
面试问题总结:
1.索引是啥?
2.索引要解决的问题
3.索引的应用场景
4.索引的数据结构
a) 为什么不用哈希表
b) 为什么不用二叉搜索树
c) 啥是B树,相较上面两个有什么优势
d) 啥是B+树,为什么用它来作索引的数据结构
5.更详细的说下索引的细节方面
6 事务是什么?解决的问题是什么?
7 事务的特性
8介绍并发执行
9 介绍 mysqlde隔离级别
下面介绍的东西都属于MYSQL里面的一些原理层面的东西:
1 索引(index)(第一章在第几页,第二章在第几页,根据目录中每一个章节对应的页码,就可以快速地找到对应的章节 )
当我们在数据库中进行查找的时候,就需要按照一定的条件来进行查找,它可以遍历表,但是他的效率比较低效,尽量的减少遍历,可以通过一些特殊的数据结构,来表示一些记录的特征,通过这些特征来减少比较次数,加快比较的速率
索引的主要的意义就是加快查找速率,查找速率提高了,但是同时也会付出一定的代价,书的目录也是废纸的,数据库中的索引,也是需要耗费一定的存储空间的,数据量越大,索引消耗的额外空间也会越多;书的目录如果确定了,后续对于书的目录调整,都可能会影响到目录的准确性,就需要重新调整目录
数据库中的索引也是一样,进行增删改查的时候,往往也需要进行同步的调整
好处:加快查找速率,大部分情况下是需要进行查询的
坏处:占用的更多的空间,拖慢了增删查的效率
有了索引之后,对于查找效率的提升是非常巨大的,当mysql中的数据量达到千万级别的时候(一个表里面有几千万,上亿的数据)再去遍历表,就会变得非常的低效,在mysql中进行查找比较的时候,不是像下面这种的 for(int i=0; i<1kw; i++) { if(arr1[i]==num) {break; } } 这个是纯纯的在内存中进行查找,但是MYSQL中的比较是在硬盘上面的,硬盘上面的IO的速度比内存中的速度要慢上一个3-4倍

1)定义:索引是一种特殊的文件,包含着数据表中所有有记录的引用指针。可以对表中的一列或多列建立索引,就好像书的目录一样,它的存在就是加快数据库查询的;
注意:我们在这里说的查找,是按照值来进行查找,而不是根据下标进行查找,根据下标来进行查找,不叫查找;
2)索引背后考虑的数据结构:
(1)顺序表:例如我想查找id是8的学生信息,如果没有索引,查找过程就相当于是顺序表查找,如果是针对顺序表查找,顺序表是在连续的内存空间中,内存访问速度还行,而且数据也没那么多,速度还可以,访问任意地址上面的数据,速度都是极快的;
如果是针对数据库查找,数据库的数据可是在磁盘上,磁盘访问速及其的慢,而且数据量也可能很多;
链表查的比顺序表都慢
(2)哈希表:
我们都知道,哈希表下的每一个节点都是一个链表连接起来的,比如要把一种数据类型放到哈希表中,是通过哈希函数得到一个下标,再根据下标取到对应的链表
但是哈希表只能处理相等的情况,不可以针对范围型数据进行查询,例如我们想查找学生id为6,到8的学生信息,哈希表就无能为力;
select * from student where id>6 and id <8

(3) 二叉搜索树:
相比于哈希表,二叉搜索树虽然可以处理范围查找,但是效率还是不太高:
1 二叉树的每个节点最多有两个叉,当数据量比较多的时候,树的高度就会比较高,高度就对应着比较次数,最终的操作效率,会很低;
2 )况且中序遍历效率也不是很高,时间复杂度为(N)(最坏的情况,变成链表);
3 )二叉树的每个节点只存放一个数据,花费磁盘IO次数比较多
4)堆也不行,只能查找最大值或者最小值
最终的数据结构:B+树,向多叉搜索树来进行解决;在整个数据量条数一定的情况下,N叉搜索树的高度一定比二叉搜索树小
首先我们先了解B树
面试|MYSQL中的索引与事务———javaweb(8)(面试必考)
文章图片

面试|MYSQL中的索引与事务———javaweb(8)(面试必考)
文章图片


仔细看一下;B树每个节点存的数据的个数和节点的度是相关的 存的个数+1=度,在B数的每一个节点,都会存储N个key值,N个key值就会存储N+1个区间,每一个区间都会对应一个子树,从B树中查找元素,这个就和二叉搜索树非常类似,先从根节点进行出发,根据带比较的元素,确定一个区间
我们再确定区间的时候,不也是在进行多次比较吗?这里面的比较和二叉搜索树相比有什么优势呢?
二叉搜索树。每一个节点比较一次,比较的次数是和高度息息相关的,但是对于B树来说高度是变小了,但是对于每一个节点来说,要进行比较多次,看似比较次数没有发生变化;
但是相比于比较次数来说,这不是关键的,IO次数是更关键的;
树是以节点为单位,进行磁盘的IO操作的,读一个磁盘花的时间比花上时间读内存几百次的时间都多
在二叉搜索树里面一个节点对应着一次IO操作,也就是说进行一次比较;但是在B树里面,一次IO操作得到一个节点,里面有多个数据,可以进行多次比较; 可能比较的次数没有发生变化,但是磁盘IO的操作是会大大进行减少的;因为访问磁盘比访问IO次数要慢个几千倍,几万倍,让访问磁盘IO的次数尽量少,提高效率;
但是此时B树想要进行范围型查找,也是不太轻松的,例如在下面的图中,我想要查找1-18之间的数据,进行范围型查找就会变得很麻烦了;

面试|MYSQL中的索引与事务———javaweb(8)(面试必考)
文章图片

现在了解一下B+树
面试|MYSQL中的索引与事务———javaweb(8)(面试必考)
文章图片

面试|MYSQL中的索引与事务———javaweb(8)(面试必考)
文章图片

但是对于B+树来说,他也是一个N叉搜索树,每一个节点上包含了多个key值,父亲结点的值都会在子节点上面进行体现,非叶子节点里面的值,最终都会在叶子节点里面体现出来;父亲节点里面的值,都会作为子节点中的最大值或者最小值,最下边的叶子节点,都是通过链表来进行数据连接的;
相对于B数优点还要更多
1 数据每一层都链接在一起;
2)数据只在叶子节点保存,非叶子节点只保存一些辅助查找的边界信息,查询任何一条的记录速度是比较平均的,不会出现效率差异大的情况;
3)使用B+树来进行查找的时候,整体使用的IO次数还是比较少的,最终的查询都会落到叶子节点上面,使用链表进行连接的时候,就非常适合范围型查找
4)所有的数据载荷,都是放到叶子节点上面的,非叶子节点上面只需要保存key值即可,因为非叶子节点整体占用的空间比较小,甚至可以放到内存里面,这个时候磁盘IO基本上就没了;
id name sex score 1lijiawei 男34 2hhhh女47 此时id这一列就叫做索引,整个信息,存的就是载荷 这里面的key的值就是id的值

——————————————————————————————————————————
索引的缺点:是可以加快查找效率,但是对删除和插入,修改操作会减慢(需要同步调整索引结果),索引也会占用额外的空间;
索引的使用场景:数据量很大适用于查找很频繁,但是插入删除修改,都不频繁的场景;
索引的分类:给具体的表中加索引的时候,加载主键上的索引和加载其他列上的索引是完全不同的,主键索引的叶子节点是存放的数据的完整记录,而其他普通的叶子节点是存放的是主键的id
例如要按name查找,先通过索引查找到王五对应的主键id,再通过主键id去主键索引查找王五的信息
对于一个student的表来说(自动创建索引),这个自带的索引,就是primary可以根据这个主键约束带来的,查询的时候如果查询条件中指定了根据主键进行查询,这个时候的查询速度就会变得非常快;unique也是自带索引的;
classidname
查找索引 show index from student
在创建表的时候,就应该考虑要把索引规划好
根据一张表中的某个列来创建索引
创建索引 create index 索引名 (什么字段)on表名(列名)
create index lijiawei on student (name)(如果针对线上环境的数据库没有索引,不要贸然的去创建索引)
删除索引 drop index lijiawei on student; (索引名字和表名)
但是有一说一,创建索引是一个非常低效的事情
2 事务:事务诞生的目的就是为了把若干个独立的操作打包成一个整体 我们首先说一个转账问题
假设A给B转账2000元钱
A的账户余额 -2000
B的账户余额 +2000
【面试|MYSQL中的索引与事务———javaweb(8)(面试必考)】但是我们假设 A的账户余额如果减少了2000,B的账户余额没有增加,A的钱减少了,但是B的钱没有增加,这2000元钱是不是凭空消失了呢?(在执行完第一个SQL之后,在执行第二个SQL语句之前/数据库崩了,程序崩了,机器断电
因此我们引出了事务的定义:
将一组操作封装到一起,称为一个共同的执行单元,此时执行整个事务就可以避免上面的问题
1 事务的特性
a)原子性:事务中的若干个操作,要么全部执行成功,要么全部不执行。但此处的不执行并不是真的不执行,而是一但中间某个操作执行操作执行出错,就把前面已经执行完毕的步骤会滚回去。所以数据库会自动地来进行一些还原性的操作,来消除前面的SQL造成的影响,看起来就好像没有进行执行;
回滚回去要借助逆向操作,目的是把原来操作造成的影响进行还原。例如:将原来的-2000操作变成+2000操作;
原子性:是一个不可拆分的最小单元 在SQL中,有的复杂的任务需要多个SQL来进行执行,有的时候,也同样需要打包在一起,前一个SQL是为了给后一个SQL提供支持,如果后一个SQL不执行了,或者在执行的过程中出现了问题,那么前一个SQL也就失去了意义; 那么进行回滚操作的时候,如何判定前面都执行了什么拿? 数据库会对执行中的每一个操作,记录下来 数据库想要记录上面的详细操作,也是需要消耗大量的时间和空间的,因此这个记录不会保存那么久,你的数据库是沉淀一年的,但是最新记录仅仅保存了几天

b)一致性:执行事务的前后,数据库中的数据始终处于一种合理合法的状态,例如转账操作,减账户操作,减帐户余额的时候,不可以将账户减成负数;
c)持久性:事务一旦执行完毕,此时对于数据来说,修改就是持久生效的啦(写入磁盘了)(写到磁盘就是持久生效的,数据写到内存中就是不持久的了,关机重启就没了)
d)隔离性
2 使用事务
1 )开启事务: start transaction;
2 )执行多个MYSQL语句;
3) 回滚或者提交
上代码
start transaction; //开启事务 -- 阿里巴巴账户减少2000 update accout set money=money-2000 where name = '阿里巴巴'; -- 四十大盗账户增加2000 update accout set money=money+2000 where name = '四十大盗'; commit; //结束事务

3)介绍 事务的隔离性:描述的是事务并发执行的时候,产生的情况
什么是事物的隔离性呢
定义:就是多个事务并发执行,事物之间不可以互相干扰,本质上就是线程安全问题。
隔离性与并发性相勃:
1 如果多个事务之间隔离性越强,并发程度越低,效率就越低。
2 如果多个事务之间隔离性越弱,并发程度越高,效率就越高。
其实来说隔离性是保证了数据的准确,并发性是为了保证事务执行的效率;
但是并发执行事务会出现问题:
1 脏读:如果一个事务A对某个数据进行修改(还没提交呢),另一个事务B读取了这里的修改内容,此时这样的事务B的读操作就是脏读;因为A在提交数据之前,随时就有可能修改刚才的数据;此时事务B可能读到的就是一个脏数据,这个数据是一个临时的结果,并不是最终的结果
例如我在写一个代码:class student,这个代码正在写的过程中,xxx看了一眼我的显示器,代码中有一个class student,里面有一个class student,里面有String name,看完之后就跑了,我之后就把这个内容删了,xxx看的数据就是脏读;
在这里面看到的student类,看到的并不是最终版本的数据,而是一个中间过程的数据,最终可能会被改成别的值,并没有什么实际意义 出现脏读的原因就是事务与事务之间,没有进行任何的隔离,他们是在交叉进行,没有进行真正的隔离开,是完全没有进行隔离

解决方法:给写这个类的过程加锁(我写着这个类的过程,同学们不可以看屏幕)(一直等到我写完这个类的时候,同学们才可以看数据),在我修改这个类的时候,别人是不可以进行读操作的;
在A修改数据的时候(提交之前),B尝试读,就会阻塞,一直阻塞到A写完,B才可以读数据
写加锁:我写这个代码的时候,大家不能读,但是大家读的时候我可以写这个类;
读加锁:现在读的时候也不可以写了
2)不可重复读:
一个事务A执行过程中针对同一个数据两次读到的结果不一样,就叫不可重复读;
举个例子:在大家读代码的过程中,我也可以修改代码,删除了其中的name属性,重新提交,这时就出现了不可重复读,也就是说刚才的同学很纳闷,刚才还有name怎么没了?
解决方案:读也加锁,读的时候也不能改呀;
这时就是对于同一个类中写加锁,读也加锁,写的时候不可以读,读的时候不可以写;
这个时候代码的并发性(效率高)又降低了,隔离性(正确性高)此时又升高了;
3)幻读:在一个事务执行过程中进行多次查询,多次查询的结果集不一样(是一种特殊的不可重复读)
同一个事务,多次读到的结果集不一样(具体的结果数据是一致的)
例如:一开始读10行数据,一会到了13行,但是前10行的数据不变(一会多了一个类)
比如说在读取这个类时(加锁只针对了student类的读与写) ,可以增加类或删除类,当同学们读的时候,发现增加了teacher类;
修改操作:让老师的修改操作和同学们的读操作彻底串行执行的时候,读的时候啥也不要写,写的时候啥也不要读;
对写所有类的时候,写加锁,读也加锁;这就会造成隔离性最高,并发程度最低,数据最可靠,速度最慢; 所以并发和隔离是不可兼得的;我们就可以根据实际需要来调整数据库的隔离级别,通过不同的隔离级别,也就控制了事务之间的隔离性,也就控制了并发程度
MYSQL的事务隔离:对隔离性的要求具体多高(隔离性高了,并发程度就越低)
1 read uncommitted:允许读取未提交的数据
(隔离程度最低,并发性最高,出现脏读,也有幻读,不可重复读)
2 read committed:只允许读取已提交的数据,相当于写加锁(对于一个类)
隔离性提高了一些,并发性降低了一些,解决了脏读,但是会有不可重复读的问题)
3 repeatable read(Mysql的默认隔离级别)
给这个类的都也加锁(隔离性提高了,并发性降低了,解决了不可重复读,但是会有幻读问题)
4 serializable:严格串行化执行(隔离性能最高,并发最低,解决了幻读);




    推荐阅读