• Java后端面试:MySQL面试篇(底层事务、SQL调优)


    👨‍🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
    🌌上期文章:Java后端面试:Redis面试篇(原理+场景题)
    📚订阅专栏:Java后端面试
    希望文章对你们有所帮助

    MySQL可能是面试最常问的了,经常喜欢问MySQL的优化,包括定位慢查询、SQL执行计划、索引、优化经验等等,相关的详细原理也可以看之前的专栏:
    MySQL进阶
    在这里更多是对常见面试题做个总结。

    SQL优化

    定位慢查询

    一般的慢查询可能是这些情况:

    1、聚合查询
    2、多表查询
    3、表数据量过大查询
    4、深度分页查询

    表象:页面加载过慢、接口压测的响应时间过长(超过1s)。

    定位慢查询可以通过开源工具,但是我没有用过,就不介绍了,还有一种慢查询的定位方式是通过MySQL自带的慢查询日志

    慢查询日志记录了所有执行时间超过指定参数(单位:秒)的所有SQL语句的日志
    若要开启慢查询日志,需要修改配置文件

    # 开启慢查询开关
    slow_query_log = 1
    # 设置慢查询时间为2s
    long_query_time = 2
    
    • 1
    • 2
    • 3
    • 4

    SQL语句执行慢,如何分析(SQL执行计划)

    若这个SQL语句执行的很慢,如何分析呢?

    通过SQL执行计划,可以找到SQL执行慢的原因

    可以采用EXPLAIN或者DESC命令获取MySQL如何执行SELECT语句的信息,只需要在select之前加上这两种关键词。
    查询出来后需要关注的字段:

    1、possible_key:当前SQL可能会用到的索引
    2、key:当前SQL实际命中的索引
    3、key_len:索引占用的大小
    4、Extra:额外的优化建议
    (1)Using where;Using Index:查找过程用了索引,需要的数据在索引列中能找到,无需回表查询
    (2)Using index condition:查找过程用了索引,但需要回表查询
    5、type:SQL的连接类型,性能由好到差为NULL、system、const、eq_ref、ref、range、index、all
    (1)system:查询系统中的表
    (2)const:根据主键查询
    (3)eq_ref:主键索引查询或唯一索引查询
    (4)ref:索引查询
    (5)range:范围查询
    (6)index:索引树扫描
    (7)all:全盘索引

    其中,我们可以通过key和key_len检查是否命中索引(索引是否有失效的情况)通过type查看SQL是否有进一步的优化空间,通过extra查询是否有回表的情况,如果出现则可以通过添加索引或者修改返回的字段来修复

    索引及其底层

    1、索引的作用:

    索引是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库还维护了一个B+树,可以在B+树上实现高级查找算法。
    若没有索引,查找数据可能要遍历整张表,效率过于低。

    2、为什么不用二叉搜索树、红黑树、或者B树?

    (1)二叉搜索树:最坏的二叉树(按顺序插入),相当于退化成了一个链表,查询效率与全盘扫描没区别(复杂度不稳定)
    (2)红黑树:虽然复杂度更稳定了,但是如果数据特别大的话,由于红黑树每个结点只有两个子结点,还是会导致树太高,查询效率低
    (3)B树:多叉路衡查找树,有很多分支(若最大度数为5,则每个节点最多存储4个key)。
    (4)B+树:比起B树更适合实现外存储索引结构,相比较B树,B+树只有叶子节点才会存储数据,非叶子节点只是一个导航定位的作用,除此之外,叶子节点还加上了双向链表。InnoDB引擎就是用B+树来实现的索引结构。
    B树与B+树的对比:
    ①磁盘读写代价B+树更低;②查询效率B+树更稳定;③B+树更便于扫库和区间查询(由于双向链表,只需要根据区间的左边界查,然后直接扫链表就好了)

    聚簇索引、非聚簇索引、回表查询

    1、什么是聚簇索引和非聚簇索引?(其实还有一种称呼是聚集索引和二级索引)

    1、聚簇索引(聚集索引):将存储数据域索引放在一起,索引的叶子节点保存了行数据。必须要有聚集索引而且只能有一个
    2、非聚簇索引(二级索引):将数据与索引分开存储,索引的叶子节点关联的是对应的主键。可以存在多个。

    2、聚集索引的选取规则?

    1、存在主键,则主键索引就是聚集索引
    2、不存在主键,则第一个唯一索引作为聚集索引
    3、没有主键和唯一索引,则InnoDB引擎会自动生成rowid作为隐藏的聚集索引

    3、什么是回表查询

    先通过二级索引找到对应的主键值,到聚集索引中查找整行数据并返回,这个过程就是回表查询

    覆盖索引、超大分页查询

    覆盖索引:查询使用了索引,并且需要返回的列在该索引树中已经能够全部找到。

    例如,id为主键索引,name为普通索引,则下列指令符合覆盖索引

    select id, name from tb_user where name = 'wxj';
    select * from tb_user where id = 2; # 查询聚集索引,叶子节点就是一整行的数据,也算覆盖索引
    
    • 1
    • 2

    如果不符合覆盖索引,例如select id,name,gender from tb_user where name='Arm',首先在二级索引中查询name字段,查询完成之后由于叶子节点只包含了id和name,所以还需要进行回表查询,这时候的效率就会很低。这也就是为啥要尽量避免select *,除非查询的条件是根据主键的。

    MySQL超大分页处理:当数据量很大的时候用limit做分页查询,越往后,分页查询的效率就越低。这是因为limit底层会给前面的记录都先排序,再去查询,这种排序代价可能会很大。

    优化思路:一般分页查询时,通过创建覆盖索引能够比较好的提高性能,可以通过覆盖索引+子查询的形式进行优化。

    select *
    from tb_sku t,
    	(select id from tb_sku order by id limit 9000000,10) a
    where t.id = a.id;
    
    • 1
    • 2
    • 3
    • 4

    索引创建原则

    1、针对数据量较大,且查询频繁的表建立索引(增加用户体验)
    2、针对常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引
    3、尽量选择区分度高的列作为索引,尽量建议唯一索引,区分度越高,使用索引的效率就越高
    4、若是字符串类型的字段,可以建立前缀索引
    5、尽量使用联合索引,减少单列索引,因为查询的时候联合索引很多时候可以覆盖索引,避免回表,提高查询效率
    6、控制索引的数量,索引越多,维护的代价就越大

    重点是1、2、5、6条,这在面试的时候肯定要讲到。

    索引失效的情况

    1、违背最左前缀法则

    若索引了多列,要遵守最左前缀法则。也就是查询从索引的最左前列开始,否则索引会完全失效。并且最好不跳过索引中的列,否则会部分失效

    2、范围查询右边的列,不能使用索引

    # address是范围查询status > '1'的列,address失效
    explain select * from tb_seller where name = '小米科技' and status > '1' and address = '北京市';
    
    • 1
    • 2

    3、在索引列上进行运算操作,索引会失效

    select * from tb_seller where substring(name,3,2) = '科技';
    
    • 1

    4、字符串不加单引号,可能会索引失效(因为底层可能会出现类型转换)

    5、以%开头的Like模糊查询,索引失效

    若仅仅是尾部模糊查询,索引不会失效。如果是头部模糊查询,索引失效

    SQL优化经验

    从五个方面回答这个问题:

    1、表的设计优化(参考了阿里的开发手册)
    (1)设置合适的数值(tinyint、int、bigint)
    (2)设置合适的字符串类型(char和varchar,其中char定长效率高,varchar长度可变但效率偏低)
    2、索引优化(优化的原则、索引失效的情况)
    3、SQL语句优化
    (1)避免使用select *
    (2)避免索引失效
    (3)尽量用union all代替union,因为union会增加一次过滤
    (4)避免在where子句中对表达式进行表达式操作(因为也会出现索引失效,如substring)
    4、主从复制、读写分离:若读的操作很多,为了避免写操作造成的性能影响,可以使用读写分离,其解决的
    5、分库分表

    MySQL事务

    事务特性

    ACID特性:

    (1)原子性
    (2)一致性
    (3)隔离性
    (4)持久性

    并发事务问题、隔离级别

    并发事务问题有哪些?解决的方法是什么?

    并发事务问题:脏读、不可重复读、幻读
    隔离级别:读未提交、读已提交、可重复读(默认)、串行化

    并行事务问题:

    问题描述
    脏读一个事务读到另外一个事务还没有提交的数据
    不可重复读一个事务先后读取同一条记录,但两次读取的数据不同
    幻读一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现了这行数据已经存在,好像出现了“幻影”(插入的时候显示存在,再次查询还是查不到,因为此时已经解决了不可重复读)

    解决方案:对事务进行隔离

    1、读未提交:三种问题都存在
    2、读已提交:解决脏读
    3、可重复读:解决脏读和不可重复读(默认)
    4、串行化:三种问题都解决

    级别越高,越安全,但是性能越低。一般情况下是不会用串行化的,如果一定要在默认(可重复读)的隔离级别下解决幻读,需要使用MVCC。

    undo log与redo log的区别

    需要记住两个概念:

    缓冲池:主内存中的一个区域,里面可以缓存磁盘上经常操作的真实数据,在执行增删改查时,先操作缓冲池数据(若没有就从磁盘加载并缓存),并以一定频率刷新到磁盘,从而减少磁盘IO,加快处理速度
    数据页:InnoDB存储引擎磁盘管理的最小单元,每个页的默认大小为16KB,页中存储的是行数据

    redo log:重做日志,记录的是事务提交时数据页的物理修改,用来实现事务的持久性
    该日志由重做日志缓冲重做日志文件组成,前者在内存中,后者在磁盘中。当事务提交后会把所有修改信息都存到该日志文件中,用于在刷新脏页到磁盘发生错误时,进行数据恢复。

    如果业务出现错误导致数据不同步,当然也可以直接把缓冲池的数据加载回磁盘,但是这样的方式将会很影响性能。
    而由于日志文件都是追加的,所以使用日志文件的这种方式的磁盘IO都是顺序的,就会提高效率。这种方式称为WAL(先写日志)

    undo log:回滚日志,用于记录数据被修改前的信息,作用包含两个:提供回滚MVCC(多版本并发控制)。undo log与redo log记录物理日志不同,它是逻辑日志

    当delete一条记录时,undo log会记录一条insert记录,反之亦然。
    当update时,会记录一条相反的update记录。
    当执行rollback时,可以从undo log中的逻辑记录读取到相应的内容并进行回滚。

    也就是说,undo log用于实现事务的一致性和原子性

    总结:

    1、redo log记录的是数据页的物理变化,服务宕机可以用来同步数据
    2、undo log记录的是逻辑日志,当事务回滚时,通过逆操作恢复数据
    3、redo log保证了事务的持久性,undo log保证了事务的原子性和一致性

    解释MVCC

    undo log和redo log保证了事务的原子性、持久性、一致性,而隔离性是如何保证的呢?

    隔离性由两个因素决定:
    1、锁:排他锁(若一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁)
    2、MVCC:多版本并发控制

    解释一下MVCC?(事务中的隔离性是如何保证的呢?)

    1、MVCC是多版本并发控制。指维护一个版本的多个数据,使得读写操作没有冲突。
    2、MVCC的具体实现,主要依赖于数据库记录中的隐藏字段undo log日志readView
    (1)隐藏字段:
    ①DB_TRX_ID:最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID
    ②DB_ROLL_PTR:回滚指针,指向这条记录的上一个版本,用于配合undo log,指向上一个版本
    ③DB_ROW_ID:隐藏主键,若表结构没有指定主键,将会生成该隐藏字段
    (2)undo log:回滚日志,在增删改的时候产生的便于数据回滚的日志。
    需要重点理解undo log版本链,可以看我之前的MySQL进阶的专栏,里面有详细解析。
    (3)readView(读视图):是快照读(记得区分一下什么是当前读,什么是快照读)SQL执行时MVCC提取数据的一句,记录并维护系统当前活跃的事务(未提交的)id。readView包含四个核心字段:
    ①m_ids:当前活跃的事务ID集合
    ②min_trx_id:最小活跃事务ID
    ③max_trx_id:预分配事务ID,即当前最大事务ID+1
    ④creator_trx_id:readView创建者的事务ID
    而具体的工作方式,需要依赖undo log版本链,具体依旧看我之前的文章。

    需要记住,不同的隔离级别下,生成ReadView的时机不同
    (1)READ COMMITED:每次执行快照读时生成readView
    (2)Repeatable Read:仅在第一次快照读生成readView,后面复用(就是因为这样,才解决了不可重复读)

    MySQL主从同步原理

    主从同步无非就是要实现主结点到从结点的复制操作,即主从复制。
    MySQL主从复制的核心是二进制日志(BINLOG),记录了所有的DDL和DML语句。
    主从复制的步骤分为三步:

    1、主库在事务提交时,会把数据变更记录在二进制文件BINLOG中
    2、从库读取主库的二进制日志文件BINLOG,写入到从库的中继日志Relay log
    3、从库重做中继日志中的事件,将改变反映它自己的数据

    MySQL分库分表

    我自己做的项目还没有用到海量数据,所以我根本没有用过分库分表,既然没有,面试被问到就说自己了解,能懂一些概念就好了。

    为什么要用分库分表?

    当使用主从分离、读写分离(主库写数据,从库读数据),可以分担访问的压力,但是主库和从库的表都是一样的,海量数据下不合适。
    单表的数据量达到了1000W,优化就已经解决不了性能的问题了,而且还会出现IO瓶颈、CPU瓶颈

    分库分表的分类?

    1、垂直拆分
    (1)垂直分库:以表为依据,根据业务把不同表拆到不同库中。
    (2)垂直分表:以字段为依据,根据字段属性将不同字段拆分到不同表中。(例如把大字段、不常用的字段拆分出来单独放在一张表)
    2、水平拆分
    (1)水平分库:将一个库的数据拆分到多个库中
    (2)水平分表:将一个表的数据拆分到多个表中(可以在同一个库内)。

  • 相关阅读:
    springBoot复杂对象表示和lombok的使用
    LCR 127.跳跃训练
    [SpringMVC笔记] SpringMVC-12-放行静态资源访问
    定义爬虫规则和数据存储
    聚观早报 | 荣耀Magic Vs2首销;iQOO 12系列已入网
    Blazor和Vue对比学习(基础1.5):双向绑定
    MOS管和IGBT区别,一看就懂
    ARM应用处理器系列
    2023第五届中国(济南)国际中医药产业展览会(CJTCM)
    UEFI基础——测试用例Hello Word
  • 原文地址:https://blog.csdn.net/m0_52380556/article/details/136738354