问题起源如下:
<img src=”https://cdn.nlark.com/yuque/0/2023/png/92791/1687946200989-e5852be8-69f0-49a9-9b40-f3960db36c2d.png” width=”50%”>
问题链接:https://www.nowcoder.com/discuss/493178141461041152
答案解析
1. 解释脏读 / 不可反复读 / 幻读
- 脏读:指一个事务读取到了另一个事务为提交保留的数据,之后此事务进行了回滚操作,从而导致第一个事务读取了一个不存在的脏数据。
- 不可反复读:在同一个事务中,同一个查问在不同的工夫失去了不同的后果。例如事务在 T1 读取到了某一行数据,在 T2 工夫从新读取这一行时候,这一行的数据曾经产生批改,所以再次读取时失去了一个和 T1 查问时不同的后果。
-
幻读:同一个查问在不同工夫失去了不同的后果,这就是事务中的幻读问题。例如,一个 SELECT 被执行了两次,然而第二次返回了第一次没有返回的一行,那么这一行就是一个“幻像”行。
不可反复读和幻读的区别
- 不可反复读的重点是批改:在同一事务中,同样的条件,第一次读的数据和第二次读的数据不一样。(因为两头有其余事务提交了批改);
-
幻读的重点在于新增或者删除:在同一事务中,同样的条件,,第一次和第二次读出来的记录数不一样。(因为两头有其余事务提交了插入 / 删除)。
2. 索引生效的场景有哪些?
常见的索引生效场景有以下这些:
- 未遵循最左匹配准则
- 应用列运算
- 应用函数办法
- 类型转换
- 应用 is not null
- 谬误的含糊匹配,应用右 % 开始查问。
具体内容请参考:https://www.javacn.site/interview/mysql/indexinvalid.html
3.Explain 执行打算用过吗?
Explain 是用来剖析 SQL 的执行状况的,explain 应用如下,只须要在查问的 SQL 后面增加上 explain 关键字即可,如下图所示:
而以上查问后果的列中,咱们最次要察看 key 这一列,key 这一列示意理论应用的索引,如果为 NULL 则示意未应用索引,反之则应用了索引。
以上所有后果列阐明如下:
- id — 抉择标识符,id 越大优先级越高,越先被执行;
- select_type — 示意查问的类型;
- table — 输入后果集的表;
- partitions — 匹配的分区;
- type — 示意表的查问类型;
- possible_keys — 示意查问时,可能应用的索引;
- key — 示意理论应用的索引;
- key_len — 索引字段的长度;
- ref— 列与索引的比拟;
- rows — 大略估算的行数;
- filtered — 按表条件过滤的行百分比;
- Extra — 执行状况的形容和阐明。
4.Type 字段有什么信息?
Explain 执行打算中最重要的就是 type 字段,type 蕴含的信息如下:
- all — 扫描全表数据;
- index — 遍历索引;
- range — 索引范畴查找;
- index_subquery — 在子查问中应用 ref;
- unique_subquery — 在子查问中应用 eq_ref;
- ref_or_null — 对 null 进行索引的优化的 ref;
- fulltext — 应用全文索引;
- ref — 应用非惟一索引查找数据;
- eq_ref — 在 join 查问中应用主键或惟一索引关联;
- const — 将一个主键搁置到 where 前面作为条件查问,MySQL 优化器就能把这次查问优化转化为一个常量,如何转化以及何时转化,这个取决于优化器,这个比 eq_ref 效率高一点。
5.binlog 和 redolog 区别?
binlog(二进制日志)和 redolog(重做日志)都是 MySQL 中的重要日志,但二者存在以下不同。
-
binlog(二进制日志):
- binlog 是 MySQL 的服务器层日志,用于记录对数据库执行的所有批改操作,包含插入、更新和删除等。它以二进制格局记录,能够被用于数据复制、复原和故障复原等操作。
- binlog 记录了逻辑上的操作,即执行的 SQL 语句或语句的逻辑示意。
- binlog 是在事务提交后才会生成,因而它是长久化的。
- binlog 能够被配置为不同的格局,包含基于语句的复制(statement-based replication)、基于行的复制(row-based replication)和混合复制(mixed replication)。
-
redolog(重做日志):
- redolog 是 MySQL 的存储引擎层日志,用于确保数据库的事务持久性和解体恢复能力。
- redolog 记录了物理层面的批改操作,即对数据页的物理批改。它次要用于保障事务的持久性,确保在产生解体时,曾经提交的事务对数据库的批改可能被复原。
- redolog 是循环写入的,它的数据写入到磁盘上的文件中。在产生解体时,通过 redolog 的重做操作,能够将数据库复原到解体前的统一状态。
- redolog 是在事务执行期间一直写入的,以确保在零碎解体时能够重做所有已提交的事务。
小结:binlog 用于记录逻辑层面的操作,能够用于数据复制和复原,而 redolog 用于记录物理层面的操作,确保事务的持久性和解体复原。它们在性能和应用上有一些不同,但都是 MySQL 中重要的日志机制。
6.Redis 根本数据类型
Redis 罕用的数据类型有 5 种:String 字符串类型、List 列表类型、Hash 哈希表类型、Set 汇合类型、Sorted Set 有序汇合类型,如下图所示:这 5 种罕用类型的用处如下:
- String:字符串类型,常见应用场景是:存储 Session 信息、存储缓存信息(如详情页的缓存)、存储整数信息,可应用 incr 实现整数 +1,和应用 decr 实现整数 -1;
- List:列表类型,常见应用场景是:实现简略的音讯队列、存储某项列表数据;
- Hash:哈希表类型,常见应用场景是:存储 Session 信息、存储商品的购物车,购物车非常适合用哈希字典示意,应用人员惟一编号作为字典的 key,value 值能够存储商品的 id 和数量等信息、存储详情页信息;
- Set:汇合类型,是一个无序并惟一的键值汇合,它的常见应用场景是:关注性能,比方关注我的人和我关注的人,应用汇合存储,能够保障人员不会反复;
- Sorted Set:有序汇合类型,相比于 Set 汇合类型多了一个排序属性 score(分值),它的常见应用场景是:能够用来存储排名信息、关注列表性能,这样就能够依据关注实现排序展现了。
更多内容请参考:https://www.javacn.site/interview/redis/types.html
7. 有序汇合底层实现数据结构?
有序汇合是由 ziplist (压缩列表) 或 skiplist (跳跃表) 组成的。
- 压缩列表 ziplist 实质上就是一个字节数组,是 Redis 为了节约内存而设计的一种线性数据结构,能够蕴含多个元素,每个元素能够是一个字节数组或一个整数。
- 跳跃表 skiplist 是一种有序数据结构,它通过在每个节点中维持多个指向其余节点的指针,从而达到快速访问节点的目标。跳跃表反对均匀 O(logN)、最坏 O(N) 复杂度的节点查找,还能够通过程序性操作来批量解决节点。
当数据比拟少时,有 序汇合是压缩列表 ziplist 存储的(反之则为跳跃表 skiplist 存储),应用压缩列表存储必满足以下两个条件:
- 有序汇合保留的元素个数要小于 128 个;
- 有序汇合保留的所有元素成员的长度都必须小于 64 字节。
如果不能满足以上两个条件中的任意一个,有序汇合将会应用跳跃表 skiplist 构造进行存储。
8. 跳表插入数据的过程?
在开始讲跳跃表的增加流程之前,必须先搞懂一个概念:节点的随机层数。
所谓的随机层数指的是每次增加节点之前,会学生成以后节点的随机层数,依据生成的随机层数来决定将以后节点存在几层链表中。
为什么要这样设计呢?
这样设计的目标是为了保障 Redis 的执行效率。
为什么要生成随机层数,而不是制订一个固定的规定,比方下层节点是上层逾越两个节点的链表组成,如下图所示:
如果制订了规定,那么就须要在增加或删除时,为了满足其规定,做额定的解决,比方增加了一个新节点,如下图所示:
这样就不满足制订的下层节点逾越上层两个节点的规定了,就须要额定的调整下层中的所有节点,这样程序的效率就升高了,所以应用随机层数,不强制制订规定,这样就不须要进行额定的操作,从而也就不会占用服务执行的工夫了。
增加流程
Redis 中跳跃表的增加流程如下图所示:
- 第一个元素增加到最底层的有序链表中(最底层存储了所有元素数据)。
- 第二个元素生成的随机层数是 2,所以再减少 1 层,并将此元素存储在第 1 层和最低层。
- 第三个元素生成的随机层数是 4,所以再减少 2 层,整个跳跃表变成了 4 层,将此元素保留到所有层中。
- 第四个元素生成的随机层数是 1,所以把它按程序保留到最初一层中即可。
其余新增节点以此类推。
更多内容请参考:https://www.javacn.site/company/redis_skiplist.html
9. 线程池有哪些参数?
线程池(ThreadPoolExecutor)有 7 个参数,这 7 个参数的含意如下:
- 第 1 个参数:corePoolSize 示意线程池的常驻外围线程数。如果设置为 0,则示意在没有任何工作时,销毁线程池;如果大于 0,即便没有工作时也会保障线程池的线程数量等于此值。但须要留神,此值如果设置的比拟小,则会频繁的创立和销毁线程(创立和销毁的起因会在本课时的下半局部讲到);如果设置的比拟大,则会节约系统资源,所以开发者须要依据本人的理论业务来调整此值;
- 第 2 个参数:maximumPoolSize 示意线程池在工作最多时,最大能够创立的线程数。官网规定此值必须大于 0,也必须大于等于 corePoolSize,此值只有在工作比拟多,且不能寄存在工作队列时,才会用到;
- 第 3 个参数:keepAliveTime 示意线程的存活工夫,当线程池闲暇时并且超过了此工夫,多余的线程就会销毁,直到线程池中的线程数量销毁的等于 corePoolSize 为止,如果 maximumPoolSize 等于 corePoolSize,那么线程池在闲暇的时候也不会销毁任何线程;
- 第 4 个参数:unit 示意存活工夫的单位,它是配合 keepAliveTime 参数独特应用的;
- 第 5 个参数:workQueue 示意线程池执行的工作队列,当线程池的所有线程都在解决工作时,如果来了新工作就会缓存到此工作队列中排队期待执行;
- 第 6 个参数:threadFactory 示意线程的创立工厂,此参数个别用的比拟少,咱们通常在创立线程池时不指定此参数,它会应用默认的线程创立工厂的办法来创立线程;
- 第 7 个参数:RejectedExecutionHandler 示意指定线程池的回绝策略,当线程池的工作曾经在缓存队列 workQueue 中存储满了之后,并且不能创立新的线程来执行此工作时,就会用到此回绝策略,它属于一种限流爱护的机制。
更多内容请参考:https://juejin.cn/post/7072921565079273480
10. 回绝策略有哪些?
线程池的回绝策略默认有以下 4 种:
- AbortPolicy:停止策略,线程池会抛出异样并中止执行此工作;
- CallerRunsPolicy:把工作交给增加此工作的(main)线程来执行;
- DiscardPolicy:疏忽此工作,疏忽最新的一个工作;
- DiscardOldestPolicy:疏忽最早的工作,最先退出队列的工作。
默认的回绝策略为 AbortPolicy 停止策略。当然除了 JDK 内置的 4 种回绝策略之外,用户还能够自定义回绝策略,通过实现 new RejectedExecutionHandler,并重写 rejectedExecution 办法来实现自定义回绝策略,实现代码如下:
public static void main(String[] args) {
// 工作的具体方法
Runnable runnable = new Runnable() {
@Override
public void run() {System.out.println("当前任务被执行, 执行工夫:" + new Date() +
"执行线程:" + Thread.currentThread().getName());
try {
// 期待 1s
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {e.printStackTrace();
}
}
};
// 创立线程, 线程的工作队列的长度为 1
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1,
100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1),
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 执行自定义回绝策略的相干操作
System.out.println("我是自定义回绝策略~");
}
});
// 增加并执行 4 个工作
threadPool.execute(runnable);
threadPool.execute(runnable);
threadPool.execute(runnable);
threadPool.execute(runnable);
}
11. 你罕用的回绝策略是哪种?为什么?
最罕用的回绝策略是自定义回绝策略,因为外面能够实现本人的业务代码,比方,咱们能够通过自定义回绝策略,发送正告信息给相干人员,这样就能及时发现程序执行的问题,同时再将回绝的工作记录下来,让开发人员手动解决,这样就能够及时发现问题,并解决问题了。
12. 三个线程交替打印 ABC
三个线程交替打印 ABC 的实现办法有很多,我集体比拟偏向于应用 JUC 下的 CyclicBarrier(循环栅栏,也叫循环屏障)来实现,因为循环栅栏天生就是用来实现一轮一轮多线程工作的,它的实现代码如下:
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* 3 个线程交替打印 ABC
*/
public class ThreadLoopPrint {
// 共享计数器
private static int sharedCounter = 0;
public static void main(String[] args) {
// 打印的内容
String printString = "ABC";
// 定义循环栅栏
CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () -> {});
// 执行工作
Runnable runnable = new Runnable() {
@Override
public void run() {for (int i = 0; i < printString.length(); i++) {synchronized (this) {
sharedCounter = sharedCounter > 2 ? 0 : sharedCounter; // 循环打印
System.out.println(printString.toCharArray()[sharedCounter++]);
}
try {
// 期待 3 个线程都打印一遍之后,持续走下一轮的打印
cyclicBarrier.await();} catch (InterruptedException e) {e.printStackTrace();
} catch (BrokenBarrierException e) {e.printStackTrace();
}
}
}
};
// 开启多个线程
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();}
}
以上程序执行的后果如下图所示:
更多内容请参考:https://www.javacn.site/interview/code/weilai_thread.html
13. 力扣括号生成
参考官网解题思路和实现代码:https://leetcode.cn/problems/generate-parentheses/solution/gua-hao-sheng-cheng-by-leetcode-solution/
参考 & 鸣谢
我没有三颗心脏
本文已收录到我的面试小站 www.javacn.site,其中蕴含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、音讯队列等模块。