适宜阅读人群
- 需要面试的初/中/高级 java 程序员
- 想要查漏补缺的人
- 想要不断完善和扩充自己 java 技术栈的人
- java 面试官
具体面试题
下面一起来看 40 道面试题,具体的内容。下一篇:将组件相关面试题Kafka、nacos,mongodb,Linux,docker相关内容
一、Redis相关内容
1.Redis为什么执行这么快?
Redis运行比较快主要原因有以下几种:
- 纯内存操作:Redis将所有数据存储在内存中,这意味着对数据的读写操作直接在内存中运行,而内存的访问速度远远高于磁盘。这种设计使得Redis能够已接近硬件极限的速度处理数据读写
- 单线程模型:Redis使用单线程模型来处理客户端请求。这可能听起来效率不高,但是实际上,这种设计避免了多线程频繁切换和过度竞争带来的性能开销。Redis每个请求的执行时间都是很短的,因此单线程下,也能处理大量的并发请求
- I/O多路复用:Redis使用了I/O多路复用技术,可以在单线程的环境下同时监听多个客户端连接,只有当有网络事件(如用户发送一个请求)发生的时候才会进行实际的I/O操作。这样有效的利用了CPU资源,减少了无谓的等待
- 高效数据结构:Redis提供了多种高效的数据结构,如哈希表、有序集合等。这些数据结构的实现都经过了优化,使得Redis在处理这些数据结构的操作是非常高效的
2.Redis是单线程执行还是多线程执行?它有线程安全问题吗?为什么吗?
- Redis版本在6.0之前都是使用的单线程运行的。所有的客户端的请求处理、命令执行以及数据读写操作都是在一个主线程中完成得。这种设计目的就是为了防止多线程环境下的锁竞争和上下文切换所带来的性能开销,这样保证在高并发场景下的性能
- Redis版本在6.0中,开始引入了多线程的支持,但是这仅限于网络I/O层面,即在网络请求阶段使用工作线程进行处理,对于指令的执行过程,仍然是在主线程来处理,所以不会存在多个线程通知执行操作指令的情况
关于线程安全问题,从Redis服务层面俩看,Redis Server本身就是一个线程安全按的K-V数据库,也就是说在Redis Server上面执行的指令,不需要任何同步机制,不会存在线程安全问题
3.在实际工作中,使用Redis实现了哪些业务场景?
Redis在实际工作中广泛应用于多种业务场景,以下是一些常见的例子:
- 缓存:Redis作为Key-Value形态的内存数据库,最先会被想到的应用场景就是作为数据缓存。Redis提供了键过期功能,也提供了键淘汰策略,所以Redis用在缓存的场合非常多
- 排行榜:很多网站都有排行榜应用,如京东的月度销量榜单、商品按时间上新排行榜等。Redis提供的有序集合(zset)数据类
- 分布式会话:在集群模式下,一般会搭建以Redis等内存等内存数据库为中心的session服务,session不再由容器管理,而是由session服务及内存数据库管理
- 分布式锁:在高并发的情景,可以利用Redis的setnx功能来编写分布式锁
4.Redis常用数据类型有哪些?
Redis中,常见的数据类型有如下几种:
- 字符串(String):最简单的数据类型,可以包含任意数据,如文本、二进制数据等。常见的使用场景是存储Session信息、存储缓存信息、存储整数信息,可以使用incr实现整数+1,使用decr实现整数-1
- 列表(List):有序的字符串元素集合,支持双端进行插入和删除操作,可以用作队列或栈
- 哈希(Hash):用于存储对象,类似于关联数组。每个哈希可以包含字段和与之相关联的值。常见使用场景是存储Session信息、存储商品的购物车,购物车非常适用于哈希字典表示,使用人员唯一编号作为字典的key,value值可以存储商品的id和数量等信息、存储详情页等信息
- 集合(Set):一个无序并唯一的键值集合。它常见的使用场景是是仙女关注功能,比如关注我的人和我关注的人,使用集合存储,可以保证人员不重复
- 有序集合(Sorted Set):使用zset表示,相当于Set集合类型多了一个排序属性score(分值)。。它常见的使用场景是可以用来存储排名信息,关注列表功能,这样就可以根据关注实现排序展示
5.存储Session信息你会使用哪种数据类型?为什么?
在实际工作中,小型的项目会使用Redis存储Session信息,但是不同业务场景存储Session信息的类型也是不同的,具体来说:
存储数据简单(不涉及局部更新):使用String类型粗怒触Session,这样做的优缺点如下:
优点:
存取操作简单直观,只需要单个键执行操作即可
多余小型Session,存储开销相对于较小
缺点:
如果Session数据复杂或者需要频繁更新其中的部分字段,则每次更新都需要重新序列化整个Session对象
不利于查询Session内特定字段值
存储数据复杂(涉及局部更新):如果Session数据结构复杂且需要频繁更新或查询其中个别字段,通常建议使用哈希表存储Session。每个Session视为一个独立的哈希表,Session ID作为key,Sesion内各个字段作为field-value对存储在哈希表中。示例:HSET session:123 userId 123 username user1,这样做的优缺点如下:
优点:
可以方便地进行字段级别的读写操作,例如 HGET session:23 userd 和 HSET session:123 lastAccessTime now, 更新部分字段时无需修改整个Session内容
缺点:
相对于简单的字符串存储,哈希表占用的空间可能更大,尤其时当Session数据包含多个值的时候
小结: 如果 Session 数据结构复杂且需要频繁更新或查询其中的个别字段,通常建议使用哈希表来存储 Session;而在 Session 数据较为简单、不涉及局部更新的情况下,使用字符串存储也是可行的选择
6.有序集合底层是如何实现的?
在Redis7之前,有序集合使用的是ziplist(压缩列表)+skiplist(跳跃表),当数据列表元素小于128个,并且所有元素成员的长度都小于64字节时,使用压缩列表存储,否则使用调表存储
在Redis之后,有序集合使用listPack(紧凑列表)+skiplist(跳跃表)
7.什么是跳表?为什么使用跳表?
skiplist是一种以空间换时间的数据结构。由于链表无法进行二分查找,因此借鉴数据库索引的思想,提取出链表中的关键节点(索引),现在关键节点上查找,在进入下层链表查找提取多层关键节点,就形成了跳表。但是由于索引要占据一定的空间,所以索引添加的越多,占用的空间越多。
对于一个单链表来讲,即便链表中存储的数据是有序的,如果我们要想在其中查找某个数据,也只能从头到尾遍历链表。这样查找效率就会很低,时间复杂度会很高O(N)
从这个例子里,我们看出,加来一层索引之后,查找一个结点需要遍历的结点个数减少了,也就是说查找效率提高了。时间复杂度从原来的O(n)到O(logn),是一空间换时间的解决方法
8.说一下跳表的查询流程?
跳表的查询流程如下:
- 起始搜索:查询操作从跳表的顶层开始,跳表的顶层包含一个或多个节点,从最顶层的头节点开始,将当前节点设置为头节点
- 检查下一个节点:检查当前节点的下一个节点,如果节点的分值小于目标分值,则向右移动检查下一个节点,重复此步骤,直到找到一个大于目标值的节点,或者最后一个节点
- 逐层探索:如果当前下一个结点的值大于目标值,或者最后一个节点,则将当前指针向下层进行搜索,重复上述步骤
- 终止并返回:在查找的过程中,如果找到了和目标分支相同的值,或者遍历完所有层级仍然未找到对应的节点,则说明要查找的元素不存在于跳表中,则终止查找并返回查询到的内容或NULL值
9.说一下跳表的添加流程?为什么要有“随机层数”这个概念?
跳表的添加流程主要是包括以下步骤:
- 查找插入位置:首先,我们需要找到新元素应该插入的位置。这个过程与跳表查找操作类似,我们从最高层所以一年开始,逐层向下查找直到找到最后一个位置,使得该位置前面的元素小于新元素,后面的元素大于新元素
- 生成随机层数:在确定新元素插入位置后,我们需要决定新元素在跳表中的层数。这个层数是通过一个随机函数生成的。每个节点肯定都有第一层指针(每个节点都在第一层链表中)。如果一个节点有第i层指针(即节点已经在第一层到第i层链表中),那么他又第(i+1)层指针的
- 插入新元素:根据生成的随机层数,我们在相应的层中插入新元素。对于每一层,我们都需要更新相应的前驱和后继指针,使得它们指向新插入的元素
- 更新跳表的最大层数:如果新插入的元素的层数大于跳表的当前最大层数,我们需要更新跳表的最大层数
关于“随机层数”的概念,其主要目的是为了保持跳表的平衡性。如果我们固定每个元素的层数,那么在某些情况下,跳表可能会退化成普通的链表,从而导致查找效率降低。通过随机选择每个元素的层数,我们可以确保跳表的高度大致为log(n),从而保证查找、插入和删除操作的时间复杂度为O(log n)
给定如上跳表,假设要插入节点2。首先需要判断节点2是否已经存在,若存在则返回false。否则,随机生成待插入节点的层数
/** * 生成随机层数[0,maxLevel) * 生成的值越大,概率越小 * * @return */ private int randomLevel() { int level = 0; while (Math.random() < PROBABILITY && level < maxLevel - 1) { ++level; } return level; }
这里的PROBABILITY =0.5。上面算法的意思是返回1的概率是1/2,返回2的概率是1/4,返回3的概率是1/8,依次类推。看成一个分布的话,第0层包含所有节点,第1层含有1/2个节点,第2层含有1/4 个节点…注意这里有一个最大层数maxLevel ,也可以不设置最大层数。通过这种随机生成层数的方式使得实现起来简单。假设我们生成的层数是3
在1和3之间插入节点2,层数是3,也就是节点2跳跃到了第3层
public boolean add(E e) { if (contains(e)) { return false; } int level = randomLevel(); if (level > curLevel) { curLevel = level; } Node newNode = new Node(e); Node current = head; //插入方向由上到下 while (level >= 0) { //找到比e小的最大节点 current = findNext(e, current, level); //将newNode插入到current后面 //newNode的next指针指向该节点的后继 newNode.forwards.add(0, current.next(level)); //该节点的next指向newNode current.forwards.set(level, newNode); level--;//每层都要插入 } size++; return true; }我们通过一个例子来模拟,由于实现了直观的打印算法。假设我们要插入1, 6, 9, 3, 5, 7, 4, 8 过程如下:
add: 1 Level 0: 1 add: 6 Level 0: 1 6 add: 9 Level 2: 9 Level 1: 9 Level 0: 1 6 9 add: 3 Level 2: 3 9 Level 1: 3 9 Level 0: 1 3 6 9 add: 5 Level 2: 3 9 Level 1: 3 5 9 Level 0: 1 3 5 6 9 add: 7 Level 2: 3 9 Level 1: 3 5 9 Level 0: 1 3 5 6 7 9 add: 4 Level 2: 3 9 Level 1: 3 5 9 Level 0: 1 3 4 5 6 7 9 add: 8 Level 2: 3 9 Level 1: 3 5 9 Level 0: 1 3 4 5 6 7 8 9
10:使用Redis如何实现分布式锁?
实现分布式锁
使用Redis实现分布式锁可以通过setnx(set if not exists)命令实现,但当我们使用setnx创建键值成功时,则表中加锁成功,否则代码加锁失败,实现示例如下:
127.0.0.1:6379> setnx lock true (integer) 1#创建锁成功 #逻辑业务处理.. 当我们重复加锁时,只有第一次会加锁成功 127.8..1:6379> setnx lock true # 第一次加锁 (integer) 1 127.8.8.1:6379> setnx lock true # 第二次加锁 (integer) 0 从上述命令可以看出,我们可以看执行结果返回是不是1,就可以看出是否加锁成功 释放分布式锁 127.0.0.1:6379> de1 lock (integer) 1 #释放锁
然而,如果使用 setnx ock true 实现分布式锁会存在死锁问题,以为 setnx 如未设置过期时间,锁忘记删了或加锁线程宕机都会导致死锁,也就是分布式锁一直被占用的情况解决死锁问题
死锁问题可以通过设置超时时间来解决,如果超过了超时时间,分布锁会自动释放,这样就不会存在死锁问题了也就是 setnx和 expire 配合使用,在 Redis 2.6.12 版本之后,新增了一个强大的功能,我们可以使用一个原子操作也就是一条命令来执行 setnx 和expire 操作了,实现示例如下:
127.0.0.1:6379> set lock true ex 3 nx OK #创建锁成功 127...1:6379> set lock true ex 3 nx (ni1) #在锁被占用的时候,企图获取锁失败其中ex为设置超时时间, nx 为元素非空判断,用来判断是否能正常使用锁的。
因此,我们在 Redis 中实现分布式锁最直接的方案就是使用 set key value ex timeout nx 的方式来实现。
二、 MySQL面试题
1.什么是mysql的内连接、外连接、交叉连接、笛卡尔积呢?
在MySQL中,内连接、外连接、交叉连接和笛卡尔积是四种不同类型的表连接方式,它们用于从两个或多个表中检索数据。以下是它们的定义和区别:
内连接(Inner Join):
- 定义:内连接返回两个表中满足连接条件的记录。它基于两个表之间的匹配列进行连接,只返回匹配的行。
- 适用场景:适用于需要从多个表中获取相关数据的场景,且只关心匹配的数据。
- 基本语法:
SELECT columns FROM table1 INNER JOIN table2 ON table1.column = table2.column;外连接(Outer Join):
- 定义:外连接不仅返回两个表中满足连接条件的记录,还包括某张表(或两张表)中不满足匹配关系的记录。MySQL支持左外连接(LEFT JOIN)、右外连接(RIGHT JOIN)和全外连接(虽然MySQL不直接支持全外连接,但可以通过UNION ALL和LEFT JOIN、RIGHT JOIN的组合来实现)。
- 左外连接:返回左表中的所有记录,以及右表中与左表中行匹配的记录。即使右表中没有匹配的行,左表的记录也会出现在结果集中。
- 右外连接:返回右表中的所有记录,以及左表中与右表中行匹配的记录。即使左表中没有匹配的行,右表的记录也会出现在结果集中。
- 适用场景:适用于需要获取某个表中的所有记录以及与之匹配的另一个表中的记录的场景。
- 基本语法(以左外连接为例):
SELECT columns FROM table1 LEFT JOIN table2 ON table1.column = table2.column;交叉连接(Cross Join):
- 定义:交叉连接也称为笛卡尔积,它返回两个表中所有可能的行组合。如果表A有m行,表B有n行,那么交叉连接的结果将有m * n行。
- 适用场景:适用于需要生成两个表之间所有可能的组合的场景,这在某些特定的数据分析场景中非常有用。
- 基本语法:
SELECT columns FROM table1 CROSS JOIN table2;笛卡尔积(Cartesian Product):
- 定义:笛卡尔积是数学中的一个概念,在多表连接时,如果没有指定连接条件,所有表中的每一行都会与其他表中的每一行进行组合,从而产生的结果集就是笛卡尔积。笛卡尔积通常由交叉连接或者在缺少连接条件的普通连接产生。
- 适用场景:通常,笛卡尔积不是有意使用的,而是在编写SQL查询时忘记指定连接条件时意外产生的。它会导致结果集中包含大量不必要的行,因此应该避免。
- 注意:在MySQL中,直接使用
CROSS JOIN关键字可以避免产生意外的笛卡尔积。总结来说,内连接、外连接、交叉连接和笛卡尔积是四种不同的表连接方式,它们根据连接条件和需求的不同,用于从两个或多个表中检索和组合数据。在实际应用中,需要根据具体需求选择合适的连接方式,并注意避免产生不必要的笛卡尔积。
2.说一下数据库的三大范式?
数据库的三大范式是数据库设计中的重要规范,它们旨在减少数据冗余,提高数据存储和使用的性能。以下是三大范式的详细解释:
一、第一范式(1NF)
第一范式要求关系型数据库中的每个列都必须是原子的,即每列的值不能再分解成其他几列。这意味着每个列中不能包含多个值或多个重复的值。如果存在多个值,应该拆分成多个列或多个表。简而言之,第一范式就是确保每列保持原子性,每个字段都应该是不可再拆分的最小单位。
二、第二范式(2NF)
第二范式在第一范式的基础上,进一步要求每列数据完全依赖于主键。如果表中存在非主键部分依赖(即某些字段只依赖于主键的一部分),就不符合第二范式。为了满足第二范式,应将非主键部分依赖的字段抽取出来,建立新的表,并使用外键关联。换句话说,第二范式要求数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关。
三、第三范式(3NF)
第三范式在第二范式的基础上,要求表中的非主键字段不依赖于其他非主键字段。如果存在传递依赖(即非主键字段依赖于其他非主键字段),就不符合第三范式。为了满足第三范式,应将传递依赖的字段抽取出来,建立新的表,并使用外键关联。简而言之,第三范式要求数据表中的每一列数据都和主键直接相关,而不能间接相关。
遵循数据库的三大范式可以帮助设计结构合理的数据库,减少数据冗余,提高数据存储和查询的效率。同时,也需要注意在实际应用中,根据系统的实际需求,有时可能需要适当违反某些范式以优化性能或满足特定需求
3.varchar 与 char 的区别?
MySQL 中的
VARCHAR和CHAR是两种用于存储字符串的数据类型,它们之间存在几个关键的区别:1. 存储方式
VARCHAR:可变长度字符串。只占用字符串实际需要的空间加上一个额外的字节(或两个,取决于最大长度)来记录字符串的长度。这意味着对于较短的字符串,
VARCHAR比CHAR更节省存储空间。CHAR:固定长度字符串。始终占据预定义的空间,无论实际存储的字符串长度如何。如果输入的字符串长度低于指定的长度,MySQL 会使用空格填充剩余空间。
2. 性能
VARCHAR:在处理大量短字符串时,
VARCHAR通常比CHAR更具性能优势。但在进行排序和查找操作时,由于其长度可变,性能可能会受到影响。CHAR:由于其固定长度的特性,检索和更新操作可能更快,因为数据库不需要在查询时考虑如何分割或合并数据。特别是在处理固定长度且较长的字符串时,
CHAR可能会提供更好的性能。3. 适用场景
VARCHAR:适合存储长度可变的字符串,如姓名、地址或描述性文本。这些字段的长度可能因记录而异,使用
VARCHAR可以更有效地利用存储空间。CHAR:适合存储长度固定、需要填充或对齐的字符串,如代码、ID 或密码。这些字段通常具有固定的长度要求,使用
CHAR可以确保数据的一致性和完整性。4. 最大长度
VARCHAR:最大长度可以达到 65,535 个字符(但实际长度受限于行的总大小和字符集)。在创建表时,需要指定
VARCHAR列的最大长度,如VARCHAR(255)表示该列可以存储最多 255 个字符的字符串。CHAR:最大长度为 255 个字符。如果需要存储更长的固定长度字符串,可能需要考虑使用其他数据类型(如
TEXT)。5. 空格处理
VARCHAR:在存储时会保留尾部的空格。
CHAR:若字段本身末尾存在空格,检索出来会自动截断末尾空格(因为分不清空格是字段含有的还是填充产生的)。若字段本身前端存在空格,是不会截断的。
6. 空值处理
VARCHAR:默认情况下不允许空值(但这取决于表的定义和 SQL 模式)。然而,这并不意味着
VARCHAR列不能存储空字符串(即长度为 0 的字符串)。CHAR:允许空值,并且如果输入文本的长度小于指定的长度,
CHAR会使用空格填充剩余空间。7. 排序和比较
VARCHAR 和 CHAR:在排序和比较时,都会根据字符集进行区分大小写的比较(除非指定了不同的排序规则)。但
CHAR在比较时会填充空格以确保字符串长度一致。在选择使用
VARCHAR还是CHAR时,应根据具体的应用需求和数据特性来决定。如果字符串长度变化较大,且存储空间是一个关注点,那么VARCHAR可能更合适;如果字符串长度基本固定,且对查询速度有较高要求,那么CHAR可能更适合。
4.blob 和 text 有什么区别?
MySQL 中的
BLOB和TEXT是两种用于存储大量数据的数据类型,但它们之间存在一些关键的区别:1. 存储数据类型
BLOB:用于存储二进制大对象(Binary Large Object)。它适用于存储二进制数据,如图片、音频、视频、文档等非文本数据。
TEXT:用于存储非二进制字符串数据。它适合存储大量的文本数据,如文章、评论、日志等。
2. 存储和检索方式
BLOB:数据不按字符编码存储,作为二进制串,对它的内容不做任何转换。检索时,返回的是二进制数据,需要在应用层进行解码或处理。
TEXT:数据按照字符编码来存储(例如:utf8, latin1等)。检索时,返回的是字符串数据,可以直接在SQL语句中进行操作。
3. 字符集和排序
BLOB:没有字符集的概念,因此不支持排序和基于字符集的比较操作。
TEXT:支持多种字符集,可以对数据进行字符集转换、排序和比较等操作。
4. 索引支持
BLOB:通常不支持索引(除了前缀索引),因为对BLOB字段创建索引会显著增加查询时间,且效果有限。
TEXT:支持前缀索引(最多768字节),可以用于加速基于前缀的查询。
5. 存储限制
BLOB 和 TEXT 都有四种类型,分别支持不同的最大长度:
TINYBLOB/TINYTEXT:最大长度为255字节。
BLOB/TEXT:最大长度为65,535字节(约64KB)。
MEDIUMBLOB/MEDIUMTEXT:最大长度为16,777,215字节(约16MB)。
LONGBLOB/LONGTEXT:最大长度为4,294,967,295字节(约4GB)。
6. 使用场景
BLOB:适用于存储图像、音频、视频、文档等二进制数据。
TEXT:适用于存储文章、评论、日志等大量文本数据。
7. 性能考虑
BLOB:在处理大量二进制数据时,可能会影响数据库的性能,特别是在插入、更新和查询操作时。因此,建议将BLOB数据存储在文件系统中,并在数据库中存储文件的路径。
TEXT:由于其可变长度的特性,可能需要更多的存储空间来存储相同的数据量。在查询时,可以使用
SUBSTRING函数来获取数据的一部分,以减少内存的使用和加快检索速度。在选择使用
BLOB还是TEXT时,应根据具体的应用需求和数据特性来决定。如果需要存储二进制数据,应使用BLOB;如果需要存储大量文本数据,应使用TEXT。
5.DATETIME和TIMESTAMP的异同?
MySQL中
DATETIME和TIMESTAMP都是用来表示日期和时间的数据类型,但它们之间有一些重要的区别。以下是两者的异同点:相同点
时间格式:
两者都可用来表示YYYY-MM-DD HH:MM.fraction类型的日期。
不同点
存储方式:
TIMESTAMP:将客户端插入的时间从当前时区转化为UTC(世界标准时间)进行存储。查询时,将其又转化为客户端当前时区进行返回。
DATETIME:不做任何时区转换,基本上是原样输入和输出。存储范围:
DATETIME:存储范围是 1000-01-01 00:00:00.000000 到 9999-12-31 23:59:59.999999。
TIMESTAMP:存储范围是 1970-01-01 00:00:01.000000 UTC 到 2038-01-19 03:14:07.999999 UTC。存储空间:
在MySQL 5.6.4之前,
DATETIME占用8个字节,而TIMESTAMP占用4字节。在MySQL 5.6.4及之后的版本中,由于这两个类型允许有小数部分,所以占用的存储空间和以前不同。
DATETIME的非小数部分需要5个字节,而TIMESTAMP的非小数部分需要4个字节,小数部分都需要0到3个字节,具体取决于存储值的小数秒精度。默认值与自动更新:
DATETIME:允许为空值,可以自定义值,不会自动修改其值。虽然不可以设定默认值,但是可以在指定DATETIME字段的值的时候使用now()变量来自动插入系统的当前时间。
TIMESTAMP:允许为空值,但不可以自定义值,默认值为CURRENT_TIMESTAMP()(即当前的系统时间)。数据库会自动修改其值,所以在插入记录时不需要指定TIMESTAMP字段的名称和值,只需要在设计表的时候添加一个TIMESTAMP字段即可。在记录被更新时,TIMESTAMP的值也会自动更新为当前系统时间。使用场景:
DATETIME:适合用来记录数据的原始的创建时间,因为无论你怎么更改记录中其他字段的值,DATETIME字段的值都不会改变,除非你手动更改它。
TIMESTAMP:适合用来记录数据的最后修改时间,因为只要你更改了记录中其他字段的值,TIMESTAMP字段的值都会被自动更新。结论
如果你需要存储的日期和时间范围超出了
TIMESTAMP的范围,或者你不希望考虑时区转换,那么DATETIME是一个更好的选择。如果你需要更高效的存储空间,并且你的应用可以处理UTC时间和时区转换,那么
TIMESTAMP可能是一个更好的选择。
6.MySQL 中 in 和 exists 的区别?
在 MySQL 中,
IN和EXISTS是两种常用于子查询的关键字,它们有着不同的用途、性能特点以及适用场景。以下是它们的主要区别:1. 用法区别
IN:
用于检查一个值是否在一系列值之一中。
常用于子查询返回的结果集作为主查询的条件。
示例:
SELECT * FROM employees WHERE department_id IN (SELECT department_id FROM departments WHERE location = 'New York');
EXISTS:
用于检查子查询是否返回至少一行数据。
如果子查询返回至少一行,则
EXISTS条件为真。示例:
SELECT * FROM employees e WHERE EXISTS (SELECT 1 FROM departments d WHERE e.department_id = d.department_id AND d.location = 'New York');2. 性能区别
IN:
当子查询返回的结果集较小时,
IN的性能通常较好。MySQL 会尝试将
IN子查询的结果集缓存起来,以便快速查找。但是,当子查询返回大量数据时,
IN的性能可能会下降,因为需要遍历整个结果集来检查匹配项。
EXISTS:
对于大型数据集,
EXISTS通常比IN更高效。
EXISTS子查询是逐行执行的,对于每一行主查询的记录,都会执行一次子查询,直到找到匹配项或确定不存在匹配项为止。这种“短路”行为使得
EXISTS在某些情况下比IN更快。3. 适用场景
IN 适用于:
子查询返回的结果集较小且相对固定。
需要对主查询的结果进行集合运算(如包含检查)。
EXISTS 适用于:
子查询返回的结果集较大或不确定。
需要检查子查询是否返回任何结果,而不仅仅是匹配特定值。
4. 其他注意事项
当使用
IN时,如果子查询返回NULL值,则这些NULL值不会被包含在结果集中,因为IN操作符不会与NULL进行比较。当使用
EXISTS时,子查询中的SELECT语句通常使用SELECT 1或SELECT *,因为实际选择的内容并不重要,重要的是是否存在记录。总结:选择使用
IN还是EXISTS取决于具体的查询场景和数据量。在优化查询性能时,建议根据实际的执行计划和响应时间来进行选择。通常,对于小型数据集和简单的查询,IN可能更合适;而对于大型数据集和复杂的查询,EXISTS可能更高效。
7.MySQL 里记录货币用什么字段类型比较好?
在 MySQL 中记录货币金额时,推荐使用
DECIMAL或NUMERIC字段类型。以下是详细解释和推荐理由:推荐字段类型:
DECIMAL 或 NUMERIC (decimal numeric):
精确性:这两种类型可以精确地表示定点数,避免了浮点数(如
FLOAT和DOUBLE)可能带来的舍入误差。自定义精度:可以指定精度(总位数)和小数位数,以满足不同货币格式的需求。例如,
DECIMAL(10, 2)可以存储最多10位数字,其中2位是小数位。适用场景:适用于金融计算、会计、电子商务等需要高精度的场景。
不推荐字段类型:
FLOAT 和 DOUBLE:
精度问题:这些类型使用二进制浮点数表示,可能会导致精度损失,不适合用于需要精确计算的货币金额。
舍入误差:在进行货币计算时,可能会引入舍入误差,导致计算结果不准确。
INT:
无小数位:只能存储整数,无法表示货币的小数部分。
限制:虽然可以用于存储没有小数部分的货币金额(如分、厘等),但通常不是首选,因为大多数货币都需要表示小数。
示例:
CREATE TABLE my_table ( id INT AUTO_INCREMENT PRIMARY KEY, amount DECIMAL(10, 2) NOT NULL, -- 使用 DECIMAL 类型存储货币金额 currency VARCHAR(3) NOT NULL -- 使用 VARCHAR 类型存储货币代码,如 USD、EUR 等 );在上面的示例中,
amount字段使用了DECIMAL(10, 2)类型来存储货币金额,确保了金额的精确性。同时,currency字段使用了VARCHAR(3)类型来存储货币代码。注意事项:
选择适当的精度和标度:根据业务需求选择合适的精度和标度,以平衡存储空间和计算准确性。
性能考虑:虽然
DECIMAL类型比浮点数类型更精确,但也可能稍微占用更多的存储空间和处理时间。因此,在设计时要权衡准确性和性能需求。国际化支持:如果应用程序需要支持多种货币,确保数据库设计和业务逻辑能够灵活应对不同的货币格式和规则。
综上所述,
DECIMAL或NUMERIC类型是 MySQL 中记录货币金额的最佳选择。
8.drop,delete与truncate的区别?
drop、delete与truncate的主要区别体现在用法、执行速度、事务处理、是否触发触发器以及是否保留表结构等方面。
一、用法不同
drop:用于删除整个表,包括表中的数据、表结构、索引、约束等。执行后,表将从数据库中完全移除。
truncate:用于清空表中的所有数据,但保留表的结构(定义)。执行后,表的结构仍然存在,但所有数据被删除,且表的自增ID计数器会重置。
delete:用于删除表中的部分或全部数据。可以根据指定的条件删除满足条件的行。如果不加where子句,将删除表中的所有数据(但保留表结构),这与truncate类似,但delete操作会记录每一行的删除操作在事务日志中。
二、执行速度不同
一般来说,drop > truncate > delete。drop直接删除表,速度最快;truncate清空表数据但不记录每行的删除操作,速度次之;delete逐行删除数据并记录每行的删除操作,速度最慢。
三、事务处理不同
drop和truncate:是DDL(数据定义语言)语句,操作立即生效,不能回滚。原数据不放入rollback segment中。
delete:是DML(数据库操作语言)语句,操作会放入rollback segment中,事务提交后才生效。如果有相应的trigger,执行时将被触发,且可以在事务中回滚。
四、是否触发触发器不同
delete:会触发与表相关的删除触发器。
truncate和drop:不会触发触发器。
五、是否保留表结构不同
drop:不保留表结构,整个表(包括数据和结构)都被删除。
truncate和delete:只删除数据,保留表结构。但truncate会重置表的自增ID计数器,而delete不会(除非手动重置)。
综上所述,drop、delete与truncate在MySQL中各有其独特的用途和特性。在选择使用哪个命令时,应根据具体的需求和场景来决定
9.UNION 与 UNION ALL 的区别?
UNION 和 UNION ALL 是 SQL 中用于合并两个或多个 SELECT 查询结果的关键字,它们的主要区别在于如何处理重复行和性能表现。
主要区别
处理重复行:
UNION:会对合并后的结果集进行去重操作,即去除重复的行,只保留唯一的行。这相当于在合并结果后执行了一个 DISTINCT 操作。
UNION ALL:不会去除重复的行,它会简单地将所有查询的结果集合并在一起,包括所有重复的行。
性能:
UNION:由于需要进行去重操作,所以在处理大量数据时,性能可能会比 UNION ALL 差。尤其是在合并的结果集中存在大量重复行时,UNION 的去重操作会消耗较多的时间和资源。
UNION ALL:由于不需要进行去重操作,所以性能通常比 UNION 好。如果确定合并的结果集中不会有重复行,或者不需要去除重复行,那么使用 UNION ALL 会更高效。
排序:
UNION:默认会对合并后的结果集进行排序,以确保返回的结果是按照默认顺序排列的。如果需要自定义排序顺序,可以使用外部的 ORDER BY 子句来指定排序条件。
UNION ALL:不会对合并后的结果集进行排序操作,默认情况下返回的结果是按照查询的顺序排列的。如果需要对结果进行排序,同样可以使用外部的 ORDER BY 子句来指定排序条件。
使用场景
UNION:适用于需要合并多个查询结果集,并且需要去除重复行的场景。例如,你可能需要从多个表中检索相似的数据,然后去除重复项后合并到一个结果集中进行进一步的处理或展示。
UNION ALL:适用于需要合并多个查询结果集,并且不需要去除重复行的场景。例如,你可能需要从多个表或查询中检索数据,然后将它们合并到一个结果集中进行进一步的处理或展示,且这些数据中可能包含重复项。
示例
假设我们有两个表
table1和table2,它们具有相同的列结构(即列的数量和对应列的数据类型相同)。
UNION 示例:
SELECT column1, column2 FROM table1 UNION SELECT column1, column2 FROM table2;这个查询将返回一个结果集,包含
table1和table2中column1和column2列的所有唯一值。UNION ALL 示例:
SELECT column1, column2 FROM table1 UNION ALL SELECT column1, column2 FROM table2;这个查询将返回一个结果集,包含
table1和table2中column1和column2列的所有值,包括重复行。注意事项
在使用 UNION 或 UNION ALL 时,每个 SELECT 语句必须具有相同数量的列,且对应列的数据类型必须兼容。
每个 SELECT 语句中的列的顺序也必须相同。
总结来说,UNION 和 UNION ALL 是 SQL 中用于合并查询结果集的重要工具,选择使用哪个关键字应根据具体的需求和场景来决定。
10.count(1)、count(*)与count(列名)的区别?
count(1)、count(*)和count(列名)在 SQL 中都是用于统计行数的函数,但它们之间存在一些重要的区别。以下是详细解释:1. 语义不同
count(1):表示统计查询结果的行数,不考虑表中的任何列的值,只关心是否存在行。这里的“1”只是一个占位符,表示每一行都会被计数。
count(*):表示统计查询结果的行数,与count(1)类似,不考虑表中的任何列的值,只关心是否存在行。*表示选取所有的列。
count(列名):表示统计指定列的非空值的数量,只统计指定列中不为 NULL 的行。2. 性能不同
count(1)和count(*):在大多数数据库系统中的性能是相同的,因为它们都不关心具体的列值,只关心行的存在与否。现代数据库优化器通常会将count(*)优化为count(1)或其他等效的形式。
count(列名):在某些数据库系统中可能会比count(1)和count(*)性能稍差,因为它需要检查指定列的值是否为 NULL,并排除 NULL 值的行。3. 空值处理逻辑不同
count(1)和count(*):不关心表中的具体列值,因此不受空值影响,会将包含 NULL 值的行也计算在内。
count(列名):只统计指定列的非空值,会排除包含 NULL 值的行,只计算指定列中不为 NULL 的行。4. 使用范围不同
count(1)和count(*):适用于查询整个表或满足特定条件的行数。
count(列名):适用于统计特定列的非空值数量。5. 结果集不同
count(1)和count(*):返回的结果集只有一行一列,即表示查询结果的行数。
count(列名):返回的结果集也只有一行一列,但表示的是指定列的非空值数量。6. 可读性不同
count(1):语义上不够明确,不容易理解其作用。
count(*):语义明确,表示统计行数。
count(列名):语义明确,表示统计指定列的非空值数量。注意事项
在选择使用
count(1)、count(*)还是count(列名)时,应根据具体需求和场景来决定。如果只需要统计行数而不需要关心列值,建议使用count(*),因为它在大多数数据库系统中的性能优化较好且语义明确。如果需要统计特定列的非空值数量,则应使用
count(列名)。
count(1)虽然在某些数据库系统中可能会稍微快一些,但由于现代数据库优化器的存在,这种性能差异通常可以忽略不计。综上所述,
count(1)、count(*)和count(列名)在 SQL 中各有其用途和适用场景,选择使用哪个函数应根据实际情况来判断。
11.一条SQL 查询语句的执行顺序?
一条SQL查询语句的执行顺序并不是完全按照书写顺序来的,而是有一套特定的逻辑顺序。以下是SQL查询语句的逻辑执行顺序:
FROM / JOIN:
首先确定数据来源,即执行FROM子句,指定要查询的表。如果查询中包含了JOIN操作,那么会根据JOIN条件来合并这些表。
ON:
在JOIN操作后,设定表之间的关联键,即执行ON子句,筛选不符合连接条件的记录。
WHERE:
对FROM或JOIN操作后得到的结果集应用WHERE子句中的条件,过滤掉不符合条件的行。
GROUP BY:
如果查询中包含了GROUP BY子句,那么会根据指定的列或表达式将结果集中的行分组。分组操作是在WHERE子句之后进行的,因为分组是基于已经过滤的数据集。
HAVING:
对GROUP BY操作后得到的分组进行过滤。HAVING子句允许你对分组后的结果进行条件过滤,这是WHERE子句做不到的,因为WHERE是在分组之前应用的。
SELECT:
此时,查询会选择SELECT子句中指定的列或表达式。如果查询中使用了聚合函数(如SUM(), AVG(), COUNT()等),这些函数会在这一步计算。注意,虽然SELECT子句在查询语句中通常位于FROM或JOIN之后,但在逻辑上,它通常在这些操作之后执行。
DISTINCT:
如果查询中包含了DISTINCT关键字,那么在这一步会对结果进行去重。
ORDER BY:
最后,根据ORDER BY子句中的列或表达式对结果进行排序。这一步是在所有其他操作(除了LIMIT)之后进行的。
LIMIT / OFFSET:
如果查询中包含了LIMIT和/或OFFSET子句,那么这些子句将用于限制查询结果的数量或跳过一定数量的行。
需要强调的是,虽然上述顺序是逻辑上的执行顺序,但数据库查询优化器可能会以不同的顺序执行这些操作,以提高查询的效率。因此,了解这个逻辑顺序有助于编写清晰、可预测的查询,但不必过于担心物理执行顺序。
12.说说 MySQL 的基础架构?
MySQL的基础架构主要由三大核心部分构成:连接层、核心服务层和存储引擎层。
连接层:
- 连接层是客户端与MySQL数据库之间的桥梁,主要负责通信和身份验证。
- 它通过TCP/IP协议或Unix Socket等方式与客户端进行通信,并处理客户端的连接请求、身份验证和权限校验等任务。
- 连接层确保数据交换的安全与稳定,是MySQL数据库接收外部请求的第一步。
核心服务层:
- 核心服务层涵盖了MySQL的众多核心功能,包括查询缓存、分析器、优化器和执行器等。
- 查询缓存(在MySQL 8.0版本已移除):用于存储之前执行过的查询结果,以提高查询效率。
- 分析器:负责SQL语句的语法分析,将输入的SQL语句转换为计算机可以理解的形式(语法树)。
- 优化器:根据分析器生成的语法树,制定多个执行计划,并选择最优的执行计划来执行查询。
- 执行器:根据优化器选择的执行计划,调用存储引擎来获取数据,并返回给客户端。
- 此外,核心服务层还包括了所有的内置函数(如日期、时间、数学和加密函数等)和所有跨存储引擎的功能(如存储过程、触发器、视图等)。
存储引擎层:
- 存储引擎层负责数据的存储和提取,是MySQL数据库的核心组件之一。
- MySQL支持多种存储引擎,如InnoDB、MyISAM、Memory等,不同的存储引擎具有不同的特点和适用场景。
- InnoDB:是MySQL的默认存储引擎,以其高性能和可靠性著称。它支持事务处理、行级锁定和外键约束等功能,适用于高并发和需要数据完整性的应用场景。
- MyISAM:是MySQL早期的默认存储引擎,适用于只读或读多写少的场景。它不支持事务处理和外键约束,但具有较快的查询速度。
- Memory:将数据存储在内存中,读写速度非常快,但数据不具备持久性。它适用于需要快速访问的临时数据场景。
通过这三层架构,MySQL实现了高效、稳定的数据处理和存储功能。同时,MySQL还提供了丰富的优化策略和事务管理功能,以满足不同场景下的需求。
请注意,MySQL的具体实现可能会随着版本更新而有所变化,因此建议参考最新的官方文档以获取最准确的信息。
13.MySQL日志文件有哪些?分别介绍下作用?
MySQL中有多种日志文件,每种日志文件都承担着不同的职责,对于数据库的管理、监控、性能优化以及故障排查等方面起着关键作用。以下是MySQL中主要的日志文件及其作用:
重做日志(Redo Log):
作用:记录事务过程中的修改操作,用于数据库崩溃恢复。确保事务的持久性,防止在发生故障的时间点,尚有脏页面未写入磁盘。在重启MySQL服务的时候,根据redo log进行重做,从而达到事务的持久性。
物理格式:记录的是物理数据页面的修改信息,redo log是顺序写入redo log file的物理文件中去的。包含内存中的日志缓冲(redo log buffer)和磁盘上的重做日志文件(redo log file)。
回滚日志(Undo Log):
作用:主要用于事务回滚,保证数据的原子性。记录修改之前的数据,如delete一条记录时,undo log中会记录一条对应的insert记录,从而保证能恢复到数据修改之前。同时,它还可以提供多版本并发控制下的读取(MVCC)。
逻辑格式:记录的是逻辑上的修改,而不是物理页面上的操作。
二进制日志(Bin Log):
作用:记录所有修改数据库数据的操作,用于数据恢复、备份和主从复制。查询操作不会记录到bin log中。
物理格式:以二进制形式存储,记录了MySQL执行的所有的DDL(数据定义语言)和DML(数据操纵语言)信息。
错误日志(Error Log):
作用:记录MySQL服务器启动、关闭和运行过程中的错误信息,帮助排查问题。
物理格式:通常是文本文件。
慢查询日志(Slow Query Log):
作用:记录执行时间超过指定阈值的SQL语句,帮助发现性能瓶颈并进行优化。
物理格式:文本文件。
一般查询日志(General Log):
作用:记录客户端连接信息以及执行的SQL语句信息,用于排错和调试。普通查询日志会记录增删改查的信息,因此一般是关闭的。
物理格式:文本文件。
中继日志(Relay Log):
作用:在主从服务器架构中,从服务器用来存放主服务器二进制日志内容的一个中间件文件。从服务器通过读取中继日志的内容,来同步主服务器上的操作。
物理格式:以二进制形式存储。
这些日志文件共同构成了MySQL的日志系统,为数据库的管理和维护提供了有力的支持。通过合理配置和使用这些日志文件,可以有效地监控和管理MySQL数据库,确保其稳定运行和高效性能。
14.binlog和redo log有什么区别?
MySQL中binlog和redo log的核心区别如下(按功能维度分类):
一、归属与作用范围
归属层级
redo log是InnoDB存储引擎特有的日志15,而binlog由MySQL Server层实现,所有存储引擎均可使用14。设计目标
redo log主要用于实现事务的崩溃恢复(crash-safe)能力,确保数据修改的持久性16;binlog则用于数据归档、主从复制等场景13。二、日志类型与内容
物理日志 vs 逻辑日志
redo log记录的是物理日志,描述数据页的具体变更(如“在数据页X的偏移量Y处更新值Z”)12;
binlog记录的是逻辑日志,保存原始SQL语句或行变更前后的逻辑数据(如“UPDATE表SET字段=值”)12。存储内容
redo log仅记录事务中未提交但已写入数据页的操作(覆盖事务提交和回滚前的修改)58;
binlog记录所有已提交的事务操作(包括DDL和DML语句)13。三、写入机制与存储方式
写入方式
redo log采用循环写入,固定大小的文件组(如默认2个文件,每个1GB)被循环覆盖15;
binlog采用追加写入,文件达到阈值后生成新文件,旧文件保留14。持久化参数
redo log通过innodb_flush_log_at_trx_commit=1实现每次事务提交时持久化到磁盘5;
binlog通过sync_binlog=1确保每次事务提交后持久化5。四、应用场景对比
特性
redo log
binlog
崩溃恢复能力
✅ 支持(恢复未刷盘的脏页)16
❌ 不支持15
主从复制
❌ 不参与
✅ 核心依赖13
数据恢复范围
仅InnoDB引擎数据
全库数据(跨引擎)1
15.一条更新语句怎么执行的了解吗?
以下是MySQL中一条更新语句的执行流程,基于核心模块的作用机制分步骤说明:
一、连接与权限验证
连接器建立通信
客户端通过TCP协议与MySQL服务端建立连接,连接器验证用户名、密码及权限。若权限不足或连接超时,直接终止流程12。清空查询缓存
更新操作会使该表关联的查询缓存失效(例如商品库存更新后,缓存中的库存数据不再有效)23。二、语法解析与执行计划
分析器处理
对SQL语句进行词法分析(识别关键词如UPDATE、SET)和语法分析(校验语句是否符合规范)12。优化器生成计划
确定最优执行路径,例如选择使用主键索引(如WHERE ID=2)定位目标行24。三、存储引擎处理与日志记录
InnoDB执行更新
加载数据页:若目标数据页不在内存(Buffer Pool),则从磁盘加载到内存35。
更新内存数据:在Buffer Pool中修改数据页,标记为“脏页”35。
写入Undo Log:记录旧版本数据,用于事务回滚或MVCC(多版本并发控制)56。
Redo Log预写入
将数据页的物理变更(如“在页号为3的偏移量16处写入值42”)写入Redo Log Buffer。
事务提交时,通过
innodb_flush_log_at_trx_commit=1强制刷盘到Redo Log文件(保证崩溃恢复能力)35。四、Binlog归档与提交确认
Binlog记录逻辑变更
将逻辑操作(如UPDATE T SET c=c+1 WHERE ID=2)写入Binlog Cache,事务提交时通过sync_binlog=1持久化到磁盘35。两阶段提交(2PC)
Prepare阶段:InnoDB将Redo Log标记为“Prepare”状态。
Commit阶段:Binlog写入完成后,Redo Log标记为“Commit”状态。
(确保Redo Log与Binlog逻辑一致,避免主从数据不一致)35。五、最终数据持久化
脏页异步刷盘
Buffer Pool中的脏页由后台线程定期刷回磁盘(Checkpoint机制),非实时操作以提升性能35。崩溃恢复
若MySQL异常宕机,重启时通过Redo Log重放未刷盘的脏页操作,并结合Binlog恢复已提交事务的数据35。关键流程对比(查询 vs 更新)
阶段
查询语句
更新语句
缓存处理
可能命中查询缓存(若开启)
强制清空关联表缓存23
日志记录
无
依赖Redo Log(物理)和Binlog(逻辑)35
持久化方式
直接返回结果
异步脏页刷盘+日志两阶段提交35
以上流程综合了MySQL Server层与InnoDB引擎的协作机制,通过Redo Log和Binlog的协同实现事务的持久性与数据一致性
16.怎么看执行计划(explain),如何理解其中各个字段的含义?
在 MySQL 中,
EXPLAIN语句用于获取 MySQL 如何执行一个 SELECT 语句的计划。这可以帮助你分析和优化查询的性能。使用EXPLAIN可以显示关于 MySQL 如何连接表和选择索引的详细信息。如何使用 EXPLAIN基本语法如下:
EXPLAIN SELECT ...;或者你也可以使用
EXPLAIN FORMAT=JSON SELECT ...;来获取更详细的 JSON 格式的输出。EXPLAIN 输出中的字段含义
以下是一些常见的
EXPLAIN输出字段及其含义:
id:
查询的标识符。如果你的查询包含子查询,MySQL 会为每个子查询分配一个唯一的 ID。
如果查询包含 UNION 或 UNION ALL,则每个查询块也会有一个唯一的 ID。
select_type:
查询的类型。
SIMPLE: 简单的 SELECT 查询,不使用 UNION 或子查询。
PRIMARY: 查询中最外层的 SELECT。
UNION: UNION 中的第二个或后续的 SELECT 查询。
DEPENDENT UNION: UNION 中的第二个或后续的 SELECT 查询,依赖于外部查询。
SUBQUERY: 子查询中的第一个 SELECT。
DEPENDENT SUBQUERY: 子查询,依赖于外部查询。
DERIVED: 派生表的 SELECT(子查询在 FROM 子句中)。table:
输出行所引用的表。
partitions:
匹配的分区。
type:
连接类型,表示 MySQL 如何找到所需行。常见的类型及其性能从高到低排列如下:
system: 表只有一行(= 系统表)。
const,eq_ref: 对于每个索引键,表中最多有一个匹配行。
ref: 非唯一性索引扫描,返回匹配某个单值的所有行。
range: 只检索给定范围的行,使用一个索引来选择行。
index: 全索引扫描(索引与表一样大小),比全表扫描稍快。
ALL: 全表扫描。possible_keys:
显示 MySQL 认为执行查询时可能使用的索引。
key:
实际使用的索引。如果没有使用索引,则该列为 NULL。
key_len:
使用的索引的长度。在某些情况下,不是索引的全部部分都会被使用。
ref:
显示索引的哪一列或常数被用于查找值。
rows:
MySQL 认为必须检查的行数,以找到查询所需的行。这是一个估算值,并不总是完全准确。
filtered:
表示返回结果的行占开始查找行的百分比。
Extra:
包含不适合在其他列中显示的额外信息。常见的值包括:
Using where: 使用 WHERE 子句来过滤行。
Using temporary: 使用临时表来存储结果,常见于 GROUP BY 和 ORDER BY。
Using filesort: MySQL 需要额外的步骤来对结果进行排序,通常出现在 ORDER BY 和 GROUP BY。
Using index: 只从索引树中读取数据。
Using join buffer (Block Nested Loop): 使用连接缓存。
Impossible WHERE noticed after reading const tables: WHERE 子句在读取常量表后立即评估为不可能,因此没有读取其他表。示例
假设有一个名为
employees的表,你可以这样使用EXPLAIN:
EXPLAIN SELECT * FROM employees WHERE dept_id = 10;假设输出如下:
textCopy Code
+----+-------------+-----------+-------+---------------+---------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------+-------+---------------+---------+---------+-------+------+-------------+ | 1 | SIMPLE | employees | ref | dept_id_idx | dept_id | 4 | const | 10 | Using where | +----+-------------+-----------+-------+---------------+---------+---------+-------+------+-------------+这个输出表示:
id: 查询的标识符为 1。
select_type: 这是一个简单的 SELECT 查询。
table: 查询的表是employees。
type: 使用 ref 类型连接,表示非唯一性索引扫描。
possible_keys: 可能使用的索引是dept_id_idx。
key: 实际使用的索引是dept_id_idx。
key_len: 使用的索引长度为 4。
ref: 使用常量值来查找匹配的行。
rows: 估计需要检查的行数为 10。
Extra: 使用 WHERE 子句来过滤行。通过这些信息,你可以更好地了解 MySQL 如何执行你的查询,并据此优化你的查询和索引设计。
17.能简单说一下索引的分类吗?
一、按数据结构分类
B+树索引
最常见的索引类型,支持范围查询和排序,适用于大部分场景(如MySQL的InnoDB默认索引)。
优点:高效的范围查询、有序访问。
缺点:插入/删除时需要维护树结构。
哈希索引
基于哈希表实现,仅支持等值查询(如
=、IN)。优点:等值查询极快(O(1))。
缺点:不支持范围查询、排序,哈希冲突可能影响性能。
全文索引
用于文本内容的模糊搜索(如
LIKE '%keyword%')。实现方式:倒排索引(记录关键词到文档的映射)。
R树索引
用于空间数据查询(如地理位置、GIS数据)。
二、按存储结构分类
聚集索引(Clustered Index)
数据行的物理存储顺序与索引顺序一致(如InnoDB的主键索引)。
特点:每个表只能有一个聚集索引。
非聚集索引(Non-Clustered Index)
索引和数据存储分离,索引仅保存指向数据的指针(如MyISAM的索引)。
特点:一个表可以有多个非聚集索引。
三、按字段数量分类
单列索引
仅基于单个字段建立的索引。
联合索引(复合索引)
基于多个字段组合建立的索引(如
INDEX (a, b, c))。使用原则:最左前缀匹配(查询需包含最左字段)。
四、按唯一性分类
主键索引(Primary Key)
唯一且非空,每个表只能有一个主键。
唯一索引(Unique Index)
保证字段值的唯一性,允许空值。
五、按功能用途分类
覆盖索引(Covering Index)
索引包含查询所需的所有字段,无需回表查数据(性能优化关键)。
前缀索引
仅对字段的前N个字符建立索引(如长文本字段)。
总结
核心作用:减少全表扫描,提升查询效率。
选择原则:根据查询场景选择索引类型(如范围查询用B+树,等值查询用哈希)。
注意事项:索引会占用存储空间,过多索引可能降低写性能。
18.为什么使用索引会加快查询?
使用索引可以加快查询速度,主要原因在于索引通过优化数据的查找方式,减少了查询过程中需要扫描的数据量。以下是具体原理和机制:
1. 索引的本质
索引是一种数据结构(如 B+ 树、哈希表等),它存储了表中某一列(或多列)的值及其对应的行位置(如行号或指针)12。
类似于书籍的目录,索引通过预先组织数据,使得查询时能够快速定位目标记录,而无需逐行扫描整个表12。
2. 索引加速查询的原理
(1)减少数据扫描量
无索引时:数据库需要执行全表扫描(Full Table Scan),逐行检查每一行数据是否符合查询条件,时间复杂度为 O(n)12。
有索引时:数据库通过索引直接定位到符合条件的行,时间复杂度通常为 O(log n)(如 B+ 树索引)或 O(1)(如哈希索引)12。
(2)优化数据访问路径
索引通过排序或哈希的方式组织数据,使得查询时可以快速跳过不符合条件的记录,减少磁盘 I/O 操作12。
例如,在 B+ 树索引中,查询可以通过二分查找快速定位目标数据12。
(3)支持高效排序和分组
索引本身是有序的,因此对于
ORDER BY或GROUP BY操作,数据库可以直接利用索引的顺序,避免额外的排序开销12。3. 索引的适用场景
等值查询(如
WHERE column = value):哈希索引或 B+ 树索引可以快速定位目标记录12。范围查询(如
WHERE column BETWEEN value1 AND value2):B+ 树索引支持高效的范围查找12。排序和分组:索引可以避免额外的排序操作,提升查询性能12。
4. 索引的代价
虽然索引可以加速查询,但也会带来一定的开销:
存储空间:索引需要额外的存储空间来存储索引数据12。
写操作性能:在插入、更新或删除数据时,索引也需要同步更新,可能导致写操作变慢12。
维护成本:索引需要定期维护(如重建或优化),以确保其性能12。
5. 总结
索引通过优化数据的查找方式,减少了查询过程中需要扫描的数据量,从而显著提升了查询性能。然而,索引的使用需要权衡查询性能与写操作开销,合理设计索引是数据库优化的关键12。
19.创建索引有哪些注意点?
创建索引是优化数据库查询性能的重要手段,但也需要谨慎设计,以避免不必要的开销和性能问题。以下是创建索引时的主要注意点:
1. 选择合适的列
高频查询列:优先为经常出现在
WHERE、JOIN、ORDER BY、GROUP BY等子句中的列创建索引12。高选择性列:选择数据分布离散的列(如唯一值较多的列),避免为低选择性列(如性别、布尔值)创建索引24。
主键和唯一性列:主键和唯一性列通常自动带有索引,无需额外创建27。
2. 避免过多索引
维护成本高:过多的索引会增加写操作(如插入、更新、删除)的开销,并占用更多存储空间14。
选择性创建:仅为对查询性能关键的列创建索引,避免为每个列都创建索引46。
3. 注意索引类型
单列索引:适用于简单查询(如
WHERE column = value)26。复合索引:适用于多列查询,需注意列顺序(遵循最左前缀原则)26。
特殊索引:如全文索引、哈希索引等,需根据具体需求选择14。
4. 优化索引长度
短索引优先:选择数据类型小的列作为索引,减少存储空间和查询时间78。
前缀索引:对于长文本字段,可以使用前缀索引以节省空间68。
5. 定期维护索引
重建索引:定期重建索引以减少碎片化,提升性能46。
监控性能:通过监控查询执行计划和索引使用情况,及时调整索引策略47。
6. 权衡读写性能
写操作影响:索引会提高读性能,但可能导致写操作变慢,需在读写之间找到平衡14。
写密集型表:在频繁更新的表上,尽量减少不必要的索引46。
7. 其他注意事项
空值处理:对于包含大量空值的列,需谨慎创建索引4。
小表避免索引:数据量较小的表(如少于 300 行)通常无需创建索引,全表扫描可能更快56。
通过合理设计索引,可以有效提升查询性能,但需综合考虑数据分布、查询频率和读写操作的影响,避免过度索引带来的负面效果
20.索引哪些情况下会失效呢?
索引在某些情况下会失效,导致查询性能下降。以下是常见的索引失效场景:
1. 模糊查询中使用前导通配符
当使用
LIKE '%abc'或LIKE '%abc%'时,索引无法生效,因为通配符在前导致数据库无法利用索引进行快速查找13。2. 数据类型不匹配
查询条件中的数据类型与索引字段类型不一致时,数据库会进行隐式类型转换,导致索引失效14。
3. 在索引列上使用函数或表达式
如果查询条件中对索引列使用了函数(如
LOWER(column)或YEAR(column)),索引会失效,因为数据库无法直接匹配索引值13。4. 使用
OR连接多个条件
当
OR连接的多个条件中有一个未使用索引时,整个查询可能会导致索引失效16。5. 使用
NOT运算
NOT运算(如NOT IN、NOT EXISTS)通常会使索引失效,因为数据库无法高效处理否定条件16。6. 联合索引未遵循最左前缀原则
使用联合索引时,如果查询条件未从最左列开始或跳过了中间列,索引可能会失效36。
7. 索引列参与运算
如果查询条件中索引列参与了算术运算(如
column + 1 = value),索引会失效47。8. 使用
IS NULL或IS NOT NULL
查询条件中包含
IS NULL或IS NOT NULL时,索引可能不会被有效利用67。9. 范围查询后再进行其他条件筛选
使用范围查询(如
BETWEEN、<、>)后,再添加其他条件,可能导致索引失效6。10. 表连接中连接条件无索引
在表连接查询中,如果连接条件中的字段没有索引,可能导致索引失效1。
11. 数据分布不均匀
当索引列的数据存在大量重复值时,数据库优化器可能会决定不使用索引7。
12. 查询条件使用了非索引列
如果查询条件中的列未被索引,数据库无法利用索引优化查询8。
通过避免上述场景,可以有效防止索引失效,提升查询性
21.索引不适合哪些场景呢?
索引在数据库查询优化中扮演着重要角色,但在某些场景下,使用索引可能并不适合,甚至可能导致性能下降。以下是一些不适合使用索引的场景:
小表
对于数据量较小的表,全表扫描的开销不大,索引带来的好处有限,反而会增加维护索引的额外开销16。频繁写操作
如果表中有大量的插入、更新或删除操作,维护索引会显著增加写操作的开销,降低整体性能。例如日志表或事务表16。低选择性列
选择性低的列,索引不具备明显的优势。例如,性别列只有“Male”和“Female”两种值,大多数查询都会返回大量记录,索引效果不佳16。频繁更新的列
索引会增加更新列的开销。如果某列经常被更新,维护该列的索引成本较高,影响性能。例如频繁变动的计数器或状态列16。复杂查询
某些复杂查询(如大量使用子查询、视图等)可能不会利用索引,即使创建了索引,优化器也可能选择全表扫描15。全表扫描更有效
对于需要读取大量数据的查询,全表扫描可能比使用索引更有效。例如大量数据的统计查询或报表生成16。非前缀匹配
对于使用LIKE的查询,如果通配符位于开头,索引可能会失效25。使用OR连接多个条件
当查询中包含OR时,如果OR连接的多个条件中有一个没有使用索引,可能会导致索引失效25。在查询中使用函数
如果查询中在索引列上应用了函数,索引可能失效25。数据类型不匹配
查询条件的数据类型与索引字段类型不一致时,索引无法被利用5。索引列数据大量重复
索引列的数据存在大量重复时,数据库优化器可能会决定不使用索引5。**查询包含SELECT ***
如果查询不针对特定字段进行筛选,或者查询需要返回表中的所有字段,索引的作用会受到限制6。复合索引字段选择不合理
复合索引的字段顺序不合理或选择性不高时,可能导致性能下降6。查询条件与索引不匹配
当查询条件与索引的匹配方式不一致时,索引可能无法发挥作用45。综上所述,索引并非在所有场景下都适用,需要根据具体情况进行权衡和选择
22.MySQL 索引用的什么数据结构了解吗?
MySQL 主要使用 B+树 作为索引结构,因为它适合大多数查询场景,尤其是范围查询和排序操作。哈希索引、全文索引和 R-Tree 索引则用于特定场景。选择合适的索引结构可以显著提升查询性能。
23.那一棵 B+树能存储多少条数据呢?
一棵 B+树能存储多少条数据,取决于以下几个关键因素:
B+树的阶数(Order):
阶数决定了每个节点最多可以存储多少个子节点或键值。阶数越大,每个节点存储的数据越多,树的层级越浅。节点大小:
节点大小通常与磁盘块大小(如 4KB、8KB 或 16KB)匹配,以减少 I/O 操作。键值大小:
键值的大小(如主键的长度)会影响每个节点能存储的键值数量。数据量:
叶子节点存储的是实际数据或指向数据的指针,数据量的大小也会影响 B+树的存储能力。计算公式
假设:
m 是 B+树的阶数(每个节点最多有 m 个子节点)。
h 是 B+树的高度(从根节点到叶子节点的层数)。
n 是 B+树能存储的最大数据量。
B+树的存储能力可以通过以下公式估算:
n=mhn=mh示例计算
假设:
B+树的阶数 m=100m=100(每个节点最多有 100 个子节点)。
B+树的高度 h=3h=3。
则 B+树能存储的最大数据量为:
n=1003=1,000,000n=1003=1,000,000即这棵 B+树最多可以存储 100 万条数据。
实际影响因素
节点大小:
如果节点大小为 16KB,键值大小为 16 字节,则每个节点可以存储约 1000 个键值。树的高度:
树的高度越低,查询效率越高。例如,阶数为 1000 的 B+树,高度为 3 时可以存储 10 亿条数据。数据分布:
如果数据分布不均匀,可能导致某些节点存储的数据量较少,影响 B+树的存储效率。总结
一棵 B+树能存储的数据量取决于阶数、节点大小、键值大小和树的高度。通过合理设计 B+树的参数,可以显著提高其存储能力和查询性能。例如,阶数为 1000 的 B+树,高度为 3 时可以轻松存储 10 亿条数据。
24.MySQL 中有哪几种锁,列举一下?
MySQL 中的锁机制是保证数据一致性和并发控制的核心,锁的类型可以从不同维度分类。以下是 MySQL 中常见的锁类型及其作用:
一、按锁的粒度分类
表级锁(Table-Level Lock)
作用:锁住整张表,其他会话无法修改表结构或写入数据。
适用场景:MyISAM 引擎默认使用表级锁;InnoDB 在特定操作(如
ALTER TABLE)时也会加表级锁。特点:简单但并发度低,容易成为性能瓶颈。
行级锁(Row-Level Lock)
作用:仅锁住表中的某一行(或索引记录),其他行仍可并发操作。
适用场景:InnoDB 引擎默认支持行级锁,适合高并发事务。
特点:并发度高,但锁管理复杂(需要维护更多锁信息)。
页级锁(Page-Level Lock)
作用:锁住数据页(一组连续的记录),已逐渐被行级锁取代。
适用场景:旧版本 MySQL 的某些存储引擎(如 BDB)使用。
二、按锁的模式分类
共享锁(Shared Lock, S Lock)
作用:允许其他会话读数据,但禁止修改数据。
语法:
SELECT ... LOCK IN SHARE MODE;兼容性:多个共享锁可以共存,但与排他锁互斥。
排他锁(Exclusive Lock, X Lock)
作用:禁止其他会话读或写数据(包括加共享锁或排他锁)。
语法:
SELECT ... FOR UPDATE;兼容性:与其他所有锁互斥。
意向锁(Intention Lock)
作用:快速判断表中是否存在行级锁,避免逐行检查锁冲突。
类型:
意向共享锁(IS Lock):表示事务准备对某些行加共享锁。
意向排他锁(IX Lock):表示事务准备对某些行加排他锁。
特点:意向锁是表级锁,与行级锁配合使用。
三、InnoDB 特有的锁类型
记录锁(Record Lock)
作用:锁住索引中的某一行记录(若表无索引,则锁住隐式主键)。
示例:
UPDATE users SET name='Alice' WHERE id=1;会锁住id=1的行。间隙锁(Gap Lock)
作用:锁住索引记录之间的间隙,防止其他事务在范围内插入数据(解决幻读)。
示例:
SELECT * FROM users WHERE age BETWEEN 20 AND 30 FOR UPDATE;会锁住age在 20~30 之间的间隙。临键锁(Next-Key Lock)
作用:锁住“记录 + 间隙”,是记录锁和间隙锁的组合。
示例:默认情况下,InnoDB 的
SELECT ... FOR UPDATE使用临键锁,防止幻读。插入意向锁(Insert Intention Lock)
作用:在插入操作前设置,表示事务想在某个间隙插入数据,与其他间隙锁兼容。
自增锁(Auto-Inc Lock)
作用:保证自增列(AUTO_INCREMENT)的值唯一性,在插入时短暂加锁。
四、其他锁类型
元数据锁(Metadata Lock, MDL)
作用:防止在查询或事务中修改表结构(如
ALTER TABLE)。触发场景:长时间未提交的事务持有 MDL 锁,可能导致 DDL 操作阻塞。
预测锁(Predicate Lock)
作用:用于空间索引(如 GIS 数据),锁定满足空间条件的记录。
五、锁的冲突与兼容性
锁类型
共享锁(S)
排他锁(X)
意向共享(IS)
意向排他(IX)
共享锁(S)
兼容
冲突
兼容
冲突
排他锁(X)
冲突
冲突
冲突
冲突
意向共享(IS)
兼容
冲突
兼容
兼容
意向排他(IX)
冲突
冲突
兼容
兼容
总结
MyISAM:仅支持表级锁(共享读锁、排他写锁)。
InnoDB:支持行级锁(记录锁、间隙锁、临键锁等)和表级锁,通过意向锁优化锁冲突检测。
锁的选择:高并发场景优先使用行级锁(InnoDB),批量操作或表结构变更时可能需表级锁。
死锁风险:行级锁可能导致死锁,可通过
SHOW ENGINE INNODB STATUS分析死锁日志。
25.说说 InnoDB 里的行锁实现?
InnoDB 的行锁实现是一个复杂而高效的机制,旨在提供细粒度的并发控制。以下是其核心实现要点:
1. 锁类型与算法
记录锁 (Record Lock):锁定索引中的特定记录。例如,
SELECT * FROM t WHERE id = 1 FOR UPDATE会在id=1的索引项上加排他锁。间隙锁 (Gap Lock):锁定索引记录之间的间隙,防止其他事务插入数据。例如,
SELECT * FROM t WHERE id BETWEEN 5 AND 10 FOR UPDATE会锁定 (5,10) 的区间。临键锁 (Next-Key Lock):结合记录锁和间隙锁,锁定记录及之前的间隙。这是 InnoDB 在 可重复读(RR)隔离级别 下的默认锁算法,用于避免幻读。例如,若索引包含值 10 和 20,临键锁会锁定 (10, 20] 区间。
插入意向锁 (Insert Intention Lock):一种特殊的间隙锁,表示事务准备插入数据。与现有间隙锁冲突,需等待。
2. 锁的粒度与索引依赖
基于索引加锁:InnoDB 的行锁通过索引实现。若查询使用索引(如主键或二级索引),仅锁定相关索引项;若无索引,则退化为 全表扫描 并锁定所有扫描过的记录(可能升级为表锁)。
隐式锁机制:新插入的记录通过事务 ID(trx_id)隐式加锁,直到事务提交。其他事务访问该记录时,隐式锁会转换为显式锁。
3. 隔离级别的影响
可重复读(RR):默认使用临键锁,防止幻读。
读已提交(RC):仅使用记录锁,允许幻读,但通过 MVCC 实现非阻塞读。
4. 锁的兼容性与竞争处理
兼容矩阵:
共享锁(S)之间兼容,但与排他锁(X)互斥。
间隙锁之间兼容(均为共享),但与插入意向锁互斥。
锁竞争管理:通过全局锁管理器跟踪锁状态,阻塞冲突请求,直到锁释放。
5. 死锁处理
主动检测:InnoDB 使用 等待图(Wait-for Graph) 检测死锁,超时或选择回滚代价较小的事务。
6. MVCC 与锁的结合
非阻塞读:通过多版本并发控制(MVCC),普通 SELECT 语句读取快照数据(基于 undo log),无需加锁。
写操作加锁:写操作(如 UPDATE/DELETE)仍需要行锁,确保数据一致性。
7. 锁释放时机
事务提交或回滚时释放所有锁,长事务可能导致锁争用。
示例场景
范围更新:
UPDATE t SET col=1 WHERE id > 10 AND id < 20在 RR 级别下,对符合条件的记录加临键锁,阻止其他事务插入或修改该区间的数据。无索引更新:若
WHERE条件无索引,InnoDB 可能锁定全表或扫描的所有行,严重影响并发性能。总结
InnoDB 的行锁实现通过 索引关联的锁算法(记录锁、间隙锁、临键锁)、隔离级别策略 和 MVCC 机制,平衡了并发性能与数据一致性。合理设计索引和事务逻辑是避免锁争用的关键。
26.意向锁是什么知道吗?
意向锁(Intention Lock)是 InnoDB 存储引擎中的一种表级锁机制,用于支持多粒度锁定,即允许事务在行级锁和表级锁之间进行协调。它主要用于提高锁管理的效率,避免事务在加行级锁时与表级锁发生冲突。以下是意向锁的核心要点:
1. 意向锁的作用
协调行锁与表锁:意向锁是一种表级锁,用于表明事务打算在表中的某些行上加行级锁(共享锁或排他锁)。它不会直接锁定数据行,而是作为一种“声明”,帮助系统快速判断表级锁与行级锁的兼容性38。
提高锁管理效率:通过意向锁,InnoDB 可以在事务加行级锁之前,快速判断是否存在表级锁冲突,从而避免不必要的锁等待48。
2. 意向锁的类型
意向共享锁(IS Lock):表示事务打算在表中的某些行上加共享锁(S Lock)。例如,
SELECT ... LOCK IN SHARE MODE语句会先获取表的 IS 锁,再获取行的 S 锁48。意向排他锁(IX Lock):表示事务打算在表中的某些行上加排他锁(X Lock)。例如,
SELECT ... FOR UPDATE或UPDATE语句会先获取表的 IX 锁,再获取行的 X 锁48。3. 意向锁的兼容性
IS 锁与 IS 锁:兼容,多个事务可以同时获取表的 IS 锁48。
IS 锁与 IX 锁:兼容,事务可以同时获取表的 IS 锁和 IX 锁48。
IX 锁与 IX 锁:兼容,多个事务可以同时获取表的 IX 锁48。
IS/IX 锁与表级锁:
IS 锁与表级共享锁(S Lock)兼容,但与表级排他锁(X Lock)冲突48。
IX 锁与表级共享锁(S Lock)和表级排他锁(X Lock)都冲突48。
4. 意向锁的使用场景
行级锁操作:当事务需要对表中的某些行加共享锁或排他锁时,会先获取表的 IS 锁或 IX 锁48。
表级锁操作:当需要对整个表加锁时(如
LOCK TABLES),意向锁可以帮助快速判断是否存在行级锁冲突48。5. 意向锁与行级锁的关系
意向锁是行级锁的“预告”:它不会直接锁定数据行,而是表明事务打算对某些行加锁38。
行级锁的获取依赖于意向锁:在获取行级锁之前,事务必须先获取表的 IS 锁或 IX 锁48。
总结
意向锁是 InnoDB 中用于协调行级锁和表级锁的机制,通过声明事务的加锁意图,提高锁管理的效率并减少锁冲突。它分为意向共享锁(IS)和意向排他锁(IX),与表级锁和行级锁共同构成了 InnoDB 的锁体系
27.MySQL 的乐观锁和悲观锁了解吗?
MySQL 中的 乐观锁 和 悲观锁 是两种处理并发控制的机制,适用于不同的场景,各有优缺点。以下是两者的详细对比和分析:
1. 悲观锁(Pessimistic Locking)
核心思想:假设并发冲突发生的概率很高,因此在操作数据前直接加锁,阻止其他事务修改数据,直到当前事务完成。
实现方式:
使用
SELECT ... FOR UPDATE(排他锁)或SELECT ... LOCK IN SHARE MODE(共享锁)。依赖 MySQL 的行锁、表锁机制(需使用 InnoDB 引擎)。
示例:
-- 开启事务 BEGIN;
-- 加排他锁查询数据 SELECT * FROM products WHERE id = 1 FOR UPDATE;
-- 修改数据 UPDATE products SET stock = stock - 1 WHERE id = 1;
-- 提交事务(释放锁) COMMIT;适用场景:
写操作频繁,冲突概率高。
需要强一致性保证(如支付、库存扣减)。
事务涉及复杂操作,需长时间持有锁。
优点:
保证数据强一致性,避免脏写。
实现简单,直接依赖数据库锁机制。
缺点:
加锁导致阻塞,降低并发性能。
可能引发死锁(需事务合理设计)。
2. 乐观锁(Optimistic Locking)
核心思想:假设并发冲突概率低,只在提交时检查数据是否被修改。若冲突则回滚或重试,而非提前加锁。
实现方式:
在表中添加版本号(
version)或时间戳字段。更新时检查版本号是否与读取时一致。
示例:
-- 读取数据和版本号 SELECT stock, version FROM products WHERE id = 1;
-- 假设读取到 stock=100, version=1
-- 更新时检查版本号 UPDATE products SET stock = stock - 1, version = version + 1 WHERE id = 1 AND version = 1;
-- 检查是否更新成功(影响行数是否为1)
-- 若失败(版本号不匹配),回滚并重试适用场景:
读多写少,冲突概率低。
高并发场景,减少锁竞争。
无法接受长时间阻塞(如秒杀活动)。
优点:
无锁机制,提高并发性能。
避免死锁问题。
缺点:
需处理冲突重试逻辑(增加代码复杂度)。
不保证绝对一致性,可能需多次重试。
3. 对比总结
特性
悲观锁
乐观锁
冲突假设
高概率冲突
低概率冲突
加锁时机
操作前加锁
提交时检查
实现复杂度
简单(依赖数据库)
复杂(需版本号+重试逻辑)
性能开销
高(阻塞)
低(无锁)
适用场景
强一致性、短事务
高并发、长事务
数据一致性
强一致
最终一致
4. 如何选择?
悲观锁:适合对数据准确性要求极高、冲突频繁的场景(如金融交易)。
乐观锁:适合高并发读、低频写、容忍重试的场景(如评论点赞)。
5. 注意事项
悲观锁需合理设计事务粒度,避免死锁和长时间锁等待。
乐观锁需结合业务处理重试(如自旋重试、消息队列异步补偿)。
MySQL 的
MVCC(多版本并发控制)机制(如REPEATABLE READ隔离级别)与乐观锁思想类似,但并非同一概念。根据具体业务场景选择合适的并发控制策略,是平衡性能与一致性的关键。
28.MySQL遇到过死锁问题吗,你是如何解决的?
MySQL 死锁是并发事务中常见的问题,通常是由于多个事务竞争资源并形成循环等待导致的。以下是关于 MySQL 死锁的成因、检测与解决方案的详细说明:
1. 死锁的成因
死锁通常发生在以下场景:
资源竞争:多个事务同时访问并试图锁定相同的资源(如行、表)12。
加锁顺序不一致:事务对资源的加锁顺序不同,导致互相等待46。
长事务和锁等待:事务执行时间过长,未及时释放锁,导致其他事务等待超时或死锁37。
2. 死锁的检测
MySQL 的 InnoDB 存储引擎内置了死锁检测机制:
等待图(Wait-for Graph):InnoDB 维护一个等待图,当检测到环路时,确定发生死锁7。
自动回滚:InnoDB 会选择代价最小的事务(如执行时间最短的事务)进行回滚,以打破死锁67。
3. 死锁的解决方案
3.1 自动处理
MySQL 会自动检测并回滚死锁事务,应用程序需捕获死锁错误并重试事务67。
3.2 手动排查与优化
查看死锁日志:使用
SHOW ENGINE INNODB STATUS;命令查看死锁详细信息,分析死锁原因17。优化 SQL 语句:减少全表扫描,使用索引,降低锁竞争13。
统一加锁顺序:确保事务以相同顺序访问资源,避免循环等待47。
3.3 预防措施
减少锁持有时间:尽量缩短事务执行时间,避免长时间占用资源37。
合理使用索引:优化查询条件,减少锁定的记录数量35。
降低隔离级别:在业务允许的情况下,将隔离级别从
REPEATABLE READ降低为READ COMMITTED,减少锁的范围和强度7。4. 实际案例
例如,在银行转账场景中,事务 A 和事务 B 分别锁定账户 1 和账户 2,随后互相请求对方的锁,导致死锁。通过统一加锁顺序(如先锁定账户 1 再锁定账户 2),可以有效避免死锁68。
5. 总结
MySQL 死锁是并发事务中的常见问题,但通过合理设计事务、优化 SQL 语句、统一加锁顺序等措施,可以有效预防和解决死锁问题
29.MySQL 事务的四大特性说一下?
MySQL 事务的四大特性(ACID)是保证数据库操作可靠性和一致性的核心机制:
1. 原子性(Atomicity)
定义:事务中的操作要么全部成功,要么全部失败回滚,不能只执行部分操作。
例子:转账时,A 账户扣款和 B 账户入账必须同时成功或同时失败,不存在中间状态。
实现机制:通过 Undo Log(回滚日志)记录事务操作前的数据状态,失败时撤销已执行的操作。
2. 一致性(Consistency)
定义:事务执行后,数据库必须从一个合法的状态转换到另一个合法的状态,保证数据的业务规则正确。
例子:转账前后,A 和 B 账户的总金额应保持不变。
实现机制:由原子性、隔离性、持久性共同保障,同时依赖应用层逻辑(如约束、触发器等)确保业务规则。
3. 隔离性(Isolation)
定义:多个并发事务执行时,彼此的操作互不干扰,事务的执行结果与串行执行一致。
例子:事务 A 读取数据时,事务 B 的未提交修改不会影响 A 的结果。
实现机制:通过 锁机制 和 MVCC(多版本并发控制) 实现不同隔离级别(如读未提交、读已提交、可重复读、串行化)。
4. 持久性(Durability)
定义:事务提交后,对数据的修改是永久性的,即使系统崩溃也不会丢失。
例子:转账成功后,即使数据库宕机,重启后金额仍为正确值。
实现机制:通过 Redo Log(重做日志)记录事务提交后的修改,系统崩溃后根据日志恢复数据。
总结
ACID 是事务的核心保障,确保在高并发或异常情况下,数据库仍能可靠运行。
MySQL 通过 Undo Log(原子性)、锁与 MVCC(隔离性)、Redo Log(持久性)等技术实现这些特性,最终共同维护一致性。
30.那 ACID 靠什么保证的呢?
MySQL 事务的 ACID 特性通过以下机制保证:
1. 原子性(Atomicity)
机制:由 Undo Log 保证。Undo Log 记录了事务执行前的数据状态,如果事务失败或回滚,系统会根据 Undo Log 撤销已执行的操作,确保事务的原子性15。
2. 一致性(Consistency)
机制:由 原子性、隔离性、持久性 共同保证,同时依赖 应用程序逻辑(如约束、触发器等)确保数据符合业务规则15。
3. 隔离性(Isolation)
机制:通过 锁机制 和 MVCC(多版本并发控制) 实现。锁机制控制并发访问,MVCC 通过保留数据的历史版本,确保事务间的读写操作互不干扰15。
4. 持久性(Durability)
机制:由 Redo Log 保证。Redo Log 记录了事务提交后的修改,即使系统崩溃,重启后也能根据 Redo Log 恢复数据,确保修改的永久性15。
总结
Undo Log 保证原子性,Redo Log 保证持久性,锁与 MVCC 保证隔离性,三者共同维护一致性
31.事务的隔离级别有哪些?MySQL的默认隔离级别是什么?
一、事务的隔离级别
事务的隔离级别定义了多个并发事务之间的可见性和相互影响程度,主要解决以下并发问题:
脏读(Dirty Read):事务读取到另一个未提交事务的中间数据。
不可重复读(Non-Repeatable Read):同一事务中多次读取同一数据,结果不一致(被其他事务修改或删除)。
幻读(Phantom Read):同一事务中多次查询同一范围的数据,结果集数量不一致(被其他事务插入或删除)。
标准隔离级别(由低到高):
读未提交(Read Uncommitted):
事务可以读取其他未提交事务的修改。
允许问题:脏读、不可重复读、幻读。
读已提交(Read Committed):
事务只能读取其他已提交事务的修改。
解决脏读,但允许不可重复读和幻读。
可重复读(Repeatable Read):
同一事务中多次读取同一数据时,结果保持一致。
解决脏读、不可重复读,但允许幻读。
MySQL 的默认隔离级别,并通过 MVCC 和间隙锁(Gap Lock)进一步避免幻读。
串行化(Serializable):
事务完全串行执行,通过加锁阻塞其他事务的读写操作。
解决所有并发问题,但性能最差。
二、MySQL 的默认隔离级别
MySQL 的默认隔离级别是 可重复读(Repeatable Read),但通过以下机制增强:
MVCC(多版本并发控制):
每个事务启动时生成一个“一致性视图”(Read View),基于事务 ID 判断数据可见性。
避免不可重复读,且通过快照读保证同一事务内的数据一致性。
间隙锁(Gap Lock):
在可重复读级别下,MySQL 会对索引记录之间的间隙加锁,阻止其他事务插入新数据。
避免幻读(标准 SQL 的可重复读允许幻读,但 MySQL 通过间隙锁规避)。
三、隔离级别对比表
隔离级别
脏读
不可重复读
幻读
性能
适用场景
读未提交
✅
✅
✅
高
无严格一致性要求的场景
读已提交
❌
✅
✅
较高
大多数 OLTP 系统(如 Oracle)
可重复读(默认)
❌
❌
❌*
中等
需避免不可重复读的场景
串行化
❌
❌
❌
低
强一致性要求的场景
注:MySQL 在可重复读级别下通过间隙锁避免幻读,但严格按 SQL 标准仍允许幻读。
四、查看和修改隔离级别
查看当前隔离级别:
SELECT @@transaction_isolation; -- MySQL 8.0+
SELECT @@tx_isolation; -- MySQL 5.x
修改会话或全局隔离级别:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; -- 修改当前会话 SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ; -- 修改全局设置五、总结
隔离级别本质是性能与一致性的权衡,级别越高,一致性越强,但并发性能越差。
MySQL 的默认隔离级别是可重复读,通过 MVCC 和间隙锁优化,解决了标准 SQL 中可重复读允许幻读的问题。
32.MVCC了解吗?怎么实现的?
MySQL 的 MVCC(Multi-Version Concurrency Control,多版本并发控制) 是一种通过维护数据的多个版本来实现高并发读写的机制。它避免了传统的锁机制带来的性能损耗,是 InnoDB 实现 可重复读(Repeatable Read) 和 读已提交(Read Committed) 隔离级别的核心手段。
一、MVCC 的核心原理
MVCC 通过以下机制实现非阻塞的读操作:
隐藏字段:每行数据包含两个隐藏字段:
DB_TRX_ID:最后一次修改该数据的事务 ID。
DB_ROLL_PTR:回滚指针,指向该数据的历史版本(存储在 Undo Log 中)。
Undo Log:保存数据修改前的旧版本,形成一条版本链。
Read View:事务启动时生成的一致性视图,用于判断数据版本的可见性。
二、MVCC 的实现细节
1. 数据行的隐藏字段与版本链
示例数据行结构:
列数据
DB_TRX_ID
DB_ROLL_PTR
100
200
0x1234
版本链:
当数据被多次修改时,通过DB_ROLL_PTR指针形成一个链表,指向所有历史版本:
最新版本(事务ID=200) ← 旧版本(事务ID=150) ← 更旧版本(事务ID=100)2. Undo Log 的作用
INSERT Undo Log:记录插入操作,事务回滚时直接删除。
UPDATE/DELETE Undo Log:记录修改前的数据,用于:
事务回滚时恢复数据。
构建 MVCC 的版本链,供其他事务读取历史版本。
3. Read View 的生成与可见性判断
事务在第一次执行查询时生成 Read View,包含以下关键信息:
活跃事务列表(m_ids):当前未提交的事务 ID 集合。
高水位(up_limit_id):生成 Read View 时,已分配的最大事务 ID +1。
低水位(low_limit_id):生成 Read View 时,已提交的最小事务 ID。
判断数据版本是否可见的规则:
数据版本的事务 ID < 低水位:事务已提交,可见。
数据版本的事务 ID ≥ 高水位:事务在 Read View 之后启动,不可见。
数据版本的事务 ID ∈ 活跃事务列表:事务未提交,不可见。
其他情况:根据事务 ID 是否等于当前事务 ID 判断可见性。
三、MVCC 的工作流程
事务启动:分配唯一事务 ID(单调递增)。
查询数据:
根据 Read View 遍历版本链,找到对当前事务可见的最新版本。
若数据版本的事务 ID 不满足可见性规则,沿
DB_ROLL_PTR查找更旧版本。修改数据:
将修改前的数据写入 Undo Log,更新
DB_TRX_ID和DB_ROLL_PTR。提交或回滚:
提交:新版本数据对其他事务可见(取决于其 Read View)。
回滚:通过 Undo Log 恢复数据。
四、不同隔离级别的 MVCC 行为
1. 读已提交(Read Committed)
Read View 生成时机:每次执行查询时生成新的 Read View。
效果:总是读取已提交的最新数据,允许不可重复读。
2. 可重复读(Repeatable Read)
Read View 生成时机:事务第一次查询时生成,后续复用。
效果:同一事务内多次读取数据一致,避免不可重复读和幻读(通过快照读)。
五、MVCC 如何解决幻读?
快照读(Snapshot Read):
普通SELECT语句基于 Read View 读取历史版本,其他事务的插入操作不会影响当前事务的可见性。当前读(Current Read):
SELECT FOR UPDATE、UPDATE、DELETE等操作会读取最新数据并加锁,结合间隙锁(Gap Lock)阻止其他事务插入,彻底避免幻读。六、MVCC 的优缺点
优点
缺点
读操作无需加锁,高并发
需要维护多版本数据,增加存储开销
避免脏读、不可重复读
Undo Log 需要定期清理(Purge 线程)
支持非阻塞的一致性读
写操作仍需加锁(行锁、间隙锁)
七、总结
MVCC 通过版本链、Read View 和 Undo Log 实现非阻塞读,是 InnoDB 高并发能力的核心。
可重复读隔离级别下,MVCC 解决了不可重复读和快照读场景下的幻读问题,但写操作仍需间隙锁避免幻读。
MVCC 的本质是空间换时间,通过维护多版本数据提升并发性能。
33.数据库读写分离了解和实现?
一、读写分离的定义与原理
读写分离是一种数据库架构设计模式,通过将写操作(
INSERT/UPDATE/DELETE)和读操作(SELECT)分配到不同的数据库节点,提升系统性能和扩展性15。其核心依赖 主从复制(Master-Slave Replication) 实现数据同步:
主库(Master):处理所有写操作,并将变更记录到二进制日志(
binlog)中47。从库(Slave):通过
I/O 线程拉取主库的binlog,生成中继日志(relay log),再由SQL 线程重放日志实现数据同步47。数据一致性:主从同步存在延迟(毫秒级),可能短暂导致从库数据落后于主库45。
二、读写分离的实现方案
1. 中间件方案
功能:通过中间件(如 MyCat、MySQL Router)自动路由请求,写操作指向主库,读操作分发到从库36。
优势:对应用透明,无需修改业务代码,支持负载均衡和故障转移56。
示例配置:
-- MyCat 配置示例
<dataNode name="master" dataHost="masterHost" database="db1" />
<dataNode name="slave1" dataHost="slaveHost1" database="db1" />
<dataNode name="slave2" dataHost="slaveHost2" database="db1" />2. 应用层方案
多数据源配置:在应用代码中分别定义主库和从库数据源,通过注解或逻辑手动控制读写分离36。
负载均衡:结合连接池(如 HikariCP)实现从库的随机或轮询选择36。
示例代码:
// Spring Boot 多数据源配置 @Bean(name = "masterDataSource") public DataSource masterDataSource() { return DataSourceBuilder.create().url("jdbc:mysql://master:3306/db").build(); } @Bean(name = "slaveDataSource") public DataSource slaveDataSource() { return DataSourceBuilder.create().url("jdbc:mysql://slave:3306/db").build(); }三、读写分离的优缺点
优点
缺点
提升读性能:分散查询压力到多个从库15
主从延迟导致数据不一致风险45
提高扩展性:按需增加从库56
主库单点故障需额外高可用方案48
降低主库负载:写操作集中处理57
复杂性增加:需维护主从同步和中间件36
四、优化策略与最佳实践
减少主从延迟:
使用 半同步复制(
semi-sync):主库提交事务前需至少一个从库确认接收日志48。启用 并行复制(
parallel replication):从库多线程重放日志,提升同步效率48。高可用设计:
主备切换:通过
CHANGE MASTER命令动态切换主库,备库设为readonly避免误操作8。多主架构:多主库互相同步,提升写可用性(需解决冲突)5。
业务层适配:
关键读操作(如支付状态查询)强制走主库,规避延迟影响5。
分库分表:结合读写分离处理海量数据场景56。
五、适用场景
高并发读场景:如电商商品页、社交媒体动态列表5。
读写比例悬殊:读请求远高于写请求(如 8:2)5。
数据分析需求:从库承担报表查询,避免影响主库性能35。
六、主从同步核心流程
主库写入
binlog,记录数据变更78。从库
I/O 线程拉取binlog并写入relay log47。从库
SQL 线程解析relay log,重放 SQL 语句完成同步47。注:标准流程为串行同步,并行复制需额外配置48。
通过上述方案,读写分离可显著提升数据库性能和可用性,但需结合业务特点权衡一致性与延迟问题
34.你们一般是怎么分库的呢?
一、分库的常见方式
分库是将一个数据库拆分为多个数据库的过程,主要分为 垂直分库 和 水平分库 两种方式13。
1. 垂直分库
定义:按业务模块将表拆分到不同的数据库中13。
示例:
电商系统中,将用户表、订单表、商品表分别存储在不同的数据库13。
优点:
降低单库压力,提升性能13。
便于业务模块的独立扩展和维护13。
缺点:
跨库查询复杂,需通过应用层拼接数据14。
分布式事务处理难度增加14。
2. 水平分库
定义:按某种规则(如用户 ID、时间等)将同一张表的数据拆分到多个数据库中13。
示例:
按用户 ID 的哈希值将用户表数据分散到多个库中13。
优点:
解决单库数据量过大的问题,提升查询性能13。
支持海量数据存储13。
缺点:
数据迁移和扩容复杂13。
跨库查询和事务处理难度较大14。
二、分库的适用场景
数据量过大:单库数据量达到 TB 级别,影响查询性能13。
高并发访问:单库无法承受高并发读写压力13。
业务模块独立:不同业务模块需独立扩展和维护13。
三、分库的实现方案
基于中间件:
使用 MyCat、ShardingSphere 等中间件实现分库分表13。
优点:对应用透明,无需修改业务代码13。
缺点:增加系统复杂性,需维护中间件13。
应用层分片:
在应用代码中实现分库逻辑,如根据用户 ID 路由到不同数据库13。
优点:灵活可控,性能较高13。
缺点:开发成本高,维护复杂13。
四、分库的挑战与解决方案
跨库查询:
问题:无法直接使用
JOIN查询14。解决方案:通过应用层拼接数据,或使用全局索引14。
分布式事务:
问题:跨库事务难以保证一致性14。
解决方案:使用 XA 协议 或 TCC 模式 实现分布式事务14。
数据一致性迁移:
问题:分库过程中需保证数据一致性13。
解决方案:使用双写、数据同步工具(如 Canal)逐步迁移13。
五、总结
分库是解决大数据量和高并发问题的有效手段,但需根据业务特点选择合适的方案(垂直分库或水平分库),并解决跨库查询、分布式事务等挑战
35.水平分表有哪几种路由方式?
水平分表的路由方式及特点
水平分表通过特定规则将数据分散到多张表结构中,主要路由方式包括以下四种:
1. 范围路由(Range Partitioning)
定义:根据数据字段(如订单 ID、时间戳等)的范围划分表,每张表存储特定区间内的数据12。
优点:
分片规则明确,查询优化简单(如时间范围查询可直接定位目标表)16。
扩容方便,新增表后直接写入后续数据16。
缺点:
数据分布可能倾斜(如新数据集中在最新表,导致单表压力大)16。
扩展需重新划分数据,迁移成本高26。
适用场景:时间序列数据(如交易流水、日志记录)16。
2. 哈希路由(Hash Partitioning)
定义:通过哈希算法对分表键(如用户 ID)计算哈希值,再按分表数量取模确定目标表26。
优点:
数据分布均匀,负载均衡效果较好26。
扩展性较优(新增分表后数据迁移量相对可控)67。
缺点:
无法支持范围查询(数据分散导致需跨表聚合)6。
扩容时需重新哈希,可能引发数据大规模迁移26。
适用场景:数据分布均匀且无需频繁范围查询的场景(如用户基本信息表)26。
3. 中间表映射路由
定义:维护独立的中间表记录分表键与目标表的映射关系,每次操作前查询中间表获取路由信息16。
优点:
灵活性高,可自定义数据存储位置1。
支持动态调整分表规则1。
缺点:
中间表可能成为性能瓶颈(需频繁查询)16。
增加查询链路复杂度,影响响应时间1。
适用场景:分表规则频繁变更或需要高度定制化的场景1。
4. 业务路由(Business Key Partitioning)
定义:根据业务属性(如地区、用户类型)分配数据到特定表6。
优点:
符合业务逻辑,查询效率高(如按地区查询直接定位本地表)6。
便于数据隔离和管理(如不同业务单元独立维护)6。
缺点:
数据分布可能不均衡(如某地区数据量激增)6。
业务规则变更需重新分表,维护成本高6。
适用场景:具有明确业务分界的数据(如多租户系统、地域化服务)6。
总结
路由方式
核心特点
典型场景
范围路由
按区间划分,易优化查询但易倾斜12
时间序列数据(订单、日志)16
哈希路由
数据均匀分布,范围查询弱26
用户基础信息、分布式ID26
中间表映射
灵活但增加链路复杂度16
分表规则动态调整场景1
业务路由
贴合业务逻辑,维护成本高6
多租户、地域化服务6
根据业务需求和数据特性选择路由方式,可结合多种策略(如哈希+范围)优化性能与扩展性
36.百万级别以上的数据如何删除?
删除百万级别以上的数据时,直接执行
DELETE操作可能导致性能下降、锁表甚至数据库崩溃。以下是高效且安全的删除策略:1. 分批删除
方法:将数据分成较小的批次(如每次删除 10000 条),避免一次性删除大量数据12。
示例代码:
SET autocommit = 0; -- 关闭自动提交
START TRANSACTION;
DELETE FROM large_table WHERE condition LIMIT 10000;
COMMIT;
SET autocommit = 1; -- 恢复自动提交优点:减少锁表时间,降低对数据库性能的影响12。
缺点:需要多次执行,耗时较长15。
2. 使用事务
方法:将删除操作放在事务中,确保数据一致性,并在失败时回滚13。
示例代码:
START TRANSACTION;
DELETE FROM large_table WHERE condition LIMIT 10000;
COMMIT;
优点:保证数据一致性,避免部分删除导致数据丢失13。
缺点:事务过大可能占用过多资源,影响其他操作13。
3. 利用索引优化
方法:确保删除条件使用索引,加速数据定位14。
示例:
DELETE FROM large_table WHERE indexed_column = value LIMIT 10000;优点:提升删除效率,减少全表扫描14。
缺点:索引维护可能增加删除操作的开销4。
4. 数据归档与分区
方法:将需要保留的数据归档到另一张表或数据库,然后使用
TRUNCATE或删除分区6。示例:
-- 归档数据
INSERT INTO archive_table SELECT * FROM large_table WHERE condition;
-- 删除分区 ALTER TABLE large_table TRUNCATE PARTITION partition_name;优点:高效删除大量数据,避免逐条删除的开销6。
缺点:需提前规划分区或归档策略6。
5. 临时表法
方法:将需要保留的数据复制到临时表,删除原表后重命名临时表5。
示例:
CREATE TABLE temp_table AS SELECT * FROM large_table WHERE condition;
DROP TABLE large_table;
RENAME TABLE temp_table TO large_table;优点:快速删除大量数据,避免锁表5。
缺点:需确保临时表结构与原表一致,且数据迁移可能耗时5。
6. 备份与恢复
方法:在执行删除操作前备份数据,确保数据安全36。
工具:使用
mysqldump或数据库管理工具进行备份36。优点:防止误删或操作失败导致数据丢失36。
缺点:备份过程可能占用较多时间和资源36。
总结
方法
适用场景
优点
缺点
分批删除
数据量极大,需避免锁表12
减少锁表时间,降低性能影响12
耗时较长,需多次执行15
使用事务
需保证数据一致性13
确保数据一致性,支持回滚13
事务过大可能占用过多资源13
利用索引优化
删除条件可索引化14
提升删除效率,减少全表扫描14
索引维护增加开销4
数据归档与分区
需高效删除大量数据6
避免逐条删除,高效清理6
需提前规划分区或归档策略6
临时表法
需快速删除大量数据5
快速删除,避免锁表5
数据迁移可能耗时5
备份与恢复
防止误删或操作失败36
确保数据安全36
备份过程占用资源36
根据业务需求和数据规模,选择合适的方法,并结合备份策略确保数据安全
37.百万千万级大表如何添加字段?
针对百万级或千万级大表添加字段的场景,需平衡性能、锁表风险与数据一致性,以下是综合实践方法:
一、在线修改工具(推荐)
使用
pt-online-schema-change
原理:通过创建影子表(与原表结构一致)、同步数据并增量更新,最终替换原表,实现无锁表操作16。
示例命令:
pt-online-schema-change --alter "ADD COLUMN new_column INT" D=database,t=table_name --execute优点:
支持在线操作,不影响读写16。
自动处理数据同步期间的增量变更6。
限制:需安装 Percona Toolkit,且部分场景(如外键约束)需额外配置6。
二、临时表迁移法
步骤:
创建新表:复制原表结构并添加新字段:
CREATE TABLE new_table LIKE old_table; ALTER TABLE new_table ADD COLUMN new_column INT DEFAULT 0;数据迁移:分批导入数据并同步增量变更(如通过时间戳过滤新增数据)24。
切换表:删除原表并重命名新表:
DROP TABLE old_table; RENAME TABLE new_table TO old_table;适用场景:可接受短暂停机或低峰期操作47。
三、MySQL 原生在线 DDL
条件:
MySQL 8.0+ 支持多数 DDL 操作的在线执行(如
ALGORITHM=INPLACE)1。示例:
ALTER TABLE table_name ADD COLUMN new_column INT, ALGORITHM=INPLACE, LOCK=NONE;优点:无需第三方工具,操作简单1。
限制:
部分操作(如修改字段类型)仍需锁表1。
需评估表大小与存储引擎(如 InnoDB 支持较好)8。
四、分区表优化
方法:若表已分区,可逐个分区添加字段,减少单次操作影响1。
示例:
ALTER TABLE partitioned_table REORGANIZE PARTITION p1 INTO (PARTITION p1 VALUES LESS THAN (1000) ENGINE=InnoDB);适用场景:数据按时间或范围分区的场景16。
操作注意事项
备份数据:执行前通过
mysqldump或快照备份,防止意外丢失36。监控资源:关注 CPU、I/O 和锁等待情况,避免影响线上业务68。
测试验证:在非生产环境模拟操作,验证执行时间与数据一致性57。
方法对比
方法
适用场景
优点
缺点
pt-online-schema-change高并发在线业务,需最小化停机16
无锁表,支持增量同步6
需额外工具,复杂场景需调试6
临时表迁移法
允许停机或低峰期操作47
数据可控,兼容性强47
迁移耗时,需处理增量数据47
MySQL 在线 DDL
小字段添加,MySQL 8.0+ 环境1
原生支持,操作简单1
大表可能仍阻塞写入18
分区表优化
已分区表16
减少单次操作压力16
需提前规划分区策略16
根据业务容忍度、数据规模及数据库版本选择最佳方案,优先推荐
pt-online-schema-change或临时表迁移法
38.MySQL数据库cpu飙升的话,要怎么处理呢?
当MySQL数据库CPU使用率异常升高时,需快速定位问题并采取相应措施。以下是详细的排查与优化步骤:
一、定位问题
查看系统资源占用
使用
top或htop命令查看 MySQL 进程的 CPU 占用率,确认是否为 MySQL 导致13。示例命令:
top -c分析当前会话
在 MySQL 中执行
SHOW PROCESSLIST,查看当前运行的 SQL 语句,找出耗时或资源占用高的查询14。示例命令:
SHOW FULL PROCESSLIST;检查慢查询日志
开启慢查询日志,记录执行时间超过阈值的 SQL 语句,分析潜在问题35。
示例命令:
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;二、优化SQL查询
优化慢查询
使用
EXPLAIN分析慢查询的执行计划,检查是否存在全表扫描、未使用索引等问题35。示例命令:
EXPLAIN SELECT * FROM table WHERE condition;添加或调整索引
根据查询条件为常用字段添加索引,避免全表扫描56。
示例命令:
CREATE INDEX idx_column ON table(column);重写复杂查询
将子查询转换为连接查询,减少嵌套层级,提升执行效率35。
示例:
-- 原查询 SELECT * FROM orders WHERE customer_id IN (SELECT customer_id FROM customers WHERE country = 'China'); -- 优化后 SELECT orders.* FROM orders JOIN customers ON orders.customer_id = customers.customer_id WHERE customers.country = 'China';三、调整MySQL配置
优化缓冲区大小
调整
innodb_buffer_pool_size,确保缓冲池能够容纳常用数据,减少磁盘 I/O15。示例配置:
innodb_buffer_pool_size = 2G限制连接数
设置
max_connections和wait_timeout,防止过多连接占用资源56。示例配置:
max_connections = 200 wait_timeout = 60启用线程缓存
配置
thread_cache_size,减少线程创建和销毁的开销56。示例配置:
thread_cache_size = 8四、其他优化措施
定期维护数据库
执行
OPTIMIZE TABLE优化表结构,清理冗余数据56。示例命令:
OPTIMIZE TABLE table_name;使用缓存技术
将热点数据缓存到 Redis 或 Memcached,减少数据库访问频率67。
分库分表
对于数据量过大的表,采用分库分表策略,减轻单表压力57。
升级硬件资源
增加 CPU 核心数或内存容量,提升服务器性能15。
总结
步骤
具体操作
参考
定位问题
使用
top、SHOW PROCESSLIST等工具1313
优化SQL查询
分析执行计划、添加索引、重写查询35
35
调整MySQL配置
优化缓冲区、限制连接数、启用线程缓存56
56
其他优化措施
定期维护、使用缓存、分库分表56
56
通过以上方法,可有效解决 MySQL 数据库 CPU 飙升的问题,确保系统稳定运行15。
三、RabbitMQ
1.rabbitmq 的使用场景有哪些?
RabbitMQ 的使用场景非常广泛,主要应用于分布式系统中,以下是其常见的应用场景:
1. 异步处理
场景:用户注册后需要发送邮件或短信通知,传统方式会阻塞主流程,影响用户体验。
解决方案:将非关键任务(如发送邮件、短信)放入消息队列,主流程快速响应,后台异步处理34。
2. 系统解耦
场景:微服务架构中,服务之间直接调用会导致高耦合,增加系统复杂性。
解决方案:通过 RabbitMQ 将服务间的调用解耦,服务 A 通过队列通知服务 B 执行操作,减少依赖24。
3. 流量削峰
场景:高并发场景(如秒杀活动)下,突发流量可能导致服务过载。
解决方案:将请求写入消息队列,后端根据处理能力逐步消费,避免服务崩溃24。
4. 日志处理
场景:多系统日志写入可能阻塞主业务。
解决方案:将日志统一发送到消息队列,由消费者集中处理(如存储、分析),避免影响主业务25。
5. 任务队列
场景:长时间任务(如视频转码)需要排队执行。
解决方案:将任务放入消息队列,支持任务优先级、重试等机制,确保任务有序执行25。
6. 跨系统/跨语言通信
场景:异构系统间需要通信。
解决方案:RabbitMQ 支持多种协议(如 AMQP、STOMP)及多语言客户端,便于不同系统间通信26。
7. 应用解耦
场景:电商系统中,订单系统与库存系统高耦合,库存系统故障会导致订单失败。
解决方案:订单系统将消息写入队列,库存系统订阅消息,即使库存系统故障,消息也不会丢失45。
8. 消息广播
场景:需要将消息广播给多个消费者。
解决方案:通过 RabbitMQ 的交换机机制,将消息路由到多个队列,实现消息广播8。
9. 延迟消息
场景:需要延迟执行某些任务(如订单超时取消)。
解决方案:通过 RabbitMQ 的延迟队列或插件实现延迟消息处理8。
10. 分布式事务
场景:分布式系统中需要保证事务一致性。
解决方案:通过消息队列实现最终一致性,确保事务的可靠执行8。
RabbitMQ 的灵活性和高性能使其成为分布式系统中不可或缺的工具,广泛应用于异步处理、解耦、削峰等场景
2.rabbitmq 有哪些重要的角色?
RabbitMQ 中有几个重要的角色,负责管理消息的流动、路由、存储和处理。以下是这些角色的详细介绍:
1. 生产者 (Producer)
定义:生产者是发送消息到 RabbitMQ 的应用程序或进程。生产者创建消息并将其发送到 RabbitMQ 中的交换机。
职责:
生产者不会直接将消息发送到队列,而是通过交换机 (Exchange) 发送消息。
生产者负责确定消息的内容和路由键 (Routing Key),以及选择目标交换机。
生产者可以选择是否使用消息的持久化特性12。
2. 消费者 (Consumer)
定义:消费者是接收和处理 RabbitMQ 队列中消息的应用程序或进程。
职责:
消费者从队列中获取消息,处理消息,并且通常会在处理完消息后发送消息确认 (acknowledgment) 来告知 RabbitMQ 消息已被成功消费12。
3. 代理 (Broker)
定义:代理就是 RabbitMQ 消息队列本身,用于扮演“快递”的角色,本身不生产消息,只是负责消息的存储和转发。
职责:
代理负责管理消息的存储、路由和传递。
代理包括交换机 (Exchange) 和队列 (Queue) 等组件24。
4. 交换机 (Exchange)
定义:交换机是接收生产者发送的消息并将这些消息路由到队列的组件。
职责:
交换机根据路由键 (Routing Key) 和绑定规则 (Binding) 将消息路由到一个或多个队列。
交换机有四种类型:直连交换机 (Direct)、扇形交换机 (Fanout)、主题交换机 (Topic) 和头交换机 (Headers)14。
5. 队列 (Queue)
定义:队列是消息的暂存地,用于存储消息直到被消费者处理。
职责:
队列是消息的终点,消息在队列中等待消费者取走。
队列支持先进先出 (FIFO) 的消息处理机制47。
6. 绑定 (Binding)
定义:绑定是用于将交换机和队列关联起来的规则。
职责:
绑定定义了交换机如何将消息路由到队列。
绑定包括绑定键 (Binding Key),用于匹配路由键 (Routing Key)47。
7. 虚拟主机 (Virtual Host)
定义:虚拟主机是 RabbitMQ 中的逻辑隔离单元,用于隔离不同的应用或环境。
职责:
每个虚拟主机拥有独立的队列、交换机和绑定。
虚拟主机提供身份认证和权限管理功能38。
这些角色共同协作,确保 RabbitMQ 能够高效地管理消息的流动和处理
3.rabbitmq 有哪些重要的组件?
RabbitMQ 是一个功能强大的消息代理系统,其核心组件共同协作,确保消息的可靠传递和处理。以下是 RabbitMQ 的重要组件:
1. 交换机 (Exchange)
作用:接收生产者发送的消息,并根据路由规则将消息分发到队列。
类型:
直连交换机 (Direct Exchange):根据路由键 (Routing Key) 精确匹配队列。
扇形交换机 (Fanout Exchange):将消息广播到所有绑定的队列,忽略路由键。
主题交换机 (Topic Exchange):根据路由键的模式匹配队列(支持通配符)。
头交换机 (Headers Exchange):根据消息头属性匹配队列,忽略路由键12。
2. 队列 (Queue)
作用:存储消息,直到消费者处理。
特性:
支持先进先出 (FIFO) 的消息处理。
可以配置持久化,确保消息在 RabbitMQ 重启后不丢失。
支持优先级队列,允许高优先级消息优先处理12。
3. 绑定 (Binding)
作用:定义交换机和队列之间的关联规则。
特性:
绑定包括绑定键 (Binding Key),用于匹配路由键 (Routing Key)。
绑定规则决定了交换机如何将消息路由到队列12。
4. 虚拟主机 (Virtual Host)
作用:提供逻辑隔离,允许多个应用或环境共享同一个 RabbitMQ 实例。
特性:
每个虚拟主机拥有独立的队列、交换机和绑定。
支持身份认证和权限管理,确保安全性13。
5. 信道 (Channel)
作用:在客户端和 RabbitMQ 之间建立轻量级的通信连接。
特性:
信道复用 TCP 连接,减少资源消耗。
支持多线程并发操作,提高性能12。
6. 消息 (Message)
作用:传递的数据单元。
结构:
消息体 (Payload):实际传递的数据。
属性 (Properties):包括路由键、优先级、持久化标志等。
头信息 (Headers):用于头交换机的匹配12。
7. 生产者 (Producer)
作用:创建并发送消息到 RabbitMQ 的应用程序。
职责:
指定消息的路由键和目标交换机。
选择是否启用消息持久化12。
8. 消费者 (Consumer)
作用:从队列中接收并处理消息的应用程序。
职责:
监听队列并获取消息。
处理消息后发送确认 (ack) 或拒绝 (nack)12。
9. 插件 (Plugins)
作用:扩展 RabbitMQ 的功能。
常见插件:
管理插件:提供 Web 管理界面。
延迟消息插件:支持延迟消息处理。
认证插件:集成 LDAP、OAuth 等认证方式38。
10. 集群 (Cluster)
作用:提供高可用性和负载均衡。
特性:
多个 RabbitMQ 节点组成集群,共享队列和交换机。
支持镜像队列,确保消息在节点故障时不丢失38。
11. 持久化 (Persistence)
作用:确保消息在 RabbitMQ 重启后不丢失。
实现方式:
消息持久化:将消息存储到磁盘。
队列持久化:队列元数据存储到磁盘12。
12. 死信队列 (Dead Letter Queue, DLX)
作用:处理无法被正常消费的消息。
场景:
消息被拒绝且未重新入队。
消息在队列中过期。
队列达到最大长度38。
这些组件共同构成了 RabbitMQ 的核心架构,使其能够高效、可靠地处理消息传递任务
4.rabbitmq 中 vhost 的作用是什么?
在 RabbitMQ 中,虚拟主机 (Virtual Host, vhost) 是一种逻辑隔离机制,用于在同一 RabbitMQ 实例中创建多个独立的环境。以下是 vhost 的主要作用:
1. 资源隔离
每个 vhost 相当于一个独立的命名空间,拥有自己的队列、交换机、绑定等资源,不同 vhost 之间的资源完全隔离,互不干扰12。
这种隔离机制避免了不同应用或团队之间的资源冲突,确保各自环境的独立性13。
2. 权限控制
vhost 提供了细粒度的权限管理,可以为每个 vhost 分配不同的用户和角色,并设置访问权限(如只读、只写或读写)14。
用户只能访问其被授权的 vhost,增强了系统的安全性25。
3. 多租户支持
vhost 支持多租户架构,允许在同一 RabbitMQ 实例中为不同租户提供独立的消息队列环境,确保数据的私密性和安全性37。
4. 环境分离
可以在同一 RabbitMQ 服务器上为开发、测试和生产环境分别配置不同的 vhost,方便管理并确保各环境的独立性37。
5. 灵活性与管理
vhost 可以根据需要动态创建和删除,而不会影响其他 vhost,使得资源管理更加灵活14。
每个 vhost 的资源可以独立监控和优化,便于管理和维护7。
6. 默认 vhost
RabbitMQ 提供了一个默认的 vhost,名为
/,用户可以在连接时指定 vhost,未指定时默认使用/58。7. 连接隔离
客户端连接 RabbitMQ 时必须指定 vhost,确保操作仅限于该虚拟主机内,进一步增强了系统的隔离性和安全性27。
通过 vhost,RabbitMQ 实现了资源、权限和环境的逻辑隔离,为多应用、多团队或多租户场景提供了高效且安全的解决方案
5.rabbitmq 的消息是怎么发送的?
RabbitMQ 的消息发送过程涉及多个关键组件和步骤,以下是消息发送的详细流程:
1. 生产者创建消息
生产者(Producer)是发送消息的应用程序,负责创建消息并指定消息的内容、路由键(Routing Key)和其他属性(如优先级、持久化标志等)28。
2. 消息发送到交换机
生产者将消息发送到交换机(Exchange),而不是直接发送到队列。交换机是消息的路由中心,负责根据规则将消息分发到合适的队列12。
3. 交换机根据路由规则分发消息
交换机会根据消息的路由键(Routing Key)和绑定规则(Binding)将消息路由到一个或多个队列。交换机的类型决定了路由规则的具体形式,例如:
直连交换机 (Direct Exchange):根据路由键精确匹配队列14。
扇形交换机 (Fanout Exchange):将消息广播到所有绑定的队列,忽略路由键47。
主题交换机 (Topic Exchange):根据路由键的模式匹配队列(支持通配符)14。
头交换机 (Headers Exchange):根据消息头属性匹配队列,忽略路由键14。
4. 消息进入队列
交换机将消息路由到队列(Queue),队列是消息的暂存地,用于存储消息直到被消费者处理。队列支持先进先出(FIFO)的消息处理机制12。
5. 消费者接收消息
消费者(Consumer)从队列中获取消息并进行处理。消费者可以主动拉取消息,也可以通过订阅队列的方式接收消息24。
6. 消息确认
消费者处理完消息后,会向 RabbitMQ 发送确认(acknowledgment),告知消息已被成功消费。如果消费者未发送确认,RabbitMQ 会认为消息未被正确处理,可能会重新将消息放入队列28。
7. 消息持久化(可选)
如果消息或队列配置了持久化,消息会被存储到磁盘,确保在 RabbitMQ 重启后消息不会丢失14。
8. 消息路由失败处理
如果消息无法被路由到任何队列(例如没有匹配的绑定规则),消息可能会被丢弃或进入死信队列(Dead Letter Queue, DLX),具体行为取决于配置48。
通过以上步骤,RabbitMQ 实现了消息的可靠传递和处理
6.rabbitmq 怎么保证消息的稳定性?
RabbitMQ 通过多种机制来确保消息传递的稳定性和可靠性,以下是其主要策略:
1. 消息持久化
消息持久化:将消息存储到磁盘,确保在 RabbitMQ 服务器重启后消息不会丢失。可以通过设置消息的
delivery_mode为2来启用持久化12。队列持久化:将队列的元数据存储到磁盘,确保队列在 RabbitMQ 重启后仍然存在12。
2. 消息确认机制
生产者确认 (Publisher Confirms):生产者发送消息后,RabbitMQ 会返回确认,告知消息是否成功到达交换机或队列。如果未收到确认,生产者可以重发消息12。
消费者确认 (Consumer Acknowledgments):消费者处理完消息后,必须向 RabbitMQ 发送确认(ack),告知消息已被成功消费。如果未收到确认,RabbitMQ 会认为消息未被正确处理,可能会重新将消息放入队列12。
3. 死信队列 (Dead Letter Queue, DLX)
无法被正常消费的消息(如被拒绝、过期或队列达到最大长度)会被路由到死信队列,便于后续处理和分析12。
4. 高可用性 (HA)
镜像队列 (Mirrored Queues):将队列复制到多个 RabbitMQ 节点,确保在某个节点故障时,消息仍然可用12。
集群 (Cluster):多个 RabbitMQ 节点组成集群,共享队列和交换机,提供高可用性和负载均衡12。
5. 消息重试机制
如果消息处理失败,可以通过配置重试机制(如重试次数、重试间隔)来重新处理消息,确保消息最终被成功消费12。
6. 流量控制
RabbitMQ 支持流量控制机制,防止生产者发送过多消息导致系统过载。可以通过配置
prefetch count限制消费者同时处理的消息数量12。7. 事务机制
RabbitMQ 支持事务机制,确保一组操作(如发送多条消息)要么全部成功,要么全部失败。但事务机制会降低性能,通常推荐使用生产者确认机制12。
8. 监控与告警
通过 RabbitMQ 的管理插件或第三方监控工具,实时监控消息队列的状态(如队列长度、消息积压等),并在异常时触发告警,便于及时处理12。
9. 消息 TTL (Time-To-Live)
可以为消息或队列设置生存时间(TTL),确保过期的消息被自动删除或路由到死信队列,避免无效消息占用资源12。
10. 备份与恢复
定期备份 RabbitMQ 的数据(如消息、队列、交换机等),确保在灾难发生时能够快速恢复系统12。
通过以上机制,RabbitMQ 能够有效地保证消息传递的稳定性和可靠性,满足高并发、高可用的业务需求
7.rabbitmq要保证消息持久化成功的条件有哪些?
要确保 RabbitMQ 消息持久化成功(以 Java 为例),需从消息属性、队列/交换机声明、生产者确认和消费者确认四个核心维度配置,并确保服务端环境可靠。以下是具体实现条件及代码示例:
1. 消息持久化设置
在生产者发送消息时,必须将消息的
deliveryMode属性设置为2(持久化模式),否则消息默认存储在内存中,服务重启后会丢失。
import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Channel; // 发送持久化消息 AMQP.BasicProperties props = new AMQP.BasicProperties.Builder() .deliveryMode(2) // 设置为持久化消息 .build(); channel.basicPublish( "exchange_name", "routing_key", props, "message_body".getBytes() );2. 队列与交换机持久化
声明队列和交换机时需设置
durable=true,否则它们会在 RabbitMQ 重启后丢失。
// 声明持久化队列 boolean durable = true; channel.queueDeclare("queue_name", durable, false, false, null); // 声明持久化交换机 channel.exchangeDeclare("exchange_name", "direct", durable);3. 生产者启用确认机制
通过
ConfirmListener确保消息成功到达 RabbitMQ 服务器,失败时重试或记录日志。
import com.rabbitmq.client.ConfirmListener; channel.confirmSelect(); // 开启发布确认 channel.addConfirmListener(new ConfirmListener() { @Override public void handleAck(long deliveryTag, boolean multiple) { // 消息成功持久化到磁盘 System.out.println("Confirmed: " + deliveryTag); } @Override public void handleNack(long deliveryTag, boolean multiple) { // 消息未持久化,需重试 System.err.println("Unconfirmed: " + deliveryTag); } });4. 消费者手动确认
消费者必须关闭自动确认(
autoAck=false),处理完消息后手动发送basicAck。
import com.rabbitmq.client.DefaultConsumer; import com.rabbitmq.client.Envelope; boolean autoAck = false; channel.basicConsume("queue_name", autoAck, new DefaultConsumer(channel) { @Override public void handleDelivery( String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body ) throws IOException { try { // 处理消息逻辑 System.out.println("Received: " + new String(body)); // 手动确认消息 channel.basicAck(envelope.getDeliveryTag(), false); } catch (Exception e) { // 处理失败时拒绝消息(可重新入队) channel.basicNack(envelope.getDeliveryTag(), false, true); } } });5. 服务端必要配置
数据目录持久化:确保 RabbitMQ 的数据目录(如
/var/lib/rabbitmq)挂载到可靠存储(如 SSD)。镜像队列:在集群中配置镜像队列,防止单节点故障。
bashCopy Code
rabbitmqctl set_policy ha-all "^persistent_queue" '{"ha-mode":"all"}' # 匹配队列名规则监控:通过管理插件监控消息堆积和持久化状态。
完整条件总结
维度
必要条件
消息
deliveryMode=2,且必须发送到持久化队列队列
声明时设置
durable=true交换机
声明时设置
durable=true,且绑定到持久化队列生产者
开启
confirm模式,监听确认事件,失败重试消费者
关闭
autoAck,处理成功后发送basicAck服务端
数据目录持久化存储,集群中启用镜像队列
注意事项
队列必须预先存在:若队列未声明为持久化,即使消息标记为持久化,重启后队列(及消息)仍会丢失。
性能权衡:持久化会降低吞吐量,需根据业务场景选择。
异常处理:生产者需实现重试逻辑,消费者需处理
Nack和死信队列。通过以上配置,可确保 RabbitMQ 消息在服务端持久化,并在生产-消费全链路中保持可靠性。
1507

被折叠的 条评论
为什么被折叠?



