其他
面试官: 你的MySQL,有点东西!
责编:Java就该这么学 | 来源:Boom
链接:juejin.cn/post/6986223980269191182
宝,让我们一起有点东西
一、MYSQL索引的底层数据结构及算法
二叉树:在极端的情况下,二叉树会退化成链表,查询的时间复杂度也随之退化。且树的高度越来越高
红黑树:它又叫二叉平衡树,虽然自旋和变色解决了退化成链表的问题,但在一定程度上,树的高度递增的问题还是没有得到解决
B树:它相当于是二叉树的一个进化版本,变成了多叉树,那么单个结点存储的数据就变多了,随之树的高度也得到了控制,但是B树有一个特点,它的每个结点都存储了对应行的data,那么我们可以试想一下,这个索引的内存大小会特别大,而MYSQL的内存是有限的,我们单表在大多数情况下,都不止一两个索引, 那如果每个索引树都去维护一份data, 这对于MYSQL将会是一笔巨大的开销, 且造成了资源浪费。另一方面,如果我们要对索引字段排序,B树会采取一系列的递归遍历,性能大打折扣
B+树:它相当于是B树的一个进化版本, 从之前的每个结点都存储data,变为只有叶子结点存储data, 且这个树的所有数据其实都在叶子结点上,并且B+树在叶子结点维护了一个单向指针(MYSQL做了优化,变为了双向指针),这个指针在很大程度上解决了我们对索引字段排序的问题
MyISAM:索引树和数据文件是分离的(非聚集),索引树中的data维护的只是对应数据文件的内存地址,通过内存地址找到对应的数据。
Innodb:它分为两种情况,一种是主键索引树(或者是唯一索引树),另一种是普通索引树(非聚集)。主键索引树中的data维护的是对应行的数据,而普通索引树中维护的是对应行的主键id,通过这个主键id,去主键索引树中进行回表查询,从而找到对应的数据。
二、SQL执行计划分析
id列: 它是select的序列号,有几个select就有几个id,并且id的顺序是按select出现的顺序增长的。id列越大执行优先级越高,id相同则从上往下执行,id为NULL最后执行。 table列: 即对应select的那个表 type列: 这一列表示SQL的优化程度,依次从最优到最差分别为:system>const>eq_ref>ref>range>index>ALL一般来说,得保证查询达到range级别,最好达到ref key列: 实际走的索引 rows列: mysql内部估算的结果数或扫描数 Extra列:这一列展示一些额外信息,重要的信息有以下几个: a. Usingindex:表示使用了覆盖索引(覆盖索引的意思就是只查询索引树上的字段,减少了回表操作,从而提升了速度) b. Usingwhere:使用where语句来处理结果,并且查询的列未被索引覆盖 c. Usingindexcondition:查询的列不完全被索引覆盖,where条件中是一个前导列的范围 d. Usingtemporary:mysql需要创建一张临时表来处理查询。出现这种情况一般是要进行优化的,首先是想到用索引来优化。 e. Usingfilesort:使用普通字段排序而不是索引排序,数据较小时从内存排序,否则需要在磁盘完成排序。这种情况下一般也是要考虑使用索引来优化的。
三、一条SQL在MYSQL中是如何执行的?
MYSQL作为服务端,我们的程序作为客户端通过TCP与MYSQL保持一个长连接。 MYSQL把我们的SQL作为一个key去缓存中查询(MYSQL的缓存采用的是LRU淘汰算法来实现缓存的淘汰机制的),判断是否缓存命中,如命中,则直接返回数据。如未命中,则继续下面的流程 MYSQL实现了一套语法分析器(C语言写的),通过这个分析器来判断我们SQL语法的正确性。 语法正确之后,再通过内部实现的优化器对我们的SQL进行一些优化,包括成本Cost计算等等(这就是为什么我们觉得某个SQL理论上会走索引,但是执行计划显示却没有走索引的原因),最终生成我们SQL的执行计划。当然,我们也可以通过FORCE_INDEX(....)强制走索引 优化完成之后,会进入MYSQL内部的执行器,然后通过执行器调用我们这张表对应的存储引擎,比如Innodb, MyISAM, Memory等等。 执行引擎会根据优化器分析出来的结果,也就是通过最优索引去对应的索引树上找到对应的数据,同时进行索引树的维护。
四、MYSQL的锁与事务的隔离级别
原子性(Atomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。 一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性。 隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。 持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。
脏写:当多个事务对同一条数据进行操作时,最后的更新覆盖了由其他事务的更新,那么其他事务的更新数据即丢失,这种问题称为脏写。 脏读:事务A读到了事务B已经修改但尚未提交的数据,事务A还在这个数据基础上做了操作。此时,如果事务B回滚,事务A读取的数据无效,不符合一致性要求。 不可重复读:事务A内部的相同查询语句在不同时刻读出的结果不一致,不符合隔离性。 幻读:事务A读取到了事务B提交的新增数据,不符合隔离性。
读未提交:事务A能够读取到事务B没有提交的数据。 读已提交:事务A只能读取到事务B已提交的数据 可重复读:事务A在当前事务中,相同SQL读取到的数据都是一样的。 串行化:相当于加了事务级别的锁。事务A提交之后,其他事务才能往下执行。
show variables like '%isolation%'
;设置事务隔离级别:set transaction_isolation='REPEATABLE-READ'
;Mysql默认的事务隔离级别是可重复读,用Spring开发程序时,如果不设置隔离级别默认用Mysql设置的隔离级别,如果Spring设置了就用已经设置的隔离级别在具体讲解这四种隔离级别之前,我们先了解一下MYSQL锁的知识,这是个大前提。锁分类:从性能上分为乐观锁(用版本对比来实现)和悲观锁 从对数据库操作的类型分,分为读锁和写锁(都属于悲观锁) 读锁(共享锁,S锁(Shared)):针对同一份数据,多个读操作可以同时进行而不会互相影响 写锁(排它锁,X锁(eXclusive)):当前写操作没有完成前,它会阻断其他写锁和读锁 从对数据操作的粒度分,分为表锁和行锁
InnoDB支持事务 InnoDB支持行级锁
set transaction_isolation='read-uncommitted';
, 接下来开启两个会话,如下图: set transaction_isolation='read-committed';
, 接下来开启两个会话,如下图:可重复读
set transaction_isolation='read-committed';
, 接下来开启两个会话,如下图:串行化 首先把隔离级别设为读未提交 set transaction_isolation='read-committed';
, 接下来开启两个会话,如下图:
五、深入理解MVCC
六、Innobd的核心之BurfferPool缓存机制
因为来一个请求就直接对磁盘文件进行随机读写,然后更新磁盘文件里的数据性能可能相当差。 因为磁盘随机读写的性能是非常差的,所以直接更新磁盘文件是不能让数据库抗住很高并发的。Mysql这套机制看起来复杂,但它可以保证每个更新请求都是更新内存BufferPool,然后顺序写日志文件,同时还能保证各种异常情况下的数据一致性。更新内存的性能是极高的,然后顺序写磁盘上的日志文件的性能也是非常高的,要远高于随机读写磁盘文件。正是通过这套机制,才能让我们的MySQL数据库在较高配置的机器上每秒可以抗下几干的读写请求。
比如我们的SQL是一个update语句,实际上,MYSQL在更新之前会先进行查询。经过MYSQL优化器优化之后得到最终的执行计划,然后执行器会先去磁盘中找到对应的数据,因为数据在磁盘上是以页的形式存储的,根据操作系统的预读取功能,会加载一整页的数据到BufferPool缓存池中。 将更新之前的数据写入undo_log,方便回滚操作。 undo_log写入成功之后,然后在BufferPool缓存池中进行数据更新。 BufferPool数据更新成功之后,会将最新的数据写入Redo_log_buffer。 在提交事务的时候,会将Redo_log_buffer中的数据写入Redo_log,主要作用是用于恢复BufferPool的数据 在操作5进行的同时,会异步开一个线程去将bin_log日志写入bin_log文件 bin_log文件写入成功之后,最后会在redo_log文件中对应的数据打上一个commit的标记,为了保证事务提交后,redo_log和bin_log的数据一致性。
在 GitHub猿 还有更多优质项目系统学习资源,欢迎分享给其他同学吧!
「Java就该这么学」建立了读者Java交流群,大家可以添加小编微信进行加群。欢迎有想法、乐于分享的朋友们一起交流学习。
扫描添加好友邀你进Java群,加我时注明【姓名+公司+职位】
版权申明:内容来源网络,版权归原作者所有。如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。
往日文章: