
04 MySQL面试八股看这一篇就够了——深度梳理MySQL面试问题
写在前面的话&内容总览
写在前面的话
本文分部分对MySQL的高频考点和面试题进行了深度梳理,综合了面试鸭、八股网站和AI的答案整理而成,针对难以理解的地方,做了深度的梳理和资料整合,基本覆盖MySQL实习/校招面试95%以上的问题。
另外,本人已写 Java后端完整版学习路线(仔细讲解每个技术栈怎么学,学到什么程度可以投实习/面试)+ 各大厂真实面试问题和参考答案 + Redis核心考点/面试真题深度梳理 3篇高质量长文,如有需要欢迎大佬们自取(如果可以的话顺手点个赞👍),后续还会更新RabbitMQ、SSM等其他部分的面试高频八股梳理,欢迎关注。 01 非科班转码拿下大厂Offer,花费一天整理的Java后端完整版学习路线 02 盘点2025遇到的各大厂面试真题总结 03 一文吃透 Redis 核心考点,面试真题深度梳理
下面的面试问题答案参考来源: 面试鸭 **** 豆包 DeepSeek
内容速读导览(借助NoteBookLM生成)
思维导图:

信息图:

核心内容概括:
这份参考资料是针对 MySQL 数据库核心知识与面试高频问题 的全面总结,涵盖了基础数据类型、存储引擎及高级架构设计。内容重点对比了 CHAR 与 VARCHAR、DATETIME 与 TIMESTAMP 等字段类型的应用差异,并深入解析了 InnoDB 与 MyISAM 在事务支持及锁机制上的本质区别。文中详细阐述了 B+ 树索引结构 的优势、MVCC 并发控制 的原理以及 ACID 事务特性 的实现。针对性能优化,资料提供了 EXPLAIN 执行计划分析、慢查询治理及深度分页的具体解决方案。此外,文章还讨论了分库分表、主从复制以及雪花算法等分布式场景下的架构实践。最后,作者通过对 Redo Log、Binlog 和 Undo Log 的协同工作机制进行剖析,揭示了数据库保障数据持久性与崩溃恢复的核心逻辑。
MySQL基础部分
1.整数类型的UNSIGNED属性有什么用?
MySQL中的整数类型可以使用可选的UNSIGNED属性来表示不允许负值的无符号整数。使用UNSIGNED属性可以将正整数的上限提高一倍,因为它不需要存储负数值。
例如,TINYINT UNSIGNED类型的取值范围是0-255,而普通的TINYINT类型的值范围是-128-127。INT UNSIGNED类型的取值范围是0-4,294,967,295,而普通的INT类型的值范围是-2,147,483,648-2,147,483,647。
对于从0开始递增的ID列,使用UNSIGNED属性可以非常适合,因为不允许负值并且可以拥有更大的上限范围,提供了更多的ID值可用
2.CHAR和VARCHAR的区别是什么?
CHAR和VARCHAR是最常用到的字符串类型,两者的主要区别在于:CHAR是定长字符串,VARCHAR是变长字符串。
CHAR在存储时会在右边填充空格以达到指定的长度,检索时会去掉空格;VARCHAR在存储时需要使用1或2个额外字节记录字符串的长度,检索时不需要处理。
CHAR更适合存储长度较短或者长度都差不多的字符串,例如Bcrypt算法、MD5算法加密后的密码、身份证号码。VARCHAR类型适合存储长度不确定或者差异较大的字符串,例如用户昵称、文章标题等。
CHAR(M)和VARCHAR(M)的M都代表能够保存的字符数的最大值,无论是字母、数字还是中文,每个都只占用一个字符。
3.VARCHAR(100)和VARCHAR(10)的区别是什么?
VARCHAR(100)和VARCHAR(10)都是变长类型,表示能存储最多100个字符和10个字符。因此,VARCHAR(100)可以满足更大范围的字符存储需求,有更好的业务拓展性。而VARCHAR(10)存储超过10个字符时,就需要修改表结构才可以。
虽说VARCHAR(100)和VARCHAR(10)能存储的字符范围不同,但二者存储相同的字符串,所占用磁盘的存储空间其实是一样的,这也是很多人容易误解的一点。
不过,VARCHAR(100)会消耗更多的内存。这是因为VARCHAR类型在内存中操作时,通常会分配固定大小的内存块来保存值,即使用字符类型中定义的长度。例如在进行排序的时候,VARCHAR(100)是按照100这个长度来进行的,也就会消耗更多内存
类似问题:INT(1)和INT(10)在MySQL中有什么不同?

4.DECIMAL和FLOAT/DOUBLE的区别是什么?
DECIMAL和FLOAT的区别是:DECIMAL是定点数,FLOAT/DOUBLE是浮点数。DECIMAL可以存储精确的小数值,FLOAT/DOUBLE只能存储近似的小数值。
DECIMAL用于存储具有精度要求的小数,例如与货币相关的数据,可以避免浮点数带来的精度损失。
在Java中,MySQL的DECIMAL类型对应的是Java类java.math.BigDecimal。
5.为什么不推荐使用TEXT和BLOB?

6.DATETIME和TIMESTAMP的区别是什么?
DATETIME类型没有时区信息,TIMESTAMP和时区有关。
TIMESTAMP只需要使用4个字节的存储空间,但是DATETIME需要耗费8个字节的存储空间。但是,这样同样造成了一个问题,Timestamp表示的时间范围更小。
-
DATETIME:'1000-01-01'到'9999-12-31'
-
Timestamp:'1970-01-01'UTC到'2038-01 'UTC
DATETIME类型存储的是字面量的日期和时间值,它本身不包含任何时区信息。当你插入一个DATETIME值时,MySQL存储的就是你提供的那个确切的时间,不会进行任何时区转换。
这样就会有什么问题呢?如果你的应用需要支持多个时区,或者服务器、客户端的时区可能发生变化,那么使用DATETIME时,应用程序需要自行处理时区的转换和解释。如果处理不当(例如,假设所有存储的时间都属于同一个时区,但实际环境变化了),可能会导致时间显示或计算上的混乱。
TIMESTAMP和时区有关。存储时,MySQL会将当前会话时区下的时间值转换成UTC(协调世界时)进行内部存储。当查询TIMESTAMP字段时,MySQL又会将存储的UTC时间转换回当前会话所设置的时区来显示。
这意味着,对于同一条记录的TIMESTAMP字段,在不同的会话时区设置下查询,可能会看到不同的本地时间表示,但它们都对应着同一个绝对时间点(UTC时间)。这对于需要全球化、多时区支持的应用来说非常有用。
7.NULL和 ''的区别是什么?
含义:
NULL代表一个不确定的值,它不等于任何值,包括它自身。因此,SELECT NULL = NULL的结果是NULL,而不是true或false。NULL意味着缺失或未知的信息。虽然NULL不等于任何值,但在某些操作中,数据库系统会将NULL值视为相同的类别进行处理,例如:DISTINCT,GROUP BY,ORDER BY。需要注意的是,这些操作将NULL值视为相同的类别进行处理,并不意味着NULL值之间是相等的。它们只是在特定操作中被特殊处理,以保证结果的正确性和一致性。这种处理方式是为了方便数据操作,而不是改变了NULL的语义。
'' 表示一个空字符串,它是一个已知的值。
存储空间:
NULL的存储空间占用取决于数据库的实现,通常需要一些空间来标记该值为空。
'' 的存储空间占用通常较小,因为它只存储一个空字符串的标志,不需要存储实际的字符。
比较运算:
任何值与NULL进行比较(例如 =, !=, >, <等)的结果都是NULL,表示结果不确定。要判断一个值是否为NULL,必须使用IS NULL或 IS NOT NULL。
'' 可以像其他字符串一样进行比较运算。例如,'' = ''的结果是true。
聚合函数:
大多数聚合函数(例如 SUM, AVG, MIN, MAX)会忽略NULL值。
COUNT(*) 会统计所有行数,包括包含NULL值的行。COUNT(列名)会统计指定列中非NULL值的行数。
空字符串 ''会被聚合函数计算在内。例如,SUM会将其视为0,MIN和MAX 会将其视为一个空字符串。
8.Boolean类型如何表示?
MySQL中没有专门的布尔类型,而是用TINYINT(1)类型来表示布尔值。TINYINT(1)类型可以存储0或1,分别对应false或true
9.手机号存储用INT还是VARCHAR?
存储手机号,强烈推荐使用VARCHAR类型,而不是INT或BIGINT。主要原因如下:
格式兼容性与完整性:
手机号可能包含前导零(如某些地区的固话区号)、国家代码前缀('+'),甚至可能带有分隔符('-'或空格)。INT或BIGINT这种数字类型会自动丢失这些重要的格式信息(比如前导零会被去掉,'+'和'-'无法存储)。
VARCHAR可以原样存储各种格式的号码,无论是国内的11位手机号,还是带有国家代码的国际号码,都能完美兼容。
非算术性:手机号虽然看起来是数字,但我们从不对它进行数学运算(比如求和、平均值)。它本质上是一个标识符,更像是一个字符串。用VARCHAR更符合其数据性质。
查询灵活性:
业务中常常需要根据号段(前缀)进行查询,例如查找所有"138"开头的用户。使用VARCHAR类型配合LIKE'138%'这样的SQL查询既直观又高效。
如果使用数字类型,进行类似的前缀匹配通常需要复杂的函数转换(如CAST或SUBSTRING),或者使用范围查询(如WHERE phone>=13800000000 AND phone<13900000000),这不仅写法繁琐,而且可能无法有效利用索引,导致性能下降。
加密存储的要求(非常关键):
出于数据安全和隐私合规的要求,手机号这类敏感个人信息通常必须加密存储在数据库中。
加密后的数据(密文)是一长串字符串(通常由字母、数字、符号组成,或经过Base64/Hex编码),INT或BIGINT类型根本无法存储这种密文。只有VARCHAR、TEXT或BLOB等类型可以。
关于VARCHAR长度的选择:
如果不加密存储(强烈不推荐!):考虑到国际号码和可能的格式符,VARCHAR(20)到VARCHAR(32)通常是一个比较安全的范围,足以覆盖全球绝大多数手机号格式。VARCHAR(15)可能对某些带国家码和格式符的号码来说不够用。
如果进行加密存储(推荐的标准做法):长度必须根据所选加密算法产生的密文最大长度,以及可能的编码方式(如Base64会使长度增加约1/3)来精确计算和设定。通常会需要更长的VARCHAR长度,例如VARCHAR(128),VARCHAR(256)甚至更长。
10.MYSQL数据库的ACID
原子性(Atomicity):事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
一致性(Consistency):执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;
隔离性(Isolation):并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间是独立的;
持久性(Durability):一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
11.视图
为什么使用视图?
视图一方面可以帮我们使用表的一部分而不是所有的表,另一方面也可以针对不同的用户制定不同的查询视图。比如,针对一个公司的销售人员,我们只想给他看部分数据,而某些特殊的数据,比如采购的价格,则不会提供给他。再比如,人员薪酬是个敏感的字段,那么只给某个级别以上的人员开放,其他人的查询视图中则不提供这个字段。
视图的理解
-
视图是一种虚拟表,本身是不具有数据的,占用很少的内存空间。
-
视图建立在已有表的基础上,视图赖以建立的这些表称为基表。
-
视图的创建和删除只影响视图本身,不影响对应的基表。但是当对视图中的数据进行增加、删除和修改操作时,数据表中的数据会相应地发生变化,反之亦然。
-
向视图提供数据内容的语句为SELECT语句,可以将视图理解为存储起来的SELECT语句。
-
视图是向用户提供基表数据的另一种表现形式。通常情况下,小型项目的数据库可以不使用视图,但是在大型项目中,以及数据表比较复杂的情况下,视图的价值就凸显出来了,它可以帮助我们把经常查询的结果集放到虚拟表中,提升使用效率。理解和使用起来都非常方便。
视图的优点

视图的不足
如果我们在实际数据表的基础上创建了视图,实际数据表的结构变更了,我们就需要及时对相关的视图进行相应的维护。特别是嵌套的视图(就是在视图的基础上创建视图),维护会变得比较复杂,可读性不好,容易变成系统的潜在隐患。因为创建视图的SQL查询可能会对字段重命名,也可能包含复杂的逻辑,这些都会增加维护的成本。
实际项目中,如果视图过多,会导致数据库维护成本的问题。
所以,在创建视图的时候,要结合实际项目需求,综合考虑视图的优点和不足,这样才能正确使用视图,使系统整体达到最优。
12.存储过程
简单来说存储过程就像是封装好的函数,我们把一组经过预先编译的SQL语句封装成存储过程,我们想要使用的时候可以直接调用存储过程名来执行这些SQL语句。
存储过程的优缺点
优点:

缺点:

13.变量、流程控制与游标
变量
在MySQL数据库的存储过程和函数中,可以使用变量来存储查询或计算的中间结果数据,或者输出最终的结果数据。
在 MySQL 数据库中,变量分为系统变量以及用户自定义变量。
系统变量属于服务器层面。启动MySQL服务,生成MySQL服务实例期间,MySQL将为MySQL服务器内存中的系统变量赋值,这些系统变量定义了当前MySQL服务实例的属性、特征。这些系统变量的值要么是编译MySQL时参数的默认值,要么是配置文件中的参数值。
系统变量分为全局系统变量(需要添加global关键字)以及会话系统变量(需要添加session关键字),有时也把全局系统变量简称为全局变量,有时也把会话系统变量称为local变量。如果不写,默认会话级别。静态变量(在MySQL服务实例运行期间它们的值不能使用set动态修改)属于特殊的全局系统变量。
用户变量是用户自己定义的,根据作用范围不同,又分为会话用户变量和局部变量。会话用户变量:作用域和会话变量一样,只对当前连接会话有效。局部变量:只在BEGIN和END语句块中有效。局部变量只能在存储过程和函数中使用。
流程控制
解决复杂问题不可能通过一个SQL语句完成,我们需要执行多个SQL操作。流程控制语句的作用就是控制存储过程中SQL语句的执行顺序,是我们完成复杂操作必不可少的一部分。只要是执行的程序,流程就分为三大类:
-
顺序结构:程序从上往下依次执行
-
分支结构:程序按条件进行选择执行,从两条或多条路径中选择一条执行
-
循环结构:程序满足一定条件下,重复执行一组语句
针对于MySQL的流程控制语句主要有3类。注意:只能用于存储程序。
-
条件判断语句:IF 语句和 CASE 语句
-
循环语句:LOOP、WHILE 和 REPEAT 语句
-
跳转语句:ITERATE 和 LEAVE 语句
游标
虽然我们也可以通过筛选条件WHERE和HAVING,或者是限定返回记录的关键字LIMIT返回一条记录,但是,却无法在结果集中像指针一样,向前定位一条记录、向后定位一条记录,或者是随意定位到某一条记录,并对记录的数据进行处理。
这个时候,就可以用到游标。游标,提供了一种灵活的操作方式,让我们能够对结果集中的每一条记录进行定位,并对指向的记录中的数据进行操作的数据结构。游标让SQL这种面向集合的语言有了面向过程开发的能力。
在SQL中,游标是一种临时的数据库对象,可以指向存储在数据库表中的数据行指针。这里游标充当了指针的作用,我们可以通过操作游标来对数据行进行操作。
MySQL中游标可以在存储过程和函数中使用。
使用流程:声明游标,打开游标,使用游标(从游标中获取数据),关闭游标
14.触发器

触发器的优缺点
优点:
可以保证数据的完整性,举例,比如一张进货表,一张存货表,当我们进货表发生变动,进货表里面的进货数量就会发生变动,我们可以通过触发器规定当进货表有数据变动的时候同步去修改存货表里面的货物数目。
还有审计和日志记录的功能,比如我们修改商品的成本和售价,可能会因为粗心导致金额设置错误,比如售价低于成本,我们就可以通过触发器先进行校验。
日志的话就是我们可以每一次修改数据都触发一次日志记录的操作。
缺点:
调试困难:触发器执行是隐式的,错误可能难以追踪
性能影响:每个DML操作都会触发额外处理,增加开销,复杂触发器可能导致锁争用和事务时间延长;批量操作可能因触发器而变慢
可移植性问题:触发器语法在不同数据库系统中差异较大,迁移到其他数据库平台可能需要重写
事务管理问题:触发器中的失败会导致主操作回滚,可能意外创建长事务,影响系统整体性能
MySQL高级知识(面试题)
1 SQL查询语句的执行顺序是怎么样的?

注:步骤6是对5中的列做去重处理,在这里没有去重所以没有6
2 如何使用MySQL实现一个可重入的锁?


3 执行一条SQL请求的过程是什么?

1.连接阶段
应用程序与MySQL服务器建立连接,MySQL验证用户名、密码和权限
2.查询缓存
查询语句如果命中查询缓存则直接返回,否则继续往下执行,MySQL 8.0已删除该模块。
3.解析SQL
解析器会对SQL进行词法分析(将SQL语句分解为标记(tokens))和语法分析(构建语法树,方便后续模块读取表名、字段、语句类型)
4.执行SQL
(1)预处理器:检查表和列是否存在;将select _的_展开为所有列名;将视图转换为基表查询
(2)查询优化器:基于成本评估不同执行计划,决定使用哪些索引,确定表连接顺序,重写查询以提高效率,生成查询成本最低的执行计划
(3)执行阶段:执行计划被传递给存储引擎,存储引擎(如InnoDB)通过索引或全表扫描定位数据,从磁盘或缓冲池读取数据;构建结果集并发送回客户端;
如果启用了查询缓存,结果还会被缓存;连接可能被关闭或保持为持久连接
MySQL连接池作用
MySQL连接池核心作用是通过复用数据库连接、优化连接资源分配,解决直接频繁创建/关闭连接带来的性能问题,具体作用如下:
1.减少连接建立与关闭的开销
建立MySQL连接需要经过TCP握手、权限验证(用户名/密码校验)、会话初始化等步骤,这些操作耗时且消耗网络和数据库资源。连接池会预先创建一定数量的连接并维护,当应用需要访问数据库时,直接从池内获取已建立的连接;使用完毕后,将连接归还给池(而非关闭)。这避免了频繁创建/关闭连接的重复开销,显著减少单次数据库操作的响应时间。
2.控制并发连接数,保护数据库
MySQL服务器能支持的最大连接数是有限的(由max_connections参数限制)。若不限制连接数,高并发场景下可能出现“连接数爆炸”,导致数据库因资源耗尽(如内存、线程)无法响应新请求,甚至崩溃。连接池可通过配置最大连接数(如maxPoolSize)限制并发连接总量,确保数据库连接数始终在安全范围内,避免过载。
3.资源复用,提高系统利用率
连接是稀缺资源(每个连接对应数据库的一个线程和内存开销)。若每次操作都创建新连接,会导致大量资源被短暂占用后释放,造成浪费。连接池通过复用已有连接,减少了对CPU、内存、网络端口等系统资源的消耗,提高整体资源利用率。
4.统一管理连接,确保可用性
连接池通常内置连接健康检查、超时控制等机制:
-
对长时间未使用的连接(空闲超时)进行回收,避免资源闲置;
-
定期检测连接有效性(如执行ping命令),确保从池内获取的连接是可用的,减少因连接失效导致的业务错误;
-
支持连接超时等待(当池内无可用连接时,请求可等待一定时间再获取),平衡并发压力。
5.提升系统吞吐量与稳定性
在高并发场景(如Web应用)中,连接池通过快速分配连接、减少阻塞,让更多请求能高效执行数据库操作,直接提升系统的吞吐量;同时,通过避免数据库过载和连接泄露,增强系统的稳定性。
4 MySQL的存储引擎

5 InnoDB和MyISAM引擎的区别?
事务支持:InnoDB支持事务,而MyISAM不支持事务,这也是MySQL选择InnoDB作为默认存储引擎的原因;
锁粒度:InnoDB支持行级锁,而MyISAM最小的锁粒度是表级锁,一个增删改操作会锁住整张表,导致其他查询和更新都被阻塞,会影响并发性能;
崩溃恢复:InnoDB可以通过redolog日志实现崩溃恢复,可以在数据库发生异常的时候(如断电),通过日志文件进行恢复,保证了数据的持久性和一致性,而MyISAM是不支持的;
索引结构:InnoDB是聚簇索引,而MyISAM是非聚簇索引;
使用场景:
lnnoDB具体适用场景:
1、事务处理系统:如银行转账、电商订单处理等场景,利用InnoDB的事务处理能力保证数据的一致性和完整性;
2、高并发读写应用:如在线订票系统、社交媒体平台等,利用InnoDB的行级锁机制能有效减少锁冲突,提高并发处理能力;
MylSAM具体适用场景:
1、读密集型应用:新闻网站,博客系统等,这类应用通常以读取数据为主,MylSAM表级锁在并发读操作时不会产生过多锁冲突,读取速度相对较快;
2、数据仓库和数据分析系统:在数仓中,数据通常是批量加载和更新的,且分析过程中主要是进行大量的读操作,MyISAM的全文索引功能在处理文本数据的搜索和分析时具有一定优势,能提高查询效率
3、嵌入式系统和移动应用:由于MylSAM相对简单,占用资源较少,在一些资源受限的嵌入式系统和移动应用中,如果对事务处理和并发控制求不高,MylSAM可以作为一种轻量级的数据存储方案。

相关问题:为什么MyISAM读比InnoDB快?

6 聚簇索引和非聚簇索引的区别?


7 三层B+树能存多少数据?

索引部分
8 索引是什么,有什么好处?

9 索引的分类?






相关问题0:什么是哈希索引?什么是全文索引?



相关问题1:没有创建主键怎么办?

相关问题2:什么是回表?

10 联合索引(底层存储、最左匹配原则、失效判断)



具体联合索引的案例分析:



注:索引下推的字段一定是在联合索引中包含的字段
11、MySQL分布式主键
(1)什么字段适合作为主键

(2)在分布式环境中,为什么不推荐使用自增长主键?
首先,自增长主键依赖于数据库的自增序列,在高并发写入场景下会成为性能瓶颈。所有插入操作都需要获取下一个ID值,这在分布式系统中会导致严重的锁竞争。
其次,在分库分表场景下,每个分片都有自己的自增序列,无法保证全局唯一。数据迁移困难:当需要合并来自不同数据库的数据时,自增ID会产生大量冲突,需要额外处理。
安全问题:自增ID会暴露数据量和增长趋势,存在信息泄露风险。
(3)UUID可以用来在做主键吗?有什么问题?
全局唯一性:理论上重复概率极低
安全性:相比自增ID,不会暴露数据量信息
但是也存在问题:
UUID如果以二进制存储,占用16字节或以字符串形式存储就占36字节,占用的存储空间大,这在大数据量的表中会对存储和索引的性能产生一定影响。
更重要的一点是,数据是由UUID构建的B+树存储的,树的内部储存的数据是已经排好序的,但是新数据的UUID是随机生成的无序的,所以索引可能会定位到一个已经满了的页(默认一个页16KB大小),这种情况下就会发生页分裂,大量随机的插入就整个容易导致整个树索引的频繁裂变,增加了IO开销,从而降低了查询和写入的性能。
(4)雪花算法可以做主键吗?雪花算法原理,优缺点,怎么解决缺点?
可以。
雪花算法64位二进制数(8字节),第一位符号位,始终为0,保证是正整数;前41位是时间戳,精确到毫秒(大约能用69年,因为默认是从1970年开始计数,所以我们一般使用会做一点小调整,用现在时间减去系统上线的时间);中间10位是机器标识,确保分布式环境下生成的主键不重复;后面12位是序列号,用户区分同一毫秒内的多个请求ID(如果达到上限系统会等待下一毫秒),保证了能够应对高并发场景。因此雪花算法是全局唯一的,时间戳的设计保证了生成的ID是按照时间递增的,所以大致有序。
缺点:如果说回拨服务器的系统时钟,可能会导致生成重复ID,因此要保证时钟同步(网络时间校准;加容错机制,比如检测到时钟回拨可以采取等待、回退序列号等策略避免ID冲突;留出备用的时钟序列位,比如把机器码减少到7位,留下3位给序列号容错);另外,在极端高并发的情况下,可能会有序列号溢出的风险(12位,2^11次方,4096个ID,方案等待下一毫秒)。
12 性别字段适合加索引吗?

13 MySQL中索引是怎么实现的?


14 B+树的特性?

15 B+树和B树的区别?

16 为什么选择B+树作为索引结构?
如果要设计一个方便查询数据的数据结构,首先会想到链表,但是链表每次查询都需要遍历整个链表;
所以又想到了二叉树,但是普通的二叉树也有问题,当我们顺序插入时会发现,二叉树退化成了链表,这主要就是因为这棵树并不是平衡的;
所以我们又想到了平衡的红黑树,但是红黑树也有问题,针对大数据量,红黑树会非常深,查询效率依然很低;
这个时候我们就想到了B树,也就是把二叉树最多两个子节点扩展从多个子节点,但是B树还是有一些问题,B树是每个节点既存储键也存储键对应的值,导致单个节点能够容纳的键数量有限,并且叶子节点之间彼此独立,范围查询的时候需要回溯到父节点;
而B+树针对这些问题进一步改进,非叶子节点只用来存储键,数据全部存储在叶子节点,进一步降低了树高,使得存储更紧凑;并且叶子节点用双向链表串联,范围查询只需要遍历链表,而无需从根节点重新查找,另外B+树还对键进行了冗余,在叶子节点也存储键,简化了分裂和合并操作。
简化了分裂和合并操作:
在B树中如果要进行分裂:需要从原节点中选出一个中间键,提升到父节点去,需要计算中间键的位置做调整,但是B+树可以直接复制要分裂的这个叶子节点的键,并且也不需要调整父节点的其他键。
哈希结构虽然等值查询的复杂度O(1),但是哈希不适合做范围查询。
相关问题:为什么MySQL不用跳表结构?

17 如何提升查询效率?
依赖覆盖索引和索引下推技术,减少回表次数
覆盖索引:
通过创建组合索引,需要查询的字段都在这个组合索引里

索引下推:

索引下推的核心思想是:将原本在Server层进行的部分数据过滤操作,“下推”到存储引擎的索引层面来完成。
这样做的最大好处是减少了存储引擎必须回表查询的次数和返回给Server层的数据行数,从而显著提升查询性能,尤其是对于包含模糊查询或范围查询的复合索引场景。
一个具体的例子:

18 如果一个列既是单列索引,又是联合索引,查询会走哪个?

19 MySQL中数据排序是怎么实现的?

双路和单路排序:

超过双路,没超过单路
(双路排序工作原理:
-
第一次读取排序字段和行指针(rowid)
-
根据排序条件对排序字段进行排序
-
第二次根据排序后的行指针回表读取完整记录
单路排序工作原理:
-
一次性读取所有需要的列(包括排序字段和查询字段)
-
在内存中完成排序
-
直接返回结果,无需回表
)
相比双路排序,单路减少了回表的操作,效率更高。

20 怎么决定建立哪些索引?

21 索引失效的场景?怎么排查?



22 常见的索引优化方法有哪些?

事务部分
23 事务的特性是什么?如何实现?

24 MySQL可能出现什么和并发相关的问题?

25 MySQL是怎么解决并发问题的?

26 事务的隔离级别以及实现方式?
事务的隔离级别:mysql默认是可重复读


27 可重复读是如何避免不可重复读的,以及为什么仍然有幻读问题?
解决不可重复读:

幻读问题:


28 什么是MVCC?
MVCC是一种并发控制机制,用于在多个并发事务同时读写数据库时保持数据的一致性和隔离性。它是通过在每个数据行上维护多个版本的数据来实现的。当一个事务要对数据库中的数据进行修改时,MVCC会为该事务创建一个数据快照,而不是直接修改实际的数据行。
1、读操作(SELECT):
当一个事务执行读操作时,它会使用快照读取。快照读取是基于事务开始时数据库中的状态创建的,因此事务不会读取其他事务尚未提交的修改。具体工作情况如下:
-
对于读取操作,事务会查找符合条件的数据行,并选择符合其事务开始时间的数据版本进行读取。
-
如果某个数据行有多个版本,事务会选择不晚于其开始时间的最新版本,确保事务只读取在它开始之前已经存在的数据。
-
事务读取的是快照数据,因此其他并发事务对数据行的修改不会影响当前事务的读取操作。
2、写操作(INSERT、UPDATE、DELETE):
当一个事务执行写操作时,它会生成一个新的数据版本,并将修改后的数据写入数据库。具体工作情况如下:
-
对于写操作,事务会为要修改的数据行创建一个新的版本,并将修改后的数据写入新版本。
-
新版本的数据会带有当前事务的版本号,以便其他事务能够正确读取相应版本的数据。
-
原始版本的数据仍然存在,供其他事务使用快照读取,这保证了其他事务不受当前事务的写操作影响。
3、事务提交和回滚:
-
当一个事务提交时,它所做的修改将成为数据库的最新版本,并且对其他事务可见。
-
当一个事务回滚时,它所做的修改将被撤销,对其他事务不可见。
4、版本的回收:
为了防止数据库中的版本无限增长,MVCC 会定期进行版本的回收。回收机制会删除已经不再需要的旧版本数据,从而释放空间。
MVCC通过创建数据的多个版本和使用快照读取来实现并发控制。读操作使用旧版本数据的快照,写操作创建新版本,并确保原始版本仍然可用。这样,不同的事务可以在一定程度上并发执行,而不会相互干扰,从而提高了数据库的并发性能和数据一致性
(实际并不是真的在数据库存了很多个版本的数据,而是通过undolog日志实现的,undolog日志会记录我们的反向操作,比如update一个记录,就会在日志里留下旧值,所以需要老版本时就顺着日志往回找;readview会记录哪个事务已经提交,哪个事务还在进行,来决定这个事务你能不能看到,自己改的一定能看,比你早提交的可以看,还未提交的不给看)
29 滥用事务,或者一个事务里有特别多sql的弊端(长事务)?

锁部分
30 MySQL锁机制,各种类型的锁详解
MySQL的锁机制大致可以分为三个层次来理解:
1.锁的类型(共享/排他锁、意向锁)
2.锁的粒度(全局锁、表级锁、行级锁)
3.锁的实现(Record Lock, Gap Lock, Next-Key Lock)
1.锁的类型(按操作属性分)


2.锁的粒度(按作用范围分)



3.InnoDB行级锁的算法(实现方式)


4. 隔离级别与锁的关系
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 锁的使用 |
|---|---|---|---|---|
| 读未提交 | ✔ | ✔ | ✔ | 几乎不加锁 |
| 读已提交 | ✘ | ✔ | ✔ | 使用记录锁,但语句执行期间会释放不符合条件的记录的锁。没有间隙锁,所以可能发生幻读。 |
| 可重复读 | ✘ | ✘ | ✘ | 默认使用Next-Key Lock(记录锁+间隙锁),从而防止幻读。 |
| 串行化 | ✘ | ✘ | ✘ | 所有读操作自动转为SELECT ... FOR SHARE,强制加锁,并发度最低。 |
5、总结与实践建议
引擎选择:需要高并发写操作,务必选择InnoDB(支持行锁),避免使用MyISAM(只有表锁)。
索引至关重要:InnoDB的行锁是加在索引上的。UPDATE/DELETE的WHERE条件以及SELECT ... FOR UPDATE的条件必须用好索引,否则会锁表。
控制事务大小:尽快提交事务,让锁尽快释放。不要在事务内执行不必要的耗时操作(如网络调用、文件处理)。
访问冲突:理解FOR UPDATE(X锁,互斥)和FOR SHARE(S锁,共享)的使用场景。
隔离级别:默认的RR级别能满足大部分场景且避免了幻读。如果对一致性要求不是极端苛刻,但希望并发更高(减少间隙锁带来的死锁概率),可以考虑RC级别,但要接受幻读的可能性。
死锁:InnoDB能自动检测死锁并回滚其中一个代价最小的事务。应用程序需要能处理并重试因死锁而失败的事务。
31 数据库的表锁和行锁分别有什么用?

32 关于几种加锁情况的分析

33 一条update是不是原子性的,为什么?

34 MySQL查询语句后面加for update是什么意思,加了什么锁?
SELECT FROM 表名 WHERE 条件 FOR UPDATE;
当这条语句执行时,MySQL会锁住所有满足WHERE条件的行;锁类型是排它锁,只对InnoDB引擎生效;
只有当前事务可以读取这些行,其他事务不能更新、删除或再次加FOR UPDATE查询这些行,直到当前事务提交或回滚。
间隙锁(Gap Lock)
为了防止幻读,InnoDB可能会对间隙也加锁;
特别是当查询条件使用范围查找(如>, <等)时,除了行锁,还会加间隙锁。
比如:SELECT FROM users WHERE age > 30 FOR UPDATE;
35 一致性非锁定读和锁定读
对于一致性非锁定读(Consistent Nonlocking Reads)的实现,通常做法是加一个版本号或者时间戳字段,在更新数据的同时版本号+ 1或者更新时间戳。查询时,将当前可见的版本号与对应记录的版本号进行比对,如果记录的版本小于可见版本,则表示该记录可见
在InnoDB存储引擎中,多版本控制 (multi versioning) 就是对非锁定读的实现。如果读取的行正在执行DELETE或UPDATE操作,这时读取操作不会去等待行上锁的释放。相反地,InnoDB存储引擎会去读取行的一个快照数据,对于这种读取历史数据的方式,我们叫它快照读(snapshot read)
在Repeatable Read和Read Committed两个隔离级别下,如果是执行普通的select 语句(不包括select ... lock in share mode, select ... for update)则会使用一致性非锁定读(MVCC)。并且在Repeatable Read下MVCC实现了可重复读和防止部分幻读
锁定读
如果执行的是下列语句,就是锁定读(Locking Reads)
-
select ... lock in share mode
-
select ... for update
-
insert、update、delete操作
在锁定读下,读取的是数据的最新版本,这种读也被称为当前读(current read)。锁定读会对读取到的记录加锁:
select ... lock in share mode:对记录加S锁,其它事务也可以加S锁,如果加x锁则会被阻塞;
select ... for update、insert、update、delete:对记录加X锁,且其它事务不能加任何锁
日志部分
36 MySQL日志包括哪几种?


37 binlog日志


MySQL主从复制通过主库记录数据变更并将binlog日志传输给从库,从库通过中继(relaylog)日志重放这些变更,从而实现数据同步。
复制过程中从库会启动两个线程:
IO线程:与主库建立连接,持续抓取binlog日志并写入到中继日志中。
SQL线程:从中继日志中读取SQL语句并执行,将主库的变更应用到从库上。


解释:sysdate()和now()方法的不同是,now()返回的是sql语句开始执行时的时间,之后每次查询返回的结果都一样,但是sysdate()方法返回的是当前函数被调用的时间,所以两次调用返回的结果可能是不一致的。
由于SYSDATE()的非确定性行为(即每次调用可能返回不同结果),它在主从复制环境中可能会带来问题。如果主库上一个使用SYSDATE()的语句执行了很长时间,那么它在从库上重放时,SYSDATE()会取从库当前的时间,这可能导致主从数据不一致。
因此,MySQL提供了sysdate-is-now这个服务器系统变量。当这个变量被设置为ON时,SYSDATE()的行为会变得和NOW()一模一样,从而保证复制的安全性。
相关问题:为什么只有binlog日志无法保证数据持久性?
核心原因1:binlog的刷盘策略默认不保证“实时持久化”
数据持久性的关键是“事务提交后,日志必须被强制刷到磁盘”(而非停留在内存/操作系统缓存)。但binlog的刷盘机制存在“延迟风险”,具体取决于sync_binlog参数:

可见:
仅当sync_binlog=1时,binlog才具备“事务级刷盘”能力;但默认值是0,此时binlog的持久化完全依赖操作系统,无法保证;
即使手动设置sync_binlog=1,仍有其他问题(见原因2),无法单独保证数据持久性。
核心原因2:binlog无“崩溃恢复的完整性校验”(依赖两阶段提交)
即使强行将sync_binlog=1(保证binlog刷盘),单独的binlog仍无法保证“数据与日志的一致性”——因为MySQL的事务提交是两阶段提交(2PC),需要binlog与redo log协同才能确保崩溃后的数据完整:
两阶段提交的核心逻辑(InnoDB+binlog):
prepare 阶段:InnoDB将事务的物理修改写入redo log,标记为“prepare状态”,并强制刷盘;
commit 阶段:MySQL Server将事务的逻辑操作写入binlog,并强制刷盘(sync_binlog=1时);
最后,InnoDB将redo log标记为“commit状态”,事务完成。
若只有binlog(无redo log),会出现什么问题?
假设事务执行到“binlog已刷盘,但数据页未刷盘”时宕机:
-
重启后,MySQL只能看到binlog中的“逻辑操作”,但无法确认“该操作是否已应用到数据页”(因为没有redo log记录“数据页是否被修改”);
-
若重新执行binlog中的操作,可能导致“数据重复插入”(破坏一致性);若不执行,又会导致“数据丢失”(破坏持久性)。
本质上,binlog缺乏“崩溃后校验操作是否已落地到数据页”的能力,必须依赖redo log的“prepare/commit状态”来判断——单独的binlog无法解决“日志与数据页的一致性校验”问题,自然无法保证持久性。
38 Undolog日志


39 redolog日志


相关问题1:Redolog日志是怎么提高性能的?

相关问题2:RedoLog怎么保证持久性的?

相关问题3:只用binlog不用redolog可以吗?

40 Binlog两阶段提交过程


41 为什么要写RedoLog,而不是直接写入B+树里?

性能调优部分
42、慢查询如何优化?
当遇到慢SQL时,可以按这个清单排查:
(1)开启慢日志:确认慢日志已开启,并设置合理的阈值(比如2s)。
(2)抓取慢SQL:可以使用MySQL自带的工具mysqldumpslow,可以汇总和排序慢查询日志(比如获取TOP 10)
(3)EXPLAIN:在SQL语句前加EXPLAIN,查看数据库的执行计划。

关于type类型:
System:这是最好的情况,表示表中只有一行数据(相当于系统表)。
出现场景:例如,查询一个只有一条数据的系统表,如: SELECT * FROM (SELECT * FROM t1 WHERE id = 1) AS derived_table;(如果子查询结果只有一行,外层查询的type可能是system)。
Const:性能极佳。表示通过索引一次就能找到唯一的一行记录。它用于比较主键或唯一索引的列与常数值。
出现场景:WHERE条件中使用主键或唯一索引的等值查询,如:SELECT * FROM users WHERE id = 10;(id是主键)。
因为索引保证了唯一性,MySQL最多只返回一行,所以速度非常快。
eq_ref:性能非常好。通常出现在多表连接(JOIN)查询中。对于前一个表的每一行,在当前表中只找到唯一的一行与之匹配。它使用主键或唯一索引进行关联。
出现场景:JOIN查询,其中驱动表(被连接的表)的关联字段是主键或唯一索引,如:SELECT * FROM users u JOIN orders o ON u.id = o.user_id;(假设o.user_id是orders表的主键或唯一索引)。
对于users表的每一行,MySQL在orders表中通过主键直接定位到一条记录,效率极高。
Ref:性能良好。使用非唯一性索引进行查找,或者只使用了唯一性索引的前缀部分。它可能返回多个符合条件的行。
出现场景:使用非唯一索引进行等值查询;使用唯一索引的“最左前缀”进行查询(即没有用到所有列),例如:SELECT * FROM users WHERE age = 30;(age字段上有一个普通索引);SELECT * FROM users WHERE last_name = ‘Smith’;(有一个联合索引(last_name, first_name),但只用了last_name。
Range:使用索引检索给定范围的行。这比全索引扫描要好,因为它只需要处理索引的某个范围。
出现场景:在索引列上使用BETWEEN、>、<、>=、<=、IN()、LIKE ‘prefix%’(前缀匹配)等操作,例如:SELECT * FROM users WHERE id BETWEEN 10 AND 20; SELECT * FROM users WHERE created_at > ‘2023-01-01’;
这是一个可以接受的性能类型,尤其是在需要范围查询时。
Index:全索引扫描,MySQL遍历整个索引树来获取数据。这比全表扫描好,因为索引文件通常比数据文件小。
出现场景:查询的列都包含在某个索引中(即覆盖索引);需要对索引进行排序,而排序顺序与索引顺序一致。
ALL:全表扫描,这是最坏的情况,MySQL会读取整个表中的每一行来找到匹配的行。
出现场景:表没有建立索引;查询条件没有使用到索引;表数据量很小,MySQL优化器认为全表扫描比走索引更快(例如,表只有几行),例如:SELECT * FROM users WHERE name = ‘John’;(name字段上没有索引)。
**这是需要重点优化的信号!**对于大数据量的表,ALL类型会导致严重的性能问题。

(4)索引检查:是否缺少索引?索引是否失效?是否可以用覆盖索引?
(5)SQL写法检查:是否有SELECT *、深分页、函数操作字段等问题?
(6)表结构设计优化:选择合适的数据类型,可以进行适当的冗余字段设计,减少JOIN,以空间换时间;对冷数据进行归档,或者进行大表的分库分表。
(7)引入缓存,比如redis,存储热点数据和频繁查询的结果,降低数据库压力。

43 如果explain用到的索引不正确的话,怎么干预?

44、深度分页问题?
首先什么是深度分页问题:举个例子,比如我们要查询第1万页的10条数据,mysql会扫描10010条数据,然后返回后面10条,这就是深度分页。
针对深度分页查询可以采用:

子查询:
我们先查询出limit第一个参数对应的主键值,再根据这个主键值再去过滤并limit,这样效率会更快一些。
# 通过子查询来获取 id 的起始值,把 limit 100000 的条件转移到子查询
SELECT *
▼SQL复制代码FROM t_order WHERE id >= (**SELECT** id FROM t_order where id > 1000000 **limit** 1) LIMIT 10;
子查询(SELECT id FROM t_order where id > 1000000 limit 1)会利用主键索引快速定位到第1000001条记录,并返回其ID值。
主查询SELECT * FROM t_order WHERE id >= ... LIMIT 10将子查询返回的起始ID作为过滤条件,使用id >=获取从该ID开始的后续10条记录。
不过,子查询的结果会产生一张新表,会影响性能,应该尽量避免大量使用子查询。并且,这种方法只适用于ID是正序的。在复杂分页场景,往往需要通过过滤条件,筛选到符合条件的ID,此时的ID是离散且不连续的。


架构部分
45 数据冷热分离?



46 数据冷热库分库,如果冷库数据突然变热怎么办?




47 分库分表
垂直分库分表
核心思想:“按业务模块拆分”或“按列拆分”。将一张宽表或者一个完整的数据库,拆分成多个不同的、更小的表或数据库。
它主要解决的是:单库/单表因字段过多导致的业务耦合和性能问题。
1. 垂直分表
基于列进行拆分。将一个包含很多字段的大表,根据访问频次和业务关联性,拆分成多个扩展表。
常见做法:
冷热分离:将“常用字段(热数据)”和“不常用字段(冷数据)”分开。
大字段分离:将TEXT, BLOB等占用空间大的字段单独拆到一张扩展表中。
例子:一张t_user表有50个字段,其中id, name, email, phone是查询最频繁的,而personal_info, description等文本字段不常查询。
拆分后:
t_user (核心表): id, name, email, phone, ...
t_user_ext (扩展表): id, user_id, personal_info, description, ...
何时使用:
表字段非常多,但每次查询只涉及其中一部分。
存在超长文本(TEXT/BLOB)字段,影响了核心表的查询和运维效率(如慢SQL、备份慢)。
2.垂直分库
基于业务模块进行拆分。将一个大而全的数据库,拆分成多个小而专的数据库。
例子:一个电商数据库shop_db包含了用户、商品、订单、支付等所有数据。
拆分后:
user_db (用户库): 存放用户、会员等级等相关表。
product_db (商品库): 存放商品、品类、库存等相关表。
order_db (订单库): 存放订单、购物车等相关表。
pay_db (支付库): 存放支付、账户等相关表。
何时使用:
业务系统模块清晰,耦合度低。
应用程序本身已经按照微服务进行了拆分,数据库自然也需要跟随服务进行隔离。
希望不同的业务团队能独立拥有和管理自己的数据库,减少相互影响。
垂直分库/分表的优点:
解耦业务:降低系统的复杂度和耦合度。
提升性能:减少单次I/O的数据量(垂直分表)、减少单库的连接数和处理压力(垂直分库)。
便于维护:不同团队可以专注于自己的数据库,升级、运维更灵活。
垂直分库/分表的缺点:
无法解决单表数据量过大的问题。order_db里的订单表依然会无限增长。
可能带来跨库Join的问题。原本一个Join查询就能搞定的事,现在需要业务代码或中间件做多次查询和结果聚合,复杂度增加。
分布式事务问题开始出现。例如:下单操作需要同时操作order_db和pay_db,需要引入分布式事务方案。
水平分库分表
核心思想:“按数据行拆分”。将同一个表中的数据,按照某种规则(分片键)分散到多个结构相同的数据库或表中。
它主要解决的是:单库单表数据量过大、读写性能瓶颈的问题。
水平分表:将一张表的数据分到同一个数据库的多个表中。例如:order_0,order_1, ...order_n。
水平分库:将一张表的数据分到多个数据库的多个表中。例如:db0.order_0, db1.order_1, ... dbn.order_n。(水平分库是水平分表的进阶,通常一起进行)
分片规则(Sharding Key):
哈希取模:根据某个字段(如user_id)的哈希值对分片数量取模,决定数据落在哪个分片。优点:数据分布均匀。缺点:扩容时需要迁移大量数据(一致性哈希可以缓解,
一致性哈希解释:
1.核心思想:“环形哈希空间” + “就近路由”
一致性哈希将整个哈希值空间映射为一个环形结构(称为“哈希环”),范围通常是0~2³² - 1(32位哈希值的取值范围,足够大且均匀),然后通过两步实现路由:
步骤1:将“分表(存储节点)”映射到哈希环
对每个分表的唯一标识(如表名t_user_0)计算哈希值(如MD5、SHA-1),并将结果映射到哈希环的某个位置,形成“节点位置”。
步骤 2:将“数据” 映射到哈希环并路由
对数据的唯一标识(如user_id)同样计算哈希值,映射到哈希环的“数据位置”;然后顺时针遍历哈希环,找到第一个比“数据位置”大的“节点位置”,该节点对应的分表就是数据的存储目标
2.直观示例:3个分表的路由过程
假设使用简单哈希函数,哈希环范围0~100,分表和数据的映射如下:
分表映射到环__:
Table1哈希值 → 20 → 环上位置20;
Table2哈希值 → 50 → 环上位置50;
Table3哈希值 → 80 → 环上位80;
数据映射与路由__:
数据A哈希值 → 40 → 顺时针找第一个节点:位置50(Table2)存到Table2;
数据B哈希值 → 60 → 顺时针找第一个节点:位置80(Table3)→ 存到Table3;
数据C哈希值 → 90 → 顺时针遍历到环末端后回到起点,找第一个节点:位置 20(Table1)→ 存到Table1。
三、一致性哈希的关键优势:解决扩缩容痛点
传统取模方案的问题是“节点数量变化导致路由规则全变”,而一致性哈希的环形结构能大幅减少迁移量。
示例:新增1个分表(扩容)
原有3个分表,新增Table4,其哈希值→60(环上位置60):
新节点位置__:60(介于table2(50)和table3(80)之间);
需迁移的数据__:仅哈希值在50~60之间的数据现在需迁移到table;
迁移量__:仅占总数据的(60-50)/(100 = 10%,远低于传统取模的90%+。
)。
范围分片:根据某个字段的范围进行分片,如按时间(月/年)、按id区间。优点:易于扩容。缺点:容易产生数据热点(最新的分片读写频繁)。
地理分片:根据用户所在地等地理位置信息分片。
自定义规则:根据业务特点自定义复杂规则。
何时使用:
单表数据量巨大,预计未来会超过千万甚至亿级别,导致索引膨胀,查询性能下降。
数据库的CPU、内存、I/O或连接数即将达到瓶颈,且无法通过升级硬件解决。
需要解决写操作的性能瓶颈(因为读操作还可以通过读写分离缓解)。
水平分库/分表的优点:
根本性解决大数据量存储和性能问题。
大幅提升系统的扩展性和吞吐量。
水平分库/分表的缺点:
复杂度急剧上升:
-
跨库Join:几乎无法执行,需要在业务层模拟。
-
跨库聚合排序:count(), order by, group by变得异常复杂。
-
全局主键ID:不能再用数据库自增ID,需要分布式ID生成方案(雪花算法等)。
-
分片键的选择至关重要,选错可能导致数据倾斜和热点。
-
运维复杂度高:数据迁移、扩容、缩容都非常麻烦。
总结与决策流程图
核心原则:能不分则不分,优先选择简单的优化方案(如优化SQL、索引、读写分离、缓存),只有当这些手段都无法满足时,再考虑分库分表。
如何选择?
| 维度 | 垂直分库/分表 | 水平分库/分表 |
|---|---|---|
| 拆分角度 | 业务/字段维度 | 数据行维度 |
| 目标 | 解耦,专库专用 | 扩容,解决性能瓶颈 |
| 影响 | 表结构不同 | 表结构相同 |
| 适用场景 | 业务清晰,系统耦合度高;字段多且有冷热之分 | 单表数据量巨大,写操作频繁 |
48 MySQL主从复制(主从同步)

相关问题:主从延迟的处理方法
强制走主库:对于大事务,或者资源密集型操作,直接在主库上执行,避免从库的额外延迟。
其他问题:
49 SQL查询,平时正常,但是偶发性的变慢SQL,可能是什么情况
(1)数据量和统计信息的不稳定性(最常见)
这是最可能的原因。MySQL的查询优化器依赖于对表的统计信息(如行数、索引分布、基数等)来决定使用哪个索引和执行计划。
这些统计信息不是实时更新的。当表中数据发生大量增删改(特别是UPDATE和DELETE)后,统计信息会变得过时。优化器基于过时的信息,可能选择了一个非最优的索引(甚至全表扫描),导致查询突然变慢。
为什么是间歇性的:UPDATE和DELETE操作积累到一定程度,或者触发了自动更新统计信息的阈值后,统计信息被更新,新的、可能更差的执行计划被生成,慢SQL就出现了。或者,当表数据量突破某个临界点时,原来的好计划突然就不好了。
之所以又可以自愈:

排查方法:
-
在慢SQL出现时,使用EXPLAIN语句分析当前的执行计划。
-
与正常时的EXPLAIN结果进行对比,看是否使用了不同的索引(key字段)或访问方式(type字段)。
-
检查表的统计信息收集设置:SHOW CREATE TABLE your_table_name;查看STATS_PERSISTENT设置。
(2)缓存失效
MySQL有多层缓存,缓存失效会导致查询需要重新处理。
InnoDB缓冲池(Buffer Pool):这是最重要的缓存,缓存了表数据和索引。如果系统有重启、缓冲池大小设置不合理、或者突然有一个大型查询/报表任务扫走了大量缓存数据,那么你的查询所需的数据就可能被挤出缓存,导致必须从磁盘读取,速度急剧下降。
查询缓存(Query Cache)(注意:MySQL 8.0中已移除):如果启用了QC,一旦表有任何修改,所有基于该表的查询缓存都会失效。后续的查询需要重新执行。如果你的表每隔一段时间才被更新一次,那么更新后的第一次查询就会变慢。
排查方法:
-
监控缓冲池命中率:SHOW STATUS LIKE 'Innodb_buffer_pool_read%’;。理想情况下,Innodb_buffer_pool_read_requests(总请求数)远大于Innodb_buffer_pool_reads (从磁盘读取的次数),命中率应接近99%。
-
检查缓冲池大小:SHOW VARIABLES LIKE 'innodb_buffer_pool_size’;,确保它设置得合理(通常是系统内存的50%-70%)。
(3)系统资源竞争
数据库服务器本身可能正在经历周期性的资源瓶颈。
周期性任务:是否存在定时任务(如每日/每周报表生成、ETL作业、批量数据处理)?这些任务会消耗大量的CPU、内存和磁盘I/O资源,与你的常规查询争抢资源,导致其变慢。
邻居吵闹:如果数据库是部署在虚拟机或云服务器上,可能存在“邻居吵闹”问题。同一台物理机上的其他虚拟机在某段时间内资源使用激增,影响到了你的数据库性能。
排查方法:
-
检查慢SQL发生时间点系统的监控指标:CPU使用率、内存使用率、磁盘I/O使用率(特别是await和util指标)、网络流量。看是否有明显的关联性。
-
检查MySQL的进程列表:在变慢时执行SHOW PROCESSLIST;,看看是否有其他大型查询正在运行。
(4)锁竞争
你的查询可能正在等待获取锁。
-
行锁/表锁:你的应用程序可能在某些业务场景下(如定期结算、批量更新)持有锁的时间过长,导致其他查询被阻塞。虽然你的查询本身很快,但等待锁的时间被计入执行时间,从而成为“慢SQL”。
-
元数据锁(Metadata Lock):有一个长时间运行的事务(甚至是一个未提交的SELECT)或者正在执行ALTER TABLE等DDL操作,会阻塞其他需要获取元数据锁的查询。
(5)应用程序层面的周期性模式
-
缓存穿透:应用层的缓存(如Redis)可能定期失效。失效后,大量请求直接打到数据库,造成数据库瞬时压力过大,其中一些查询响应变慢。
-
特定时间的高流量:例如,每周一的早上用户活跃度最高,并发请求数增加,数据库负载加重,导致个别查询性能下降。
如何系统性地排查和解决?
开启并监控慢查询日志
-
确保MySQL的慢查询日志(slow query log)是开启的,并设置一个合理的阈值(如long_query_time = 2秒)。
-
使用工具(如mysqldumpslow, pt-query-digest)定期分析慢日志,找出TOP N的慢SQL及其出现的时间规律。
-
在问题发生时现场排查
第一步:使用SHOW PROCESSLIST; 查看当前所有连接状态,有没有阻塞、有没有状态不正常的查询。
第二步:对变慢的SQL立即执行EXPLAIN,分析其执行计划。保存下来与正常时对比。
第三步:查看服务器资源状态(top, htop,iostat -x 1)和MySQL状态变量(SHOW GLOBAL STATUS LIKE 'Threads_running’; 查看并发数,SHOW GLOBAL STATUS LIKE 'Innodb_row_lock%’; 查看行锁情况)。


