(拿铁)MySQL-真实面试题/超高频八股速成/中频八股提升/低频八股扩展(持续更新中)

之前看面经分享帖的时候,学到了已经上岸大厂的前辈的做法。在准备暑期实习时,我也效仿着根据以往的真实面经整理八股。从牛客、小破站等各个平台搜集了上千篇真实面经,自己整理得到了面试题,根据题目在面试题中出现的频率以及我自己、交流群、好朋友面试被问到的频率进行了分类整理,得到⭐🌟💡三种级别的。在此,给大家分享一下我自己面试被问到的题目,以及我根据以往面经整理得到的题目。各位uu可在专栏关筑一波:

https://www.nowcoder.com/creation/manager/columnDetail/Mq7Xxv

Top 博主都订阅了,比如“Java 抽象带篮子”(7000+ 粉丝),在这里感谢篮子哥的支持!

所有内容经过科学分类巧妙标注,针对性强,让你的学习事半功倍:

  • 必须掌握(必看):时间紧迫时的救命稻草,优先攻克核心要点。(可参考神哥的高频题,但我整理出来的比神哥还会多一些,另外还包括以下内容
  • 🌟 尽量掌握(有时间就看):适合两周以上备考时间的同学稳步提升,冲击大厂的uu们建议看!
  • 💡 了解即可(知识拓展):时间充裕时作为补充,拓宽视野,被问到的概率小,但如果能答出来就是加分项
  • 🔥 面试真题:根据真实面经整理出来的面试题,有些可能难度很高,可根据自身水平酌情参考。

按照推荐观看顺序 “🔥⭐> 🔥🌟 > > 🔥💡” 有条不紊地学习,让每一分每一秒都用在刀刃上,自此一路畅行。

全面覆盖面试核心知识

面试真题涵盖技术领域的核心考点,从高频热点到冷门难点一网打尽。以下是部分模块概览:

Java基础&集合 :

https://www.nowcoder.com/issue/tutorial?zhuanlanId=Mq7Xxv&uuid=4f5b4cac4b9f4dee8b4b213851c154c5

JVM篇:

https://www.nowcoder.com/issue/tutorial?zhuanlanId=Mq7Xxv&uuid=c87d9ad65eb840728ae63774893bccf5

Java并发编程&JUC:

https://www.nowcoder.com/issue/tutorial?zhuanlanId=Mq7Xxv&uuid=28c748189f6b471f9f4218791778f41c

MySQL

https://www.nowcoder.com/issue/tutorial?zhuanlanId=Mq7Xxv&uuid=55b03d6d16604319a24395f393d615be

Redis:

https://www.nowcoder.com/issue/tutorial?zhuanlanId=Mq7Xxv&uuid=77bd828f85984c22858c3724eef78723

计网:

https://www.nowcoder.com/issue/tutorial?zhuanlanId=Mq7Xxv&uuid=65e9951c2e754d7086d26b9b46aa4a1a

后续还将持续更新 操作系统、设计模式、场景题、智力题等丰富内容

独特解析:知其然,更知其所以然

我整理的八股面经绝非简单的问答堆砌。

每一道题目都配有深度剖析的思考过程,在你看题之前,便清晰呈现出题意图,让你迅速抓住题目核心,加深对题目的理解与记忆,做到 “知己知彼,百战不殆”。

Java基础&集合举例

MySQL

Redis

JVM

Java并发(JUC)

计算机网络

助力你举一反三,深度梳理知识点之间的内在逻辑联系,真正实现知识的融会贯通,做到知其然更知其所以然。

后续还会分享如何包装项目、leetcode 刷题模版与刷题技巧、各种学习经验以及真实面经等,从多个角度助力牛u提升技术能力和面试水平。

还是那句话:

1、简历上相关技术点对应的面试题一定要准备,因为写在简历上了,面试官就默认你会,答不上来的话就很减分

2、抓大放小,优先重点高频八股,根据自身情况进行准备。

更新日志:

2025.4.9 更新:存储引擎(主要考察InnoDB存储引擎的特性以及与别的存储引擎的区别)与索引结构(超级重要:索引类型、B+树索引的特点、与B树的区别)

2025.4.10 更新:基础与语法(这部分重点不多、内连接和外连接经常问!)

2025.4.13 更新:索引的使用(覆盖索引、回表、索引下推以及该怎么建立索引、查看索引的效果)

2025.4.15 更新:事务四大特性(ACID四大特性以及各自如何实现的一定要清楚)

2025.4.18 更新:事务隔离级别以及实现(主要考察不同的隔离级别解决了什么问题其中RC和RR是重点,MVCC也经常问)

2025.4.21 更新:binlog(问的比较多的就是binlog的三种格式和与redolog的比较)Redo log(redo log如何实现持久化一定要清楚)以及 两阶段提交(考察不算多,能说出来两阶段提交的背景以及大致过程即可)

2025.4.24 更新:锁(锁的分类要能说出大概,乐观/悲观锁的实现,RR级别的三种锁,可能会结合sql语句让判断加的是什么锁)

2025.4.26 更新:死锁(可以结合操作系统死锁看,解决死锁的思路是相通的)

2025.2.27 更新:高可用(问的比较少,一般就是问MySQL主从复制过程以及三种复制的模式)

暂时更新完毕...

基础与语法(这部分重点不多、内连接和外连接经常问!)

🔥⭐Q: COUNT 作用于主键列和非主键列时,结果会有不同吗?

思考过程:

这个问题考察对 COUNT 函数用法的理解,需要区分主键列(不允许 NULL)和非主键列(允许 NULL)的情况。

  • COUNT() 返回非 NULL 值的数量。
  • 主键列不允许 NULL 值。
  • 非主键列可以包含 NULL 值。
  • COUNT(主键) 统计所有行数。
  • COUNT(非主键) 统计非 NULL 值数量。

回答提问:

好的,COUNT 函数用于统计指定列中非 NULL 值的数量。当 COUNT 作用于主键列和非主键列时,结果可能会有所不同,这取决于该非主键列是否允许 NULL 值。

  • COUNT(主键列):由于主键列在定义时通常被约束为不允许存储 NULL 值,因此 COUNT(主键列) 会统计表中的所有行数,因为每一行在该主键列上都有一个非 NULL 的值。
  • COUNT(非主键列):非主键列通常允许存储 NULL 值。在这种情况下,COUNT(非主键列) 将会统计该列中所有非 NULL 值的数量。如果该列中存在 NULL 值,那么这些 NULL 值不会被计入 COUNT 的结果中,因此结果可能会小于表中的总行数。

所以,总结来说,COUNT(主键) 通常用于获取表的总记录数,而 COUNT(非主键) 则用于统计该特定列中实际有值的记录数,会忽略掉 NULL 值。

🔥⭐Q: MySQL 中的内连接(INNER JOIN)和外连接(OUTER JOIN)有什么主要的区别?

思考过程:

这个问题考察对 SQL 连接类型的理解,需要说明内连接返回匹配的行,外连接则包含匹配和不匹配的行,并区分左外连接和右外连接。

  • 内连接: 返回两个表中匹配的行(交集)。
  • 外连接: 返回匹配的行加上驱动表中不匹配的行。
  • 左外连接: 保留左表所有行,右表不匹配则填充 NULL。
  • 右外连接: 保留右表所有行,左表不匹配则填充 NULL。

回答提问:

好的,MySQL 中的内连接(INNER JOIN)和外连接(OUTER JOIN)在数据查询时有着本质的区别:

内连接(INNER JOIN:它会只返回两个表中连接条件相匹配的行。也就是说,只有当连接的两个表中都存在满足 ON 子句条件的记录时,这条记录才会被包含在结果集中。内连接关注的是两个表的交集部分。

外连接(OUTER JOIN:外连接则会返回两个表中匹配的行,并且还会包含某个表(或两个表)中不匹配的行。MySQL 外连接主要分为两种:

  • 左外连接(LEFT JOIN 或 LEFT OUTER JOIN):它会返回左表中的所有行,以及右表中与左表匹配的行。如果右表中没有与左表某行匹配的记录,那么在结果集中,右表对应的列将会显示为 NULL。左连接适用于需要保留左表所有数据的场景。
  • 右外连接(RIGHT JOIN 或 RIGHT OUTER JOIN):它会返回右表中的所有行,以及左表中与右表匹配的行。如果左表中没有与右表某行匹配的记录,那么在结果集中,左表对应的列将会显示为 NULL。右连接适用于需要保留右表所有数据的场景。

🔥🌟Q: HAVING 子句和 WHERE 子句有什么区别?在什么场景下会使用它们?

思考过程:

这个问题考察对 HAVINGWHERE 子句的理解和使用场景的掌握,需要强调 WHERE 在分组前过滤,HAVING 在分组后过滤,以及 HAVING 可以使用聚合函数。

  • WHERE: 分组前过滤,不能使用聚合函数。
  • HAVING: 分组后过滤,可以使用聚合函数。
  • WHERE 场景: 对原始数据行进行筛选。
  • HAVING 场景: 对分组聚合后的结果进行筛选。

回答提问:

好的,HAVING 子句和 WHERE 子句都是用于在 SQL 查询中指定过滤条件的,但它们之间有几个关键的区别,并且在不同的场景下使用:

WHERE 子句

  • WHERE 子句用于在 GROUP BY 分组和聚合函数计算之前对数据行进行过滤。
  • 它作用于原始的每一行数据。
  • 不能在 WHERE 子句中使用聚合函数,因为聚合函数是在分组之后才计算出来的。

HAVING 子句

  • HAVING 子句用于在 GROUP BY 分组和聚合函数计算之后对数据行进行过滤。
  • 它作用于分组后的结果集。
  • 可以在 HAVING 子句中使用聚合函数,因为此时聚合函数的结果已经计算出来了。

使用场景

  • WHERE 子句通常用于对原始数据行进行筛选,例如筛选出某个时间段内的订单,或者筛选出某个特定用户的记录等。它的目的是在分组和聚合之前尽可能地减少需要处理的数据量。
  • HAVING 子句通常用于对分组聚合后的结果进行二次筛选,例如筛选出订单总金额大于某个值的用户组,或者筛选出平均分高于某个值的班级等。由于它可以使用聚合函数,因此非常适合对分组统计结果进行条件过滤。

简单来说,WHERE 是用来筛选“行”的,而 HAVING 是用来筛选“组”的。如果过滤条件涉及到聚合函数的结果,那么必须使用 HAVING 子句。

🔥🌟Q:了解 CHARVARCHAR 这两种字符串类型的区别吗?

思考过程:

这个问题考察对 CHARVARCHAR 数据类型的理解,需要从存储方式、长度处理以及性能等方面进行比较。对于性能追问,需要考虑理论上的差异以及在实际应用中的影响。

回答提问:

好的,CHARVARCHAR 都是 MySQL 中用于存储字符串的列类型,它们之间最主要的区别在于长度处理

CHAR(n) 是一种固定长度的字符串类型。当您定义一个 CHAR(n) 类型的列时,无论实际存储的字符串长度是否小于 n,MySQL 都会分配 n 个字符所占用的固定存储空间。如果实际存储的字符串长度小于 n,MySQL 会在末尾填充空格以达到指定的长度。但在检索数据时,尾部的空格通常会被忽略(取决于具体的 MySQL 版本和配置)。

VARCHAR(n) 是一种可变长度的字符串类型。当您定义一个 VARCHAR(n) 类型的列时,MySQL 只会分配存储实际字符串所需的空间,再加上额外的 1 到 2 个字节用于存储字符串的长度信息(如果字符串的最大长度小于或等于 255 字节,则使用 1 个字节;如果大于 255 字节,则使用 2 个字节)。这里的 n 代表的是可以存储的最大字符数,实际占用的字节数取决于字符集。

总的来说,选择 CHAR 还是 VARCHAR 需要根据具体的业务场景和数据特点来决定。如果存储的字符串长度基本固定,并且长度较短,CHAR 可能是一个不错的选择。如果字符串长度变化较大,或者为了节省存储空间,VARCHAR 通常是更合适的选择。

🔥🌟Q: 能说一下 SQL 中 SELECT, FROM, JOIN, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT 的执行顺序吗?

思考过程:

这个问题考察对 SQL 查询语句执行顺序的理解,这是优化 SQL 的基础。需要按照逻辑执行的先后顺序进行说明。

  1. FROM 和 JOIN: 确定数据来源和连接表。
  2. WHERE: 对原始数据进行初步筛选。
  3. GROUP BY: 对数据进行分组。
  4. HAVING: 对分组后的结果进行筛选。
  5. SELECT: 选择要返回的列。
  6. ORDER BY: 对结果进行排序。
  7. LIMIT: 限制返回的行数。

回答提问:

好的,SQL 查询语句中这些子句的执行顺序通常是这样的:

  1. FROM:首先,数据库会确定要查询的数据来源,也就是指定的基础表。如果涉及到多表查询,还会通过 JOIN 操作(比如 INNER JOIN, LEFT JOIN, RIGHT JOIN)将相关的表连接起来,连接时会使用 ON 子句来指定连接条件。
  2. WHERE:接着,数据库会对连接后的结果集进行初步的过滤,WHERE 子句用于指定过滤条件,只有满足条件的行才会被保留下来。
  3. GROUP BY:然后,如果 SQL 语句中包含 GROUP BY 子句,数据库会根据指定的字段对结果集进行分组,将具有相同字段值的行归为一组。
  4. HAVING:在分组之后,如果 SQL 语句中包含 HAVING 子句,数据库会对分组后的结果进行进一步的过滤,HAVING 子句通常用于对分组后的聚合结果进行条件判断,与 WHERE 子句不同的是,HAVING 子句中可以使用聚合函数。
  5. SELECT:在经过前面的步骤之后,数据库会根据 SELECT 子句中指定的列,从结果集中选择要返回的字段。这里可以包括普通的列、聚合函数的结果以及计算表达式等。
  6. ORDER BY:如果 SQL 语句中包含 ORDER BY 子句,数据库会根据指定的字段对结果集进行排序,可以指定升序(ASC)或降序(DESC),默认是升序。
  7. LIMIT:最后,如果 SQL 语句中包含 LIMIT 子句,数据库会限制返回结果集的行数,通常还会配合 OFFSET 来实现分页查询。

所以,总结起来,逻辑上的执行顺序大致是:FROM -> JOIN -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORDER BY -> LIMIT

🔥🌟Q:COUNT(*)COUNT(1)COUNT(主键)COUNT(普通字段) 有什么区别以及它们的性能考量吗?

思考过程: 这个问题考察对 SQL 中 COUNT 函数不同用法的理解,以及它们在统计行数时的行为和潜在的性能差异。需要从统计范围、是否包含 NULL 值以及执行效率等方面进行分析。

  • 统计范围: 它们分别统计哪些行?
  • NULL 值处理: 它们对 NULL 值的处理方式是否一样?
  • 性能考量: 在不同的场景下,它们的性能会有什么差异?与索引有关吗?

回答提问: 好的,COUNT() 函数在 SQL 中用于统计行数,不同的参数会导致不同的统计行为和性能表现:

COUNT(*)COUNT(1) 这两种用法实际上是等价的。它们都会统计表中的所有行,包括那些列值为 NULL 的行。从性能上来说,在现代的 MySQL 版本中,优化器通常会对这两种写法进行相同的处理,选择效率最高的索引来执行统计,因此它们的性能差异可以忽略不计。通常推荐使用 COUNT(*),因为它更符合 SQL 的标准语法,可读性也更好。实际上,COUNT() 会被优化器转换为 COUNT(0),而 COUNT(1) 中的 1 只是一个常量表达式,两者都不会是 NULL。

COUNT(主键) 会统计指定主键列中非 NULL 的行数。由于主键约束保证了主键列的值不可能为 NULL,因此,对于一个定义了主键的表来说,COUNT(主键) 的结果通常与 COUNT(*)COUNT(1) 相同。但在执行逻辑上,COUNT(主键) 需要读取主键列的值,虽然主键通常有索引,但相比于直接统计行数,可能会稍微多一些开销。

COUNT(普通字段) 则会统计指定普通字段中非 NULL 的行数。如果该字段允许为 NULL,那么 COUNT(普通字段) 的结果可能会小于 COUNT(*)。在性能方面,如果该字段上没有索引,那么数据库可能需要进行全表扫描。如果该字段上有索引,则可能会利用索引进行统计,性能会有所提升。

总的来说,在不需要特定字段的非 NULL 行数时,推荐使用 COUNT(*)COUNT(1),它们在语义上更清晰,并且通常能获得最佳的性能。而 COUNT(字段) 则适用于需要统计某个特定字段的有效值(非 NULL 值)的场景。在实际应用中,我们应该根据具体的统计需求选择合适的 COUNT 用法,并且可以通过 EXPLAIN 分析 SQL 的执行计划,来了解不同写法的性能表现。

🔥🌟Q:MySQL 中都有哪些常见的约束类型呢?

思考过程: 这个问题考察对 MySQL 数据库表约束的理解,约束是保证数据完整性和准确性的关键。需要回忆并列举 MySQL 支持的常见约束类型,并简述它们的作用。

  • 主要约束类型: 有哪些核心的约束类型?
  • 作用: 每种约束分别用来做什么?它们是如何保证数据质量的?
  • MySQL 支持情况: MySQL 是否支持所有的标准 SQL 约束?

回答提问: 好的,MySQL 中常见的约束主要有以下几种,它们用于保证数据库中数据的准确性和一致性:

首先是主键约束(PRIMARY KEY),它的作用是唯一标识表中的每一条记录,保证记录的唯一性,并且主键字段不能为空。一张表只能有一个主键。

其次是外键约束(FOREIGN KEY),它用于建立表与表之间的关联,确保引用完整性。一个表的外键会引用另一个表的主键。

然后是唯一性约束(UNIQUE),它保证了表中的某个字段或字段组合的值是唯一的,不允许重复。一张表可以有多个唯一性约束。

非空约束(NOT NULL) 顾名思义,就是确保表中的某个字段不允许为空,必须有值。

默认约束(DEFAULT) 用于给表中的某个字段设置默认值,如果在插入数据时没有为该字段指定值,就会使用默认值。

最后是检查约束(CHECK),它用于限制表中某个字段的取值范围。需要注意的是,MySQL 虽然支持 CHECK 关键字,但在实际执行中会忽略它,不会进行数据验证。 如果需要在 MySQL 中实现类似检查约束的功能,通常会使用触发器或者在应用程序层面进行控制。

总的来说,MySQL 支持前五种约束,通过它们可以有效地维护数据的完整性和一致性。

🔥🌟Q:你能简单介绍一下什么是外键约束吗?它的作用是什么?

思考过程: 这个问题考察对外键约束的基本概念和作用的理解。需要解释外键的定义以及它如何保证表之间数据的引用完整性。

回答提问: 好的,外键约束是 MySQL 中用来建立和加强两个表之间连接的一种机制,它用于维护数据的引用完整性

简单来说,外键是指在一个表(通常称为从表子表)中的一个或多个列,这些列的值引用另一个表(通常称为主表父表)中一个或多个列的值。被引用的列通常是主表的主键或唯一键。

外键约束的主要作用是确保从表中的外键值必须在主表中存在对应的记录。这样可以防止在从表中插入无效的数据,或者在主表中删除被从表引用的数据,从而保证了两个表之间相关数据的一致性

例如,如果有一个订单表和一个用户表,订单表中的 user_id 列可以作为外键,引用用户表中的 id(主键)。这样就保证了每一个订单都必须关联到一个已存在的用户。如果尝试插入一个不存在的 user_id 到订单表,或者尝试删除一个还有订单关联的用户,数据库就会因为外键约束而报错。

🔥🌟Q:数据库三大范式是什么?

思考过程:

这个问题考察对数据库设计理论基础的理解,需要能够清晰地描述第一范式(1NF)、第二范式(2NF)和第三范式(3NF)的内容和目标。

  • 第一范式(1NF): 它的核心要求是什么?
  • 第二范式(2NF): 它在 1NF 的基础上增加了什么要求?是为了解决什么问题?
  • 第三范式(3NF): 它在 2NF 的基础上又增加了什么要求?是为了解决什么问题?
  • 实际应用: 在实际的数据库设计中,是否需要严格遵守这三个范式?

回答提问: 好的,数据库的三大范式是数据库设计的理论基础,旨在规范数据库的结构,减少数据冗余,提高数据的一致性和完整性。

第一范式(1NF) 的核心要求是原子性,即关系模式中的每个属性都应该是不可再分的最小数据单元,不允许有重复的列和多值字段。简单来说,表中的每个字段都只包含一个值。

第二范式(2NF) 是在满足第一范式的基础上提出的,它要求表中的非主键字段必须完全依赖于主键,而不能只依赖于主键的一部分(部分依赖)。它的目的是解决数据冗余问题,例如,如果一个订单表的主键是订单 ID 和产品 ID 的组合,那么订单的地址就应该完全依赖于订单 ID 和产品 ID 的组合,而不是只依赖于订单 ID。通常,我们会将存在部分依赖关系的表拆分成多个表。

第三范式(3NF) 是在满足第二范式的基础上提出的,它要求表中的非主键字段之间不能存在传递依赖。也就是说,一个非主键字段不能通过另一个非主键字段来间接依赖于主键。它的目的是进一步减少数据冗余,提高数据的独立性。例如,在一个包含员工信息、部门信息和部门经理信息的表中,部门经理 ID 依赖于部门 ID,而部门 ID 又依赖于员工 ID,这就存在传递依赖。为了满足第三范式,通常会将部门信息和部门经理信息单独拆分成一个部门表。

虽然这三大范式是数据库设计的指导原则,但在实际的工程实践中,并不一定需要完全严格地遵守。有时候为了提高查询性能,可能会进行反范式化的设计,即允许适度的冗余,通过增加冗余字段来减少关联查询,从而提升性能,尤其是在数据量非常大的情况下。总的来说,我们需要在规范化和性能之间进行权衡,选择最适合当前业务场景的设计方案。

🔥🌟Q:请比较一下 DELETEDROPTRUNCATE 这三个 SQL 命令的区别

思考过程:

这个问题考察对 MySQL 中删除操作的理解,需要从执行速度、命令类型、删除对象、是否可回滚以及是否重置自增值等方面进行区分。

  • 执行速度: 哪个命令执行速度最快?
  • 命令类型: 它们属于 DDL 还是 DML?
  • 删除对象: 它们分别删除什么?是数据、表结构还是两者都删除?
  • 回滚性: 删除操作是否可以回滚?
  • 自增值: 删除操作是否会影响自增主键的计数器?

回答提问:

好的,DELETEDROPTRUNCATE 都是用来删除数据的 SQL 命令,但它们在功能和行为上有着明显的区别:

首先,从执行速度上来说,通常 DROP 命令最快,其次是 TRUNCATE,最慢的是 DELETE

其次,它们的命令类型不同。DROPTRUNCATE 属于数据定义语言(DDL),它们会直接操作数据库的结构。而 DELETE 属于数据操纵语言(DML),它主要操作表中的数据行。

删除对象方面,DROP TABLE 命令会删除整个表,包括表结构、数据、索引、约束和触发器等。TRUNCATE TABLE 命令则会删除表中的所有数据,但保留表的结构、索引和约束等。它相当于清空了表中的数据。而 DELETE 语句可以删除表中的部分或全部数据,但表结构不会改变。

关于回滚DELETE 操作是 DML 语句,它可以被事务控制,因此可以使用 ROLLBACK 命令进行回滚,撤销删除操作。而 DROPTRUNCATE 都是 DDL 语句,它们的操作是不可逆的,无法通过 ROLLBACK 恢复。

最后,关于自增初始值TRUNCATE TABLE 命令会重置表的自增主键计数器,使其从 1 重新开始。而 DELETE 语句在删除所有数据后,自增主键的计数器会保持删除前的最大值加 1,不会被重置。DROP TABLE 后再重新创建表,自增主键会从初始值开始。

如果你想删除整个表并且不再需要它,可以使用 DROP TABLE。如果你想快速清空表中的所有数据,并且保留表结构,可以使用 TRUNCATE TABLE。如果你想删除表中的部分数据或者需要能够回滚删除操作,那么应该使用 DELETE 语句。

🔥💡Q: 如果 MySQL 的长连接太多了,有什么好的处理方法吗?

思考过程:

这个问题考察对 管理过多长连接的策略的理解。需要从主动释放连接和限制连接数量两个方面给出建议。

  • 主动释放连接: 定期关闭空闲过长的连接。
  • 限制连接数量: 配置合理的连接池参数(最大连接数、连接生命周期、连接超时)。

回答提问:

好的,如果 MySQL 的长连接过多,可能会对服务器的性能产生负面影响,甚至导致连接数达到上限而无法处理新的请求。针对这种情况,我们可以从以下两个主要方面来处理:

  1. 主动释放不再需要的连接:定期检测并关闭空闲连接:可以在应用程序端或者 MySQL 服务器端配置机制,定期检查长时间处于空闲状态的连接,并将其关闭,从而释放服务器资源。使用完连接后及时关闭:在应用程序代码中,确保在使用完数据库连接后及时将其关闭或者归还到连接池中,避免连接被长时间占用。
  2. 限制连接的数量和生命周期:配置合理的连接池参数:如果应用程序使用了连接池(这是推荐的做法),需要仔细配置连接池的相关参数,例如设置最大连接数,防止连接池无限制地创建连接;设置最小空闲连接数,保证一定的可用连接数;设置连接的最大生命周期,强制连接在一段时间后失效并重新建立;以及设置连接的空闲超时时间,及时回收空闲连接。设置连接超时机制:在应用程序端设置连接超时时间,避免因网络问题或者服务器响应缓慢导致连接一直被占用。

🔥💡Q: 你在 MySQL 中使用过哪些常用的函数呢?

思考过程:

这个问题考察对 MySQL 常用函数的掌握情况,需要从字符串函数、数学函数、日期函数、聚合函数和条件函数等几个方面列举一些常用的函数,并说明其用途。

  • 字符串函数:CONCAT, SUBSTRING, LENGTH, REPLACE, UPPER, LOWER, TRIM, LEFT, RIGHT。
  • 数学函数:ABS, CEIL, FLOOR, MOD, POWER。
  • 日期函数:NOW, DATE_ADD, DATE_SUB, DATEDIFF, YEAR, MONTH, DAY, STR_TO_DATE。
  • 聚合函数:COUNT, SUM, AVG, MAX, MIN。
  • 条件函数:IF, IFNULL, CASE.

回答提问:

好的,我在 MySQL 中使用过不少常用的函数,根据功能可以大致分为以下几类:

  1. 字符串函数:比如 CONCAT 用于拼接字符串,SUBSTRING 用于提取子串,LENGTH 计算字符串长度,REPLACE 替换字符串中的内容,UPPER 和 LOWER 进行大小写转换,TRIM 去除字符串首尾空格,LEFT 和 RIGHT 提取左右指定长度的字符。这些函数在处理文本数据时非常有用。
  2. 数学函数:像 ABS 计算绝对值,CEIL 和 FLOOR 用于向上和向下取整,MOD 计算余数,POWER 计算幂值等等。这些函数在进行数值计算时会用到。
  3. 日期函数:例如 NOW 获取当前日期时间,DATE_ADD 和 DATE_SUB 进行日期加减,DATEDIFF 计算日期差,YEAR、MONTH、DAY 提取日期的年、月、日,STR_TO_DATE 将字符串转换为日期格式。这些函数在处理时间相关的数据时很常用。
  4. 聚合函数:这是在分组查询中经常用到的,比如 COUNT 统计行数,SUM 计算总和,AVG 计算平均值,MAX 和 MIN 获取最大值和最小值。
  5. 条件函数:像 IF 函数用于简单的条件判断,IFNULL 用于处理 NULL 值,CASE 语句则可以实现更复杂的条件分支逻辑。

这些函数在日常的 SQL 查询和数据处理中非常实用,能够帮助我们更高效地完成各种数据库操作。

🔥💡Q:你能说一下 MySQL 中 DATETIMETIMESTAMP 这两种数据类型的主要区别吗?

思考过程:

这个问题考察对 MySQL 中日期时间类型 DATETIMETIMESTAMP 的理解,需要从存储方式、存储范围、时区处理以及占用空间等方面进行比较。

  • 存储方式: 它们在磁盘上是如何存储的?
  • 存储范围: 它们能表示的日期时间范围有什么不同?
  • 时区处理: 它们是如何处理时区信息的?是否会自动转换?
  • 占用空间: 它们在存储上会占用多少字节?

回答提问:

好的,DATETIMETIMESTAMP 都是 MySQL 中用来存储日期和时间的数据类型,但它们之间存在一些关键的区别:

首先,存储方式不同。DATETIME 类型以字符串的形式存储具体的日期和时间,格式通常是 YYYY-MM-DD HH:MM:SS,它占用 8 个字节的存储空间。而 TIMESTAMP 类型则以 Unix 时间戳的形式存储,也就是从 1970-01-01 00:00:00 UTC 到现在的秒数,它占用 4 个字节。

其次,它们的存储范围也不一样。DATETIME 的范围比较广,从 '1000-01-01 00:00:00''9999-12-31 23:59:59'。而 TIMESTAMP 的范围相对较窄,从 '1970-01-01 00:00:01' UTC 到 '2038-01-19 03:14:07' UTC。这是因为 Unix 时间戳是一个 32 位的有符号整数,存在溢出的限制。

第三个重要的区别在于时区处理DATETIME 类型存储的是具体的日期和时间,它不会受到时区的影响,你存入的是什么时间,取出来的就是什么时间。而 TIMESTAMP 类型在存储时会自动将当前时区的时间转换为 UTC(协调世界时)进行存储,当查询取出时,又会根据当前连接的时区设置将 UTC 时间转换回本地时区的时间。因此,TIMESTAMP 更适合处理需要考虑时区变化的应用场景。

最后,由于存储方式和范围的不同,它们占用的存储空间也不同,DATETIME 占用 8 字节,而 TIMESTAMP 占用 4 字节。

在选择使用哪种类型时,需要根据具体的业务需求来决定。如果需要存储更广范围的日期时间,并且不需要考虑时区转换,那么 DATETIME 可能更合适。如果需要处理跨时区的数据,并且存储范围在 TIMESTAMP 的限制内,那么 TIMESTAMP 会更方便。

存储引擎(主要考察InnoDB存储引擎的特性以及与别的存储引擎的区别)

🔥⭐Q:能详细描述一下 MySQL 执行一条查询 SQL 语句的完整流程吗?

思考过程:

这个问题考察对 MySQL 整体架构的理解和认识,需要清晰地描述一条查询语句从客户端发送到最终获取结果的各个环节。核心模块包括连接器、查询缓存(在 8.0 版本前)、解析器、优化器、执行器以及存储引擎。需要强调每个模块的作用和数据流向。

回答提问:

好的,当 MySQL 执行一条查询 SQL 语句时,整个过程大致可以分为以下几个步骤:

首先,客户端会通过连接器与 MySQL 服务器建立连接。连接器负责处理 TCP 连接、用户身份验证和权限校验。

连接成功后,MySQL 会先检查查询缓存(在 MySQL 8.0 版本之前)。如果查询的 SQL 语句和查询条件与缓存中的记录完全一致,并且查询结果未过期,则会直接从缓存中返回结果,避免了后续的执行过程。但由于查询缓存的命中率不高,MySQL 8.0 已经移除了这个功能。

如果查询缓存没有命中或者版本在 8.0 之后,SQL 语句会被发送到解析器。解析器会对 SQL 语句进行词法分析和语法分析,识别出语句的类型、表名、字段名等,并构建一个抽象语法树(AST)。

接下来是优化器的阶段。优化器会根据一定的规则和成本模型,对语法树进行优化,生成最优的执行计划。这包括选择合适的索引、决定表的连接顺序等。优化器的目标是找到执行成本最低的方案。

优化完成后,执行器会根据优化器生成的执行计划,调用存储引擎提供的接口来执行实际的数据查询操作。执行器会负责控制数据的读取和返回,它就像一个调度员,指挥存储引擎完成任务。

最后一步是存储引擎的工作。存储引擎负责数据的存储和检索。MySQL 支持多种存储引擎,如 InnoDB、MyISAM 等。执行器会根据指定的存储引擎,调用相应的接口从磁盘读取数据,并将结果返回给执行器,最终由执行器返回给客户端。

🔥🌟Q:MySQL 是如何实现数据排序的呢?

思考过程

这个问题考察 MySQL 中 ORDER BY 子句的实现方式。主要有两种情况:利用索引进行排序和使用文件排序。需要理解这两种方式的原理以及如何判断使用了哪种方式。

回答提问:

好的,MySQL 中实现数据排序主要有两种方式:索引排序文件排序

索引排序是效率最高的方式。如果 ORDER BY 子句中涉及的字段恰好是某个索引的一部分,并且排序方式(升序或降序)与索引的顺序一致,那么 MySQL 可以直接利用索引的有序性,避免额外的排序操作。这种方式下,数据本身就是有序的,可以直接返回。

当无法使用索引排序时,MySQL 会使用文件排序(filesort)。文件排序是指 MySQL 需要先将符合条件的数据从表中读取出来,然后在内存或者磁盘上进行排序。这个过程会消耗额外的 CPU 和 I/O 资源,因此效率相对较低。如果排序的数据量较小,MySQL 会在内存中完成排序;如果数据量较大,内存放不下,则会使用磁盘文件进行排序。

我们可以通过 EXPLAIN 语句来查看 SQL 查询的执行计划。如果在 EXPLAIN 的结果中,extra 列包含了 using filesort,就说明 MySQL 在执行该查询时使用了文件排序,而不是利用索引进行排序。

为了优化排序性能,我们应该尽量创建合适的索引,使得查询能够利用索引的有序性进行排序,从而避免文件排序的发生。

🔥⭐Q:MySQL 中都有哪些常见的存储引擎?你能简单介绍一下吗?

思考过程:

这个问题考察对 MySQL 存储引擎的了解。需要列举常见的存储引擎,并简述它们的主要特点,重点关注 InnoDB、MyISAM 和 Memory。

回答提问: 好的,MySQL 中常见的存储引擎主要有 InnoDBMyISAMMemory

我比较熟悉的是 InnoDB 引擎,它也是 MySQL 的默认存储引擎。InnoDB 支持事务,具有事务的提交、回滚和崩溃恢复功能,这保证了数据的完整性和一致性。此外,InnoDB 还支持行级锁,这使得在并发环境下可以有更好的性能。

MyISAM 引擎是我在学习过程中了解到的,它不支持事务,只支持表级锁。这意味着当一个会话正在写入 MyISAM 表时,其他会话都无法读取或写入该表,并发性能相对较差。但 MyISAM 在某些只读或者读多写少的场景下,可能会有更高的性能,因为它维护了一个表的行数,执行 COUNT(*) 操作会非常快。

Memory 引擎正如其名,它将数据存储在内存中,因此读写速度非常快。但是,Memory 引擎不支持持久化存储,一旦服务器发生故障或者重启,数据就会丢失。它通常用于临时存储对性能要求较高的中间结果或者作为缓存。

当然,MySQL 还支持其他的存储引擎,比如 Archive、CSV、NDB Cluster 等,但 InnoDB、MyISAM 和 Memory 是最常见和最常用的。

🔥🌟Q:MyISAM 和 InnoDB 这两种存储引擎有什么主要的区别呢?

思考过程:

这个问题是面试中经常出现的关于存储引擎的对比。需要从数据存储方式、B+ 树结构、锁粒度以及事务支持等关键方面进行详细阐述。

回答提问:

好的,MyISAM 和 InnoDB 是 MySQL 中最常用的两种存储引擎,它们之间存在显著的区别:

首先,在数据存储方式上,InnoDB 采用的是聚簇索引,这意味着表的数据和索引存储在同一个文件中,索引的叶子节点直接存储了完整的数据行。因此,通过主键进行查询时,效率非常高。而且,二级索引的叶子节点存储的是主键值,通过二级索引查询时,通常需要回表查询主键索引才能获取完整数据。这样做的好处是,如果数据发生修改,二级索引通常不需要维护,除非修改了主键。

MyISAM 采用的是堆表结构,数据和索引是分开存储在不同的文件中的。索引的叶子节点存储的是数据行的物理地址。这意味着每次通过索引查询数据时,都可能需要根据地址回磁盘读取数据,这可能会带来额外的 I/O 开销。当数据发生改变并且物理位置发生变化时,所有索引中的地址都需要更新,这会影响性能。

其次,在 B+ 树结构上,虽然两者都使用 B+ 树作为索引结构,但 InnoDB 的 B+ 树叶子节点存储的是索引和数据(聚簇索引)或者索引和主键(二级索引),而 MyISAM 的 B+ 树叶子节点存储的是索引和数据地址。

锁粒度方面,InnoDB 支持行级锁,这允许更高的并发访问,只有在修改同一行数据时才会发生锁冲突。而 MyISAM 只支持表级锁,当一个会话在操作 MyISAM 表时(无论是读还是写),整个表都会被锁定,并发性能较低。

最后,也是最重要的一点,InnoDB 支持 事务,提供了 ACID(原子性、一致性、隔离性、持久性)特性,适用于对数据一致性要求非常高的场景。而 MyISAM不支持事务,这意味着一系列操作要么全部成功,要么全部失败的保证在 MyISAM 中是不存在的,数据操作的安全性相对较低。

因此,由于 InnoDB 在事务支持、并发性能和数据一致性方面的优势,它被选为 MySQL 的默认存储引擎,更适合于需要处理事务和高并发的在线事务处理(OLTP)应用。而 MyISAM 由于其简单的结构和在某些只读场景下的性能,可能更适合读密集型的应用。

索引结构(超级重要:索引类型、B+树索引的特点、与B树的区别)

🔥⭐Q: MySQL 支持哪些常见的索引类型呢?

思考过程:

这个问题考察对 MySQL 索引类型的基本了解。需要从存储引擎的角度出发,区分不同存储引擎支持的索引类型,并重点介绍 InnoDB 引擎常用的索引类型。

  • 索引的实现依赖存储引擎。
  • 常见索引类型: B+ 树索引(大多数引擎支持)、哈希索引(Memory 引擎)、全文索引。
  • 从不同角度分类索引:按存储结构: B+ 树索引、哈希索引、全文索引。按存储方式(InnoDB): 聚簇索引、非聚簇索引。按字段特性: 主键索引、唯一索引、普通索引、前缀索引。按索引列数: 单列索引、联合索引。
  • 强调 B+ 树索引的常用性及其特性。
  • 简要提及 MySQL 选择树结构作为索引的原因。

回答提问:

好的,我了解到 MySQL 支持多种索引类型,这主要取决于存储引擎的实现。不同的存储引擎支持的索引类型可能会有所不同。

存储结构上来看,主要的索引类型有:

  • B+ 树索引:这是 MySQL 中最常见和最广泛使用的索引类型,尤其是在 InnoDB 和 MyISAM 存储引擎中。它是一种平衡树结构,非常适合范围查询、排序查询和等值查询。
  • 哈希索引:这种索引类型主要由 Memory 存储引擎实现。它的特点是查找速度非常快,时间复杂度可以达到 O(1),但它不支持范围查询、排序等操作。
  • 全文索引:全文索引用于在文本类型的数据中进行关键词搜索,可以支持模糊匹配等功能。它主要用于解决 LIKE '%keyword%' 这类查询效率低下的问题。

此外,我们还可以从其他角度对索引进行分类:

  • 按存储方式(针对 InnoDB 引擎):可以分为聚簇索引和非聚簇索引。
  • 按字段特性:可以分为主键索引、唯一索引、普通索引和前缀索引。
  • 按索引列数:可以分为单列索引和联合索引。

我个人比较常用的是 B+ 树索引,因为它是 InnoDB 引擎默认使用的索引类型,而且功能非常全面,支持各种常见的查询场景。

顺便提一下,MySQL 选择使用树结构(特别是 B+ 树)作为索引,主要是因为树结构能够很好地支持范围查询,并且在磁盘 I/O 方面做了优化,更适合数据库这种需要频繁进行磁盘操作的场景。像哈希索引虽然查找快,但不支持范围查询,而跳表在磁盘场景下的适应性不如树结构。

🔥⭐Q: InnoDB 存储引擎的索引底层是基于什么数据结构实现的呢?

思考过程:

这个问题直接考察对 InnoDB 索引底层数据结构的理解。需要明确 InnoDB 主要使用 B+ 树索引,并可以简单提及全文索引。

  • InnoDB 主要使用 B+ 树索引。
  • 可以提及 FULLTEXT 索引(如果了解)。
  • 重点介绍 B+ 树的特性,为后续问题做铺垫。

回答提问:

好的,我了解到 InnoDB 存储引擎主要采用 B+ 树作为其索引的数据结构

B+ 树是一种自平衡的多路查找树。对于一个度数为 m 的 B+ 树,它的每个节点最多可以有 m 个子节点,并且所有的数据都存储在叶子节点上。叶子节点之间通常会通过指针连接起来,形成一个有序链表(在 InnoDB 中,这个链表是双向的)。

B+ 树的一些关键特性使得它非常适合作为数据库索引的底层结构,比如它的平衡性保证了查询效率的稳定性,数据只存储在叶子节点的特性以及叶子节点之间的链表结构都为范围查询提供了便利。

当然,InnoDB 引擎也支持 FULLTEXT 索引,用于全文搜索,但其底层实现可能与 B+ 树有所不同。在日常使用中,我们谈到 InnoDB 的索引,通常指的都是 B+ 树索引。

🔥⭐Q: 能详细说说 B+ 树这种数据结构都有哪些特点吗?

思考过程:

这个问题深入考察对 B+ 树特性的理解。需要从中间节点是否存储数据、叶子节点是否存储数据、叶子节点间的连接方式以及查询性能等方面进行阐述。

  • 中间节点不存储数据,只存储索引。
  • 叶子节点存储索引和数据。
  • 叶子节点之间通过双向链表连接。
  • 查询性能稳定且高效,I/O 次数少。

回答提问:

好的,B+ 树作为一种常用的索引结构,它有几个非常重要的特性:

  • 首先,B+ 树的中间节点(也就是非叶子节点)不会存储实际的数据,它们只存储索引信息(也就是键值)以及指向子节点的指针。这样做的好处是,在相同的磁盘页大小下,中间节点可以存储更多的索引,从而使得 B+ 树更加“矮胖”,降低了树的高度。更低的树高度意味着查询时需要访问的磁盘 I/O 次数更少,提高了查询效率。
  • 其次,B+ 树的所有实际数据都存储在叶子节点上。每个叶子节点包含了索引的键值以及对应的数据记录。
  • 第三个重要的特性是,B+ 树的所有叶子节点之间会通过双向指针(在 InnoDB 中)串联在一起,形成一个双向链表。这个特性对于进行范围查询(例如查找某个区间内的数据)非常高效。我们只需要找到范围的起始叶子节点,然后沿着链表顺序遍历即可,不需要再回到上层节点进行搜索。同时,这个链表也方便进行全表扫描。
  • 最后,B+ 树的查询性能非常稳定。因为所有的数据都存储在叶子节点,所以每次查询都需要从根节点遍历到叶子节点,查询路径的长度是相同的,这确保了所有数据项的检索都具有相同的 I/O 延迟。而且,由于 B+ 树通常能够保持很低的树高度(例如,3-4 层的 B+ 树可以存储千万级别的数据),因此查询效率非常高。

🔥⭐Q: B+ 树和 B 树这两种数据结构有什么主要的区别呢?

思考过程:

这个问题考察对 B+ 树和 B 树之间差异的理解,需要从数据存储、范围查询和查询效率三个方面进行对比说明。

  • 数据存储: B 树节点存储数据和索引,B+ 树只有叶子节点存储数据。
  • 范围查询: B+ 树通过叶子节点的链表实现高效范围查询,B 树需要中序遍历。
  • 查询效率: B 树可能在非叶子节点命中,平均更快,但 B+ 树查询路径更稳定。

回答提问:

好的,B+ 树和 B 树都是常用的多路平衡查找树,它们之间主要的区别可以从以下几个方面来说明:

  • 数据存储的区别:B 树的所有节点(包括中间节点和叶子节点)都会存储索引和实际的数据,而 B+ 树只有叶子节点才会存储实际的数据,中间节点只存储索引信息和指向子节点的指针。这意味着在存储相同数据量的情况下,B+ 树的中间节点可以存储更多的索引,因此 B+ 树通常会比 B 树更加“矮胖”,树的高度更低,从而减少了查询叶子节点所需的磁盘 I/O 次数。
  • 范围查询的区别:B+ 树的所有叶子节点之间会通过双向指针串联在一起,形成一个有序链表。这种结构使得 B+ 树在进行范围查询时非常高效。我们只需要找到范围的起始点,然后沿着链表顺序遍历即可。而 B 树并没有将叶子节点用链表连接起来,进行范围查询时可能需要通过中序遍历,这可能会涉及更多节点的磁盘 I/O 操作,因此在范围查询方面,B+ 树通常比 B 树更高效。
  • 查询效率的区别:在 B 树中,如果我们要查找的值恰好在某个非叶子节点上,那么在查找到该节点后就可以直接结束查询,B 树的优点在于其查找速度可能更快,尤其是在要查找的数据位于靠近根节点的非叶子节点时。然而,B+ 树由于数据只存储在叶子节点,所以每次查询都必须从根节点搜索到叶子节点,查询路径的长度是固定的。从平均时间代价来看,B 树可能在某些情况下会比 B+ 树稍快一些,但是 B+ 树的查询性能更加稳定,因为每一次成功的查询都需要访问到叶子节点,具有相同的 I/O 延迟。

总的来说,B+ 树在范围查询和查询性能稳定性方面通常优于 B 树,这也是为什么像 MySQL 这样的数据库系统选择 B+ 树作为其主要的索引结构的原因之一。

🔥⭐Q: 聚簇索引和非聚簇索引之间有什么主要的区别呢?

思考过程:

这个问题考察对 聚簇索引和非聚簇索引概念的理解。需要从叶子节点存储的内容、每个表索引的数量以及是否需要回表等方面进行区分。

  • 聚簇索引: 叶子节点存储完整行数据,主键索引是聚簇索引(InnoDB)。
  • 非聚簇索引: 叶子节点存储索引值和主键 ID,二级索引是非聚簇索引。
  • 每个表只能有一个聚簇索引。
  • 非聚簇索引可能需要回表查询。
  • 聚簇索引查询速度通常更快。
  • 提及没有主键时 InnoDB 的处理方式。

回答提问:

好的,聚簇索引和非聚簇索引是 MySQL 中两种主要的索引类型,它们最主要的区别在于 B+ 树的叶子节点所存储的内容不同

  • 聚簇索引:在 InnoDB 存储引擎中,主键索引就是聚簇索引。聚簇索引的 B+ 树的叶子节点存储的是完整的行数据,包含了该行所有列的值。由于数据和索引是存储在一起的,所以通过聚簇索引查找数据时,可以直接获取到整行数据,不需要进行额外的查找。一个表只能有一个聚簇索引,通常是表的主键。
  • 非聚簇索引(也叫二级索引或辅助索引):非聚簇索引的 B+ 树的叶子节点存储的不是完整的行数据,而是索引列的值以及对应行的主键 ID。如果我们需要查询的数据列不是索引列本身,也不是主键,那么在使用非聚簇索引查询到主键 ID 后,通常还需要回到聚簇索引中再进行一次查找,才能获取到完整的行数据,这个过程被称为“回表”。一个表可以有多个非聚簇索引。

因此,从查询效率上来说,通常情况下,通过聚簇索引查询数据的速度会更快,因为只需要扫描一次 B+ 树就可以获取到所需的数据。而通过非聚簇索引查询时,可能需要扫描两次 B+ 树(一次非聚簇索引,一次聚簇索引,即回表)。

另外,需要补充一点的是,如果一个表没有显式地定义主键,InnoDB 会默认选择一个唯一的非空索引作为聚簇索引。如果也没有这样的索引,InnoDB 会在内部生成一个隐藏的主键 (db_row_id) 来作为聚簇索引。这个隐藏的主键对用户是不可见的。

🔥⭐Q: 你觉得为什么 MySQL 会选择使用 B+ 树作为其索引结构呢?

思考过程:

这个问题是考察选择 B+ 树作为索引结构的深层原因。需要从 B+ 树的特性出发,对比其他可能的数据结构(如平衡树、红黑树、跳表、B 树),并结合数据库的应用场景进行解释。

  • B+ 树是多路平衡查找树,适合磁盘 I/O。
  • 多路结构降低树高,减少磁盘 I/O。
  • 平衡性保证查询、插入、删除的效率稳定。
  • 特别适合范围查询(叶子节点链表)。
  • 对比其他数据结构:二叉搜索树(平衡树、红黑树):树高过高,I/O 次数多。跳表:不适合磁盘场景,平衡性可能较差。B 树:范围查询不如 B+ 树,查询性能不如 B+ 树稳定。

回答提问:

好的,MySQL 选择使用 B+ 树作为其索引结构,我认为主要有以下几个重要的原因:

  • 首先,B+ 树是一种多路平衡查找树,这种结构非常适合磁盘 I/O 操作。数据库的数据通常存储在磁盘上,而每次磁盘 I/O 的成本是比较高的。B+ 树的非叶子节点只存储索引键值和子节点指针,而不存储实际的数据,这使得每个节点能够存储更多的索引,从而显著降低了树的高度。例如,一个 3-4 层的 B+ 树就可以存储千万级别的数据,这意味着查询一条数据只需要 3-4 次磁盘 I/O 操作,大大提高了查找效率。相比之下,像二叉搜索树(包括平衡树和红黑树),在存储大量数据时树的高度会很高,导致更多的磁盘 I/O。
  • 其次,B+ 树通过自平衡的机制(节点的分裂和合并)来保持树的平衡,这确保了查询路径的长度是相对稳定的,从而保证了查询、插入和删除操作的时间复杂度都是 O(log n),具有较好的性能稳定性。对于频繁进行增删改操作的数据库来说,这一点非常重要。
  • 第三,B+ 树特别适合范围查询。这是因为 B+ 树的所有叶子节点都通过链表连接在一起,形成一个有序链表。在进行范围查询时,我们只需要找到范围的起始叶子节点,然后沿着链表顺序扫描即可,非常高效。而像 B 树在进行范围查询时可能需要进行多次中序遍历。
  • 此外,对比其他数据结构,例如哈希表虽然等值查询很快,但是不支持范围查询;跳表在内存中表现良好,但在磁盘场景下不如 B+ 树;B 树虽然所有节点都存储数据,可能在某些情况下等值查询更快,但其范围查询性能不如 B+ 树,且查询性能不如 B+ 树稳定。

综上所述,B+ 树在平衡性、查询效率(特别是范围查询)、以及对磁盘 I/O 的优化等方面都非常适合数据库索引的需求,因此成为了 MySQL 等主流数据库系统的首选索引结构。

索引的使用(覆盖索引、回表、索引下推以及该怎么建立索引、查看索引的效果)

🔥⭐Q: 什么是覆盖索引?在什么情况下会用到?

思考过程:

这个问题考察对 覆盖索引的理解及其应用场景。需要说明覆盖索引是指查询所需的所有列都可以在非聚簇索引的叶子节点中找到,从而避免回表。

  • 覆盖索引: 查询所需的列在非聚簇索引的叶子节点中就能找到。
  • 不需要回表查询聚簇索引。
  • 提高查询效率。
  • 通过 EXPLAIN 命令查看 extra 列是否包含 "using index"。
  • 举例说明联合索引的覆盖情况。

回答提问:

好的,覆盖索引是指在一个查询中,我们需要查询的字段(包括 SELECT 列表中的列、WHERE 子句中的条件列等)都可以在所使用的二级索引的叶子节点中找到,而不需要再回到聚簇索引中去查找完整的行记录

简单来说,就是我们要查询的数据,索引已经“覆盖”了,不需要再进行回表操作。

使用覆盖索引的主要目的是为了提高查询效率,因为避免了回表操作,减少了一次磁盘 I/O。只需要扫描一次索引 B+ 树就可以获取到所有需要的数据。

我们可以通过 EXPLAIN 命令来查看查询是否使用了覆盖索引。如果 EXPLAIN 结果的 extra 列显示了 “using index”,则表示该查询使用了覆盖索引。

🔥⭐Q: 回表了解吗?在什么情况下会发生回表查询呢?

思考过程:

这个问题考察对 回表查询的理解。需要说明当使用二级索引查询的列不在该索引的叶子节点中时,需要通过主键回到聚簇索引查找完整数据的情况。

  • 回表查询: 使用二级索引查询,但需要的列不在二级索引的叶子节点中。
  • 二级索引叶子节点存储索引值和主键 ID。
  • 需要通过主键 ID 回到聚簇索引查找完整行数据。
  • 导致需要扫描两次 B+ 树。
  • 举例说明。

回答提问:

好的,当我们在使用二级索引(非聚簇索引)进行查询的时候,如果我们要查询的数据列,并不能在二级索引的叶子节点中全部找到,那么就会发生回表查询

具体来说,二级索引的 B+ 树的叶子节点存储的是索引列的值以及对应数据行的主键 ID。如果我们通过二级索引查询的字段除了索引列本身和主键 ID 之外,还包含了其他的列,那么在二级索引中找到对应的主键 ID 后,还需要再通过这个主键 ID 到聚簇索引(主键索引)中去查找完整的行记录,才能获取到我们需要的全部数据。这个通过二级索引找到主键 ID,然后再回到聚簇索引中查找数据的过程就叫做“回表”。

回表查询会增加额外的 I/O 开销,因此在设计索引和编写 SQL 查询时,我们应该尽量避免不必要的回表,例如可以通过使用覆盖索引来减少回表的发生。

🔥⭐ Q:建立索引要注意什么呢?

思考过程: 这个问题考察索引设计的原则。需要从查询的频率、条件、排序、分组以及字段的特性(如唯一性、区分度)等方面考虑哪些字段适合建立索引,以及哪些情况不适合建立索引。

回答提问: 好的,在选择字段建立索引时,我们通常会考虑以下几个方面:

首先,经常出现在 WHERE 子句中的字段是建立索引的首要考虑对象。因为索引的主要目的是为了加快数据检索的速度,如果一个字段经常被用来作为查询条件,那么在该字段上建立索引可以显著提高查询效率。如果查询条件涉及到多个字段,可以考虑建立联合索引

其次,用于 ORDER BYGROUP BY 子句的字段也适合建立索引。如果查询结果需要排序或者分组,并且排序或分组的字段上存在索引,MySQL 可以直接利用索引的有序性,避免额外的排序操作,提高查询性能。

第三,具有唯一性限制的字段非常适合建立索引,比如商品编码、订单号等。唯一索引不仅可以提高查询速度,还可以保证数据的唯一性。即使不是强制唯一,区分度高(即字段中不同值的比例较高)的字段也适合建立索引,因为区分度越高,索引的选择性就越好,查询效率也越高。

然而,也存在一些不适合建立索引的情况:

  • 在 WHERE、GROUP BY 或 ORDER BY 子句中很少使用的字段,建立索引不会带来明显的性能提升,反而会增加存储空间和维护成本。
  • 字段中存在大量重复数据,区分度很低的字段,比如性别字段,只有男女两种值。在这种情况下,索引的选择性很差,MySQL 优化器可能会选择全表扫描而不是使用索引。
  • 频繁更新的字段不适合建立过多的索引。因为每次更新操作,不仅需要更新表中的数据,还需要更新相关的索引,索引越多,维护成本就越高,可能会降低更新操作的性能。

总的来说,选择哪些字段建立索引需要根据具体的业务场景和查询模式进行分析和权衡。可以通过分析慢查询日志等方式来找出需要优化的查询,并根据查询中涉及的字段来选择合适的索引。

🔥⭐Q: 能给我解释一下什么是 MySQL 的最左匹配原则吗?

思考过程:

这个问题考察对 联合索引中最重要的概念之一——最左匹配原则的理解。需要结合联合索引的结构和查询条件进行解释。

  • 最左匹配原则适用于联合索引。
  • 联合索引按照创建时的顺序排序(先按第一列,再按第二列,以此类推)。
  • 查询时需要从联合索引的最左边的列开始匹配。
  • 如果跳过中间的列,后面的列无法使用索引。
  • 范围查询后的列无法使用索引。

回答提问:

好的,MySQL 的最左匹配原则是针对联合索引来说的。当我们创建一个包含多个列的联合索引时,例如 (a, b, c),MySQL 在构建这个索引时,会先按照最左边的列 a 进行排序,然后在 a 值相同的情况下再按照列 b 进行排序,最后在 b 值也相同的情况下再按照列 c 进行排序。

因此,在使用这个联合索引进行查询时,查询条件必须从索引的最左侧开始匹配。具体来说有以下几个规则:

  1. 查询条件中必须包含联合索引最左边的列(或者最左边的几个列),这样才能利用到这个联合索引。例如,对于索引 (a, b, c),我们可以使用 WHERE a = 1,WHERE a = 1 AND b = 2,或者 WHERE a = 1 AND b = 2 AND c = 3 这样的查询条件来利用索引。
  2. 如果查询条件中没有使用到联合索引最左边的列 a,那么即使查询条件中包含了 b 或 c,这个联合索引也是无法被使用的。例如,WHERE b = 2 或者 WHERE c = 3 都不会使用到 (a, b, c) 这个索引。
  3. 如果查询条件中跳过了联合索引中间的列,那么只有跳过之前的列可以使用到索引,跳过之后的列无法使用索引。例如,对于索引 (a, b, c),如果查询条件是 WHERE a = 1 AND c = 3,那么只有列 a 能使用到索引,列 c 是无法使用索引的。
  4. 当查询条件中使用了范围查询时,例如 WHERE a > 1 AND b = 2 AND c = 3,那么只有范围查询的列 a 可以使用到索引,而其后面的列 b 和 c 通常无法再使用到索引进行精确匹配。

总而言之,最左匹配原则要求我们在使用联合索引进行查询时,需要从索引的最左边开始,连续地、不跳过地使用索引中的列,这样才能充分发挥联合索引的性能优势。

🔥🌟Q:了解索引下推吗?什么情况下会下推到引擎去处理?

思考过程:

这个问题考察对 MySQL 索引下推(Index Condition Pushdown,ICP)优化技术的理解。需要解释 ICP 的目的、原理以及它适用的场景。

回答提问:

是的面试官,我了解索引下推(Index Condition Pushdown,简称 ICP)这种优化技术。

索引下推是 MySQL 5.6 版本引入的一种优化,其主要目的是减少二级索引在查询时的回表操作,从而提高查询效率。简单来说,它就是将部分原本需要在 Server 层进行的条件过滤操作,下推到存储引擎层进行处理。

当我们需要通过二级索引查询数据时,通常的流程是存储引擎根据二级索引找到满足条件的记录的主键,然后 Server 层再根据这些主键进行回表操作,到聚簇索引中获取完整的行数据,最后在 Server 层根据 WHERE 子句中的其他条件进行过滤。

而使用索引下推后,如果 WHERE 子句中的某些条件可以仅通过二级索引中的字段进行判断,那么存储引擎在找到对应的二级索引记录后,会先根据这些下推的条件进行过滤,只有满足所有下推条件的二级索引记录才会进行回表操作。这样就大大减少了回表的次数,提高了查询效率。

索引下推通常在以下情况下会下推到存储引擎去处理:

  • 查询是在二级索引上进行的。
  • WHERE 子句中的过滤条件涉及了二级索引中包含的列,但这些条件不能用于通过索引直接定位记录(例如范围查询后或者联合索引中不满足最左前缀原则的后续字段)。
  • 过滤条件能够被存储引擎评估。

举个例子,假设我们有一个联合索引 (age, score),查询语句是 SELECT * FROM t_user WHERE age > 20 AND score = 100000。虽然 score 字段不能直接用于索引查找(因为 age 是范围查询),但在没有索引下推的情况下,存储引擎会找到所有 age > 20 的记录的主键,然后回表,Server 层再判断 score 是否等于 100000。而有了索引下推,存储引擎在扫描 age > 20 的二级索引记录时,会先判断该记录的 score 值是否等于 100000,只有满足条件的记录才会进行回表,从而减少了不必要的回表操作。

我们可以在 EXPLAIN 语句的 Extra 列中看到 Using index condition,这表示该查询使用了索引下推优化。

🔥🌟Q: 假设一张表有两千万的数据,你估计一下这个表的 B+ 树索引的高度大概是多少?你是怎么计算出来的呢?

思考过程:

这个问题考察对 B+ 树高度的估算能力,需要结合 B+ 树的特性(节点存储的索引数量、叶子节点存储的数据量)以及 InnoDB 的页大小进行估算。

  • B+ 树高度影响磁盘 I/O 次数。
  • 估算需要考虑非叶子节点能存储的索引数量和叶子节点能存储的数据行数。
  • InnoDB 页大小通常是 16KB。
  • 假设主键大小和每行数据大小。
  • 逐步计算每一层能存储的数据量。
  • 通常情况下,千万级别的数据 B+ 树高度在 3-4 层左右。

回答提问:

好的,对于一张有两千万数据的表,其 B+ 树索引的高度大概在 3 到 4 层左右,具体取决于数据库表的字段数量、字段类型以及每行数据的大小。下面我来说一下我的估算方法:

B+ 树的高度直接影响到查询时需要进行的磁盘 I/O 次数。为了尽量减少 I/O,B+ 树的设计目标是保持较低的高度。

我们可以进行一个简单的估算:

假设 InnoDB 的每个数据页大小是 16KB。

对于非叶子节点,它们主要存储的是索引键值和指向子节点的指针。假设主键是 BIGINT 类型,占用 8 个字节,每个指针占用 10 个字节,那么每个非叶子节点大约可以存储 16KB / (8 + 6) ≈ 1170 个索引条目(指向 1171 个子节点)。

对于叶子节点,它们存储的是实际的数据行。假设每行数据的大小是 1KB,那么每个叶子节点大约可以存储 16KB / 1KB = 16 条数据行。

现在我们来估算高度:

  • 高度为 1:只能存储一个页的数据,最多 16 条记录。
  • 高度为 2:根节点可以指向大约 1171 个子节点(叶子节点),每个叶子节点可以存储 16 条记录,所以总共可以存储 1171 * 16 ≈ 18736 条记录。
  • 高度为 3:根节点可以指向大约 1171 个第二层节点,每个第二层节点又可以指向大约 1171 个叶子节点,每个叶子节点存储 16 条记录,所以总共可以存储 1171 * 1171 * 16 ≈ 21939856 条记录,大约 2200 万条。

因此,对于一个有两千万数据的表,如果每行数据大小约为 1KB,那么其 B+ 树索引的高度大概是 3 层。如果每行数据更大,比如 5KB,那么每个叶子节点只能存储 3 条左右的数据,这时高度可能会是 4 层

总的来说,即使数据量达到千万级别,B+ 树的高度通常也能维持在 3-4 层左右,这保证了查询效率。MySQL 为了提高性能,也会将索引加载到内存中,在 innodb_buffer_pool_size 足够的情况下,大部分甚至全部索引都可以缓存在内存中,进一步减少了磁盘 I/O。

🔥🌟 Q: 在什么情况下,我们反而不应该创建索引呢?

思考过程:

这个问题考察对 不适合创建索引场景的理解。需要从表的读写比例、列的重复度以及表数据量等方面进行考虑。

  • 频繁进行增删改操作的表(写多读少)。
  • 列中的数据重复度很高。
  • 表的数据量很小。

回答提问:

好的,虽然索引在很多情况下都能提升查询效率,但在某些特定场景下,不创建索引反而可能是更好的选择:

首先,如果一个表经常进行插入、删除和更新操作,而查询操作相对较少,也就是我们常说的“写多读少”的场景,那么不创建索引可能会更好。因为在写入数据时,需要额外维护索引结构,这会增加写入的开销。如果索引带来的查询性能提升远小于维护索引的成本,那么就不适合创建索引。

其次,如果表中的某个列包含大量重复的值,例如性别字段(男/女),那么在这个列上创建索引的意义可能不大。因为即使使用了索引,数据库也可能需要扫描大量的行才能找到符合条件的记录,优化器可能会选择全表扫描而不是使用索引。在这种情况下,创建索引不仅不会带来明显的性能提升,还会占用额外的存储空间,并影响写入性能。

最后,如果表的数据量非常小,例如只有几百或者几千行数据,那么全表扫描的成本可能已经很低了,使用索引可能不会带来明显的性能提升。反而,维护索引会带来额外的开销。

因此,是否创建索引需要根据具体的业务场景和数据特点进行权衡。我们需要考虑查询的需求、表的读写比例、列的数据分布以及表的数据量等因素,来决定是否应该创建索引。

🔥🌟Q: 你知道哪些常见的索引优化方法呢?

思考过程:

这个问题考察对 索引优化策略的了解。需要从覆盖索引、主键设计、避免索引失效、前缀索引等方面进行阐述。

  • 覆盖索

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

在准备暑期实习时,从等各个平台搜集了上千篇真实面经,自己整理得到了面试题,根据题目在面试题中出现的频率以及我自己、交流群、好朋友面试被问到的频率进行了分类整理,所有内容经过科学分类与巧妙标注,针对性强: 得到⭐🌟💡三种级别的,其中⭐为最高频的题目(类似神哥总结的高频八股),只是我自己整理出来的这部分更多一些,🌟为中高频题目(冲击大厂的uu们建议看)、💡为低频题,可以作为补充,时间充裕再看!

全部评论

相关推荐

04-27 16:50
已编辑
门头沟学院 Java
1. mysql删除表语句是什么?drop和truncate区别?2. 删除一行数据的sql3. 查询一张表所有的数据的sql4. 查询一张表有多少条数据sql。  count(1)和count(*)区别5. i++和++i区别6. 介绍下重载和重写。  构造方法可以重写重载吗?7. arraylist和linkedlist的区别8.解释一下深拷贝和浅拷贝9. 如果有三个线程,我如何能保证顺序执行.方法1: 使用join()join() 方法可以确保一个线程在另一个线程执行完毕后才开始执行。通过合理地调用 join(),可以实现线程的顺序执行。方法2:使用countdownlatch/semaphore方法3: 使用锁和原子类,wait() + notifyAll()方法4: 使用completablefuture10. 如果有一个 T1 线程,想把 T2 线程加到 T1 里面,怎么实现?方法1. 使用join()Thread t2 = new Thread(() -> {    // T2 线程的执行逻辑    System.out.println("T2 线程正在执行");});Thread t1 = new Thread(() -> {    try {        System.out.println("T1 线程开始执行");                // T1 线程在此处等待 T2 线程执行完成        t2.start();  // 启动 T2 线程        t2.join();    // T1 等待 T2 完成                System.out.println("T2 线程已完成,T1 继续执行");    } catch (InterruptedException e) {        e.printStackTrace();    }});t1.start();方法2. 使用future.get()ExecutorService executor = Executors.newSingleThreadExecutor();Future future = executor.submit(() -> {    // T2 线程的执行逻辑    System.out.println("T2 线程正在执行");});Thread t1 = new Thread(() -> {    try {        System.out.println("T1 线程开始执行");                // T1 线程在此处等待 T2 线程执行完成        future.get();  // 阻塞直到 T2 完成                System.out.println("T2 线程已完成,T1 继续执行");    } catch (InterruptedException | ExecutionException e) {        e.printStackTrace();    } finally {        executor.shutdown();    }});t1.start();方法3. 使用countdownlatchCountDownLatch latch = new CountDownLatch(1);Thread t2 = new Thread(() -> {    try {        System.out.println("T2 线程正在执行");    } finally {        latch.countDown();  // 完成时减少计数    }});Thread t1 = new Thread(() -> {    try {        System.out.println("T1 线程开始执行");                t2.start();  // 启动 T2 线程        latch.await();  // 等待 T2 完成                System.out.println("T2 线程已完成,T1 继续执行");    } catch (InterruptedException e) {        e.printStackTrace();    }});t1.start();11. a=a+b 和 a+=b的区别a = a + b,如果 a 和 b 的类型不同(如 a 是 int,b 是 double),需要显式强制类型转换,否则会编译报错。即:double a= 1;int b = 2;a = (double) (a+b); a += b,会自动执行隐式类型转换,将结果转换为 a 的类型。13. 数据库的三大范式如答案有错误欢迎指正
查看24道真题和解析
点赞 评论 收藏
分享
评论
5
30
分享

创作者周榜

更多
牛客网
牛客企业服务