从面经背八股


0. 面经

JVM元空间了解吗?里面包含了哪些东西?
1.8之后才引入的,里面有运行时常量池、类信息

元空间OOM遇到过吗?什么情况下元空间会OOM?
元空间大小过小+创建class过多,没有及时回收无用class

redis加速:

  1. 原生批量操作mset/sadd等
  2. 避免大key
  3. pipeline:一组命令封装为一组一起发送到服务器(不支持按照顺序)
  4. lua脚本批量操作多条指令(严格按照顺序)

redis数据结构底层实现
在这里插入图片描述

synchronized可以修饰静态方法吗
可以,锁这个类

volatile会不会造成线程阻塞
不会,没有资源互斥一说

volatile和synchronized是否能被编译器优化
不能,都是运行时根据线程状态实时变化的

全局异常处理器如何实现?
@ControllerAdvice定义全局控制器建议

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleException(Exception e) {
        // 处理异常逻辑
        return new ResponseEntity<>("An error occurred: " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

一致性哈希

分布式场景下某个数据通过hash放到了指定的机子上,如果集群有增删机子,基本所有数据的哈希值都要重新计算

一致哈希 是一种特殊的哈希算法。在使用一致哈希算法后,哈希表槽位数(大小)的改变平均只需要对 K/n 个关键字重新映射,其中K是关键字的数量, n是槽位数量。然而在传统的哈希表中,添加或删除一个槽位的几乎需要对所有关键字进行重新映射。

原理:整个哈希值所处的空间设置为环,把机子映射到环中指定位置上,新来的数据映射到环上后,顺时针找离他最近的机子
在这里插入图片描述
机子增删的情况下,只有环中一小部分数据被分配到了新的机子上

额外地,为了解决原始机子没有被较为均匀地分布到环上,引入了虚拟节点的概念,让虚拟节点分布得均匀一些

参考

线程池线程出异常,主线程怎么感知
前置知识:线程池提交任务有两种方式:execute(runnable接口,没有返回值) & submit(callable接口,返回future)
1、execute方法,可以看异常输出在控制台,而submit在控制台没有直接输出,必须调用Future.get()方法把异常抛给主线程,可以捕获到异常。【日志打印】
2、一个线程出现异常不会影响线程池里面其他线程的正常执行。【别人无影响】
3、线程不是被回收而是线程池把这个线程移除掉,同时创建一个新的线程放到线程池中。【替代】

参考 二

线程池中的线程怎样知道线程正在占用或者线程空闲

  1. submit提交任务,使用future判断状态
  2. getActiveCount() 获取当前活动线程数
  3. 设计额外字段标记状态
	public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        AtomicBoolean isBusy = new AtomicBoolean(false);
        // 提交任务到线程池
        executor.submit(() -> {
            isBusy.set(true); // 标记线程为占用状态
            try {
                Thread.sleep(2000);  // 模拟任务执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            isBusy.set(false); // 标记线程为空闲状态
        });
        // 检查线程状态
        if (isBusy.get()) {
            System.out.println("线程正在执行任务,处于占用状态");
        } else {
            System.out.println("线程空闲");
        }
        executor.shutdown();
    }

多线程获取锁,大量的线程阻塞等待,怎么优化

优化锁本身的角度: 减少锁粒度、使用读写锁、使用自旋锁(自旋锁在获取锁时会忙等待一段时间,而不是立即阻塞线程。如果锁被其他线程持有的时间很短,自旋锁可以减少线程切换的开销)

使用适当的并发数据结构角度: ConcurrentHashMap

考虑使用无锁算法:CAS

优化业务逻辑:尽量减少持锁时间,避免在持锁期间执行耗时操作

reentrantlock的await源码

reentrantlock(可重入/可公平)底层是AQS+CAS

  1. 将当前线程封装成node,添加到阻塞队列
  2. 当前线程释放所有资源
  3. 当前线程状态设置为wait
  4. 当前线程被唤醒

参考

一个线程被唤醒的两种情况(1)别的线程signal他了(2)别的线程signal他了之后,这个signal别人的线程中断,被signal的线程也被中断

redisson watch dog底层具体怎么刷新或者加锁释放锁的?

加锁时,默认加锁 30秒(lockWatchdogTimeout参数设置),每10秒钟检查一次(lockWatchdogTimeout/3),如果存在就重新设置 过期时间为30秒(续期)。

外层使用timeout定时开实现每10秒钟检查一次

在这里插入图片描述
(定时任务的时间没截图进来)

锁续期的底层还是lua脚本:

"if (redis.call(‘hexists’, KEYS[1], ARGV[2]) == 1) then " + // redis的hash数据结构中,是否存在KEYS[1], ARGV[2] 键值对
"redis.call(‘pexpire’, KEYS[1], ARGV[1]); " + // ,使用 pexpire 命令为键 KEYS[1] 设置过期时间为 ARGV[1]
"return 1; " + //操作成功
"end; " +
“return 0;”, // 不存在或操作失败

参考

零拷贝

用于避免在内核中的多次状态切换与数据拷贝

前置知识:DMA(Direct Memory Access 直接内存访问)
DMA本质上是一块主板上独立的芯片,允许外设设备和内存存储器之间直接进行IO数据传输,其过程不需要CPU的参与。

零拷贝优化前:
在这里插入图片描述
用户调用read然后调用write,总计发生四次上下文切换,两次DMA拷贝(从硬件拷贝到内核空间),两次cpu拷贝(内核缓冲区拷贝到用户缓冲区+用户缓冲区拷贝到socket缓冲区)

零拷贝并不是没有拷贝数据,而是减少用户态/内核态的切换次数以及CPU拷贝的次数。零拷贝实现有多种方式,分别是

  1. mmap+write
    在这里插入图片描述

用户调用mmap然后调用write,将内核中的读缓冲区与用户空间的缓冲区进行映射(两个虚拟地址映射到同一个物理地址),所有的IO都在内核中完成。总计I/O发生了4次用户空间与内核空间的上下文切换,以及3次数据拷贝。其中3次数据拷贝中,包括了2次DMA拷贝和1次CPU拷贝。

  1. sendfile

在这里插入图片描述

用户调用1次sendfile(调用与返回),sendfile表示在两个文件描述符之间传输数据,它是在操作系统内核中操作的,避免了数据从内核缓冲区和用户缓冲区之间的拷贝操作,因此可以使用它来实现零拷贝。总计I/O发生了2次用户空间与内核空间的上下文切换,以及3次数据拷贝。其中3次数据拷贝中,包括了2次DMA拷贝和1次CPU拷贝

  1. 带有DMA收集拷贝功能的sendfile

在这里插入图片描述
用户调用1次sendfile(调用与返回),总计I/O发生了2次用户空间与内核空间的上下文切换,以及2次数据拷贝(DMA)。

原理:linux 2.4版本之后,对sendfile做了优化升级,引入SG-DMA技术,其实就是对DMA拷贝加入了scatter/gather操作,它可以直接从内核空间缓冲区中将数据读取到网卡。使用这个特点搞零拷贝,即还可以多省去一次CPU拷贝。

  • DMA控制器,把数据从硬盘中拷贝到内核缓冲区。
  • CPU将读缓冲区中数据拷贝到socket缓冲区
  • DMA控制器,异步把数据从socket缓冲区拷贝到网卡,

应用举例:Kafka 使用零拷贝技术来减少数据在生产者和消费者之间的传输过程中的数据复制。当生产者发送消息时,消息首先被写入 Kafka 的内存缓冲区(称为页缓存),然后由 Kafka 的文件系统直接将数据写入磁盘,而不需要额外的数据复制操作。同样,在消费者接收消息时,Kafka 也能够将数据直接从磁盘读取到内存中,避免了额外的数据复制操作。

参考

可重复读怎么实现

当前读:加锁的select语句(select … lock in share mode; select … for update;),一定读取最新的数据
快照读:不加锁的select语句,读取到的可能是历史数据,基于mvcc实现

关于mvcc的readview:用于快照读,确定读的是哪个版本的数据
在这里插入图片描述

读已提交:每次快照读都生成新的快照readview。每次SELECT都能读取到已经COMMIT的数据,所以才存在不可重复读、幻读 现象。
可重复读:第一次快照读的时候才生成快照readview,后来commit的事务id还在m_ids中

参考

Kafka能手动删除消息吗

  1. 开启允许删除配置,命令行中执行delete
  2. 修改配置,开启定时清理
  3. 从zk/broker的目录中删除
  4. 提交offset(推荐)
public class DeleteReordsByOffset {
    public static void main(String[] args) throws ClassNotFoundException {
        // 1.创建kafkaAdminClient
        Properties properties = new Properties();
        properties.put("bootstrap.servers","192.168.27.111:9092");
        AdminClient kafkaAdminClient = KafkaAdminClient.create(properties);
        // 2.从数据库获取需要删除的消息
        Class.forName("com.mysql.jdbc.Driver");
        Map<TopicPartition, RecordsToDelete> recordsToDelete = new HashMap<>();
        String url  = "jdbc:mysql://localhost:3306/test?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8";
        String user = "root";
        String password = "123456";
        Connection conn = null;
        Statement statement = null;
        ResultSet res = null;
        String sql = "SELECT Topic, KafkaPartition, UntilOffset FROM Kafka_Offset;";
        try {
            conn = DriverManager.getConnection(url, user, password);
            statement = conn.createStatement();
            res = statement.executeQuery(sql);
            if (res != null) {
                while (res.next()) {
                    String topic = res.getString("Topic");
                    Integer partition = res.getInt("KafkaPartition");
                    Long offset = res.getLong("UntilOffset");
                    TopicPartition topicPartition = new TopicPartition(topic, partition);
                    // RecordsToDelete.beforeOffset(offset) 表示要删除offset之前的所有消息
                    RecordsToDelete recordsToDelete1 = RecordsToDelete.beforeOffset(offset);
                    // key:TopicPartition 表示主题和分区
                    // value:RecordsToDelete 表示要删除的消息记录类型,即在 offset 之前的消息记录。
                    recordsToDelete.put(topicPartition, recordsToDelete1);
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        //-----------------------------------------------------------
        //--------------------上面全是准备要删除的数据-------------------
        
        // 3.根据map--recordsToDelete执行删除
        DeleteRecordsResult result = kafkaAdminClient.deleteRecords(recordsToDelete);
        // lowWatermarks 表示成功删除的最大偏移量
        Map<TopicPartition, KafkaFuture<DeletedRecords>> lowWatermarks = result.lowWatermarks();
        try {
            for (Map.Entry<TopicPartition, KafkaFuture<DeletedRecords>> entry : lowWatermarks.entrySet()) {
                System.out.println(entry.getKey().topic() + " " + entry.getKey().partition() + " " + entry.getValue().get().lowWatermark());
            }
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        kafkaAdminClient.close();
    }
}
 

参考

SpringBoot核心注解
启动类上面的注解:@SpringBootApplication包含以下三个复合注解

  1. @SpringBootConfiguration:里面就一个 @Configuration 注解,表示当前类是一个配置类
  2. @EnableAutoConfiguration: 告诉 Spring Boot 在启动应用程序时要自动配置所需的 bean。Spring Boot 会根据 classpath(应用程序的类路径) 中的依赖、配置文件、注解等信息,自动配置应用程序所需的组件。
  3. @ComponentScan:扫描该路径下所有配置类

springboot框架带来了什么
自动化配置和约定大于配置的方式,使开发人员可以专注于业务逻辑,而不需要过多地关注底层的技术细节。

自动配置实现:@EnableAutoConfiguration注解,指定了自定义&其它配置类,按照这些配置类实现bean的导入(环境配置)

在项目启动的时候,Spring Boot框架会自动读取META-INF/spring.factories配置文件中org.springframework.boot.autoconfigure.EnableAutoConfiguration所配置的配置类,然后将其中所定义的bean根据条件注解所指定的条件来决定是否需要将其导入到Spring容器中。

在这里插入图片描述

参考

MTU:网络通信中一次可以传输的最大数据包的大小。MTU的标准大小为1500字节。这意味着在以太网上,一个数据包的大小不能超过1500字节,否则就需要分片成更小的数据包进行传输
RTT

http用tcp还是udp还是都有
tcp / quic(udp)

为什么tcp可以做,还要udp做然后增加quic协议

之前常见的是http2,quic主要是http3

http2基于http1做出的改进:
(1)hpack压缩头部
(2)主动推送一些内容
(3)二进制帧
(4)并发传输(通过stream实现并发):http1.1中,同一个连接中,HTTP 完成一个事务(请求与响应),才能处理下一个事务,也就是说在发出请求等待响应的过程中,是没办法做其他事情的,如果响应迟迟不来,那么后续的请求是无法发送的,也造成了队头阻塞的问题。http2中,多个 Stream 复用一条 TCP 连接(但是stream之间有拓扑关系),同一个 HTTP 请求与响应是跑在同一个 Stream 中,HTTP 消息可以由多个 Frame 构成, 一个 Frame 可以由多个 TCP 报文构成。
在这里插入图片描述
上图为同一个连接中的并行,也是http2并发传输的原理
不同 Stream 的帧是可以乱序发送的(因此可以并发不同的 Stream ),因为每个帧的头部会携带 Stream ID 信息,所以接收端可以通过 Stream ID 有序组装成 HTTP 消息,而同一 Stream 内部的帧必须是严格有序的。
在这里插入图片描述

在这里插入图片描述

HTTP2问题:
创建初衷:quic(传输层协议) = tcp的可靠性+udp的快速

  1. 通过减少往返次数,以缩短连接建立时间
    在这里插入图片描述
    上图从左到右分别是3RTT、2RTT、1RTT(tcp和TLS握手整合在一起了),红色箭头代表后续的加密通信

  2. http2在tcp层面有队头阻塞问题

http2在一个tcp连接中可以跑多个stream,但是必须编号较小的stream先到

在http3中,每个stream都是独立的,没有依赖关系

在这里插入图片描述

在这里插入图片描述
实际传输时使用的是包,某个包丢了重传该包即可

websocket

WebSocket 和 HTTP 两者都是基于 TCP 的应用层协议,都可以在网络中传输数据。

区别:
WebSocket 双向通信持久连接(低延迟),客户端和服务器可以同时收发数据,ws:// wss:// 通信数据格式比较轻量,头部包小
HTTP单向通信, http:// https://

开启WebSocket ,可以由HTTP升级而来:客户端向服务器发送一个 HTTP 请求,请求头中包含 Upgrade: websocket 和 Sec-WebSocket-Key 等字段,表示要求升级协议为 WebSocket;

全局异常类实现

  1. 定义异常枚举类,包括错误码+描述
  2. 定义异常类,包括异常枚举类(错误码+描述)+若干种构造函数
  3. 定义返回类(以上三步重在封装好异常返回类)
  4. 定义全局异常处理类,先用@ControllerAdvice修饰这个类,表示开启全局异常处理。然后用@ExceptionHandler修饰里面针对某个异常捕获到的响应处理函数

参考

为什么四次挥手,Time_Wait状态,2MSL、收到SYN包情况
在这里插入图片描述2msl,图上写错了.
MSL(Maximum Segment Lifetime) : 一个片段在网络中最大的存活时间,2MSL 就是一个发送和一个回复所需的最大时间。如果直到 2MSL,Client 都没有再次收到 FIN(服务端重发的),那么 Client 推断 ACK 已经被成功接收,则结束 TCP 连接。

在这里插入图片描述
如果在Time_Wait阶段收到SYN包,通常会被忽略,因为此时连接已经关闭,不应再接受新的连接。

java如何做http响应

HttpClient或者HttpURLConnection

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class HttpResponseExample {
    public static void main(String[] args) {
        try {
            // 创建URL对象
            URL url = new URL("http://example.com/api/data");

            // 打开连接
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();

            // 设置HTTP请求方法
            conn.setRequestMethod("GET");

            // 获取响应码
            int responseCode = conn.getResponseCode();
            System.out.println("Response Code: " + responseCode);

            // 读取响应数据
            BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line;
            StringBuffer response = new StringBuffer();

            while ((line = reader.readLine()) != null) {
                response.append(line);
            }

            reader.close();

            // 输出响应数据
            System.out.println("Response Data: " + response.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

处理HTTP响应通常包含:

  1. 设置url与请求方式,发起请求
  2. 获取响应状态码/响应头/响应体
  3. 处理响应数据

linux收到tcp请求做哪些处理

服务端:

  1. 建立连接阶段
    调用socket(),分配文件描述符,即监听套接字
    调用bind(),将套接字与本地IP地址和端口绑定
    调用listen(),监听特定端口,socket()创建的套接字是主动的,调用listen使得该文件描述符为监听套接字,变主动为被动
    调用accept(),阻塞等待客户端连接

bind()绑定服务器的地址和端口,然后调用listen()开始监听连接请求,最后通过accept()接受客户端连接

  1. 数据交互阶段
    调用read(),阻塞等待客户端发送的请求,收到请求后从read()返回,处理客户端请求
    调用write(),将处理结果发送给客户端,然后继续调用read()等待客户端请求

  2. 关闭连接
    当read()返回0的时候,说明客户端发来了FIN数据包,即关闭连接,也会调用close()关闭连接套接字和监听套接字

客户端:

  1. 建立连接阶段
    调用socket(),分配文件描述符
    调用connect(),向服务器发送建立连接请求(tcp三次握手)

  2. 数据交互阶段
    调用write(),将请求发送给服务器
    调用read(),阻塞等待服务器应答

  3. 关闭连接
    当没有数据发送的时候,调用close()关闭连接套接字,即关闭连接,向服务器发送FIN数据报(tcp四次挥手)

建立请求之后 linux怎么处理 等待数据会使用线程等待吗?

使用事件驱动模型: I/O 多路复用技术

一旦建立了 TCP 连接,Linux 会处理后续的数据传输。在等待数据的过程中,Linux 通常会使用事件驱动的模型来处理数据,而不是简单地使用线程等待。常见的做法是使用 I/O 多路复用技术,如 select()、poll()、epoll() 等,来等待数据的到来,以提高效率。

使用事件驱动模型的优势在于可以同时处理多个连接,而不需要为每个连接创建一个单独的线程。这样可以节省系统资源,并提高系统的并发处理能力。当有数据到达时,系统会通知相应的处理程序进行数据处理,而不是让线程一直等待数据的到来。

详见3

内存的分段分页,解决了什么问题

(都是非连续内存管理)

分段:逻辑上的分离,不会产生内部碎片
分页:不会产生外部碎片
段页式:先分段后分页

在这里插入图片描述
虚拟地址 = 段号+段内页号+页内地址
段表始址+段号 = 页表始址
页表始址+页号 = 指定页地址
指定页地址+页内地址 = 数据的物理地址

MAC有哪些字段
OUI字段(Organizationally Unique Identifier):前24位(前3个字节)是OUI字段,用于标识设备的制造商。OUI由IEEE(Institute of Electrical and Electronics Engineers)分配给设备制造商。

NIC字段(Network Interface Controller):后24位(后3个字节)是NIC字段,用于标识设备的唯一性。NIC字段由设备制造商分配。

Linux下如何查看域名对应的IP

(1) host example.com 显示该域名的ip
(2) ping example.com 显示该域名的ip并发送ICMP请求

锁之间通信如何实现
等待通知机制,notify/wait
AQS中countdownlatch那几个

Git常用命令
git rebase合并多个commit
git revert撤销某个commit

TCP拥塞控制
拥塞情况、超时重传、快速恢复

讲一下mysql/innodb有哪些锁
全局级/表级(表锁/元数据锁/意向锁)/行级(记录锁/间隙锁/临键锁)

进行表级操作怎么判断是否有行锁
意向锁

什么时候触发老年代的gc
老年代空间不足、full gc

用什么参数调整老年代大小
-XX:NewRatio

java线程和linux线程的关系
java线程由jvm管理,jvm将线程映射到Linux 线程,与内核态交互

1. spring&java

spring事务的传播

三个关键概念:

融入:B事务融进A事务
挂起:AB事务互不影响,
嵌套:mybatis中自定义checkpoint
在这里插入图片描述

七种传播行为:(前提 A外B里)

  1. required:A无事务,B创,A有,B融(99%的B改)
  2. support:A有事务B融入,A无事务B非事务(99%的B查)
  3. mandatory:A有事务B融入,A无事务抛异常,强制B加入到事务中
  4. requires_new:A有则挂起,B创事务(B需要一个新事务)
  5. not_support:A有则挂起,B以非事务运行(B不支持事务)
  6. never:不应当有事务,A有事务抛异常(和mandatory是一对)
  7. nested:一个conn里begin多次开启嵌套事务,但是mysql不支持,只能mybatis中savepoint+rollbackto逻辑实现(嵌套方式)

一个数据库连接处理一个事务!!!

Q:Spring中的事务是如何实现的?A:
基于数据库事务和AOP。spring对于事务的实现有两种方式:声明式事务(@Transaction创建一个代理)和编程式事务(少)。如果一个类或者一个类中的某个方法被@Transaction修饰,那么会给该类创建代理类,实际调用的是TransactionInterceptor中的invoke方法。在这个方法中实现:
(1)执行sql前创建数据库连接,自动提交设置为false;(2)执行sql后进行提交或回滚逻辑
引申:spring的事务隔离级别就是数据库的默认隔离级别,spring事务传播机制

在spring事务传播中,数据库连接池,是将connection放进threadlocal里的,以保证每个线程从连接池中获得的都是线程自己的connection,保证每个线程获取的都是同一个连接

JDBC

jdbc:DB厂商提供的DB操作工具

实现数据库连接池

栈存放连接,这样每次都拿新的,老的因为一直压在栈底不被使用会达到回收寿命被回收
过期连接的释放:在使用完连接放回栈的时候进行一次扫描

参考

mybatis/plus原理

数据库表映射到具体的类,mapper层定义接口extends BaseMapper(空的,不过里面有很多封装好的CRUD),service层通过mapper层调用,尽量用封装好的函数,不要自己写sql

在这里插入图片描述
在这里插入图片描述
如果要在service层调用数据库CRUD:
定义:
在这里插入图片描述
使用:
在这里插入图片描述

参考

表与类进行映射:

  1. mybatis-plus:@TableName(“user”) @TableId @TableField(“username”)
  2. mybatis:xml的resultType(查询出来的信息自动填充到resultType中)

Spring设计模式

单例 bean
工厂 beanFactory
模板 template
观察者 listener 一个变了,别人观察到就跟着变
装饰器 aop 功能增强
适配器 新旧接口共同工作,有点点像aop

// 旧的服务类
public class LegacyService {
    public void doAction() {
        System.out.println("LegacyService is performing action...");
    }
}

// 新的接口
public interface NewService {
    void performAction();
}

// 适配器类
public class LegacyServiceAdapter implements NewService {
    private LegacyService legacyService;

    public LegacyServiceAdapter(LegacyService legacyService) {
        this.legacyService = legacyService;
    }

    @Override
    public void performAction() {
        legacyService.doAction(); // 在适配器中调用旧接口的方法
    }
}

// 在Spring中使用适配器
public class Client {
    public static void main(String[] args) {
        LegacyService legacyService = new LegacyService();
        NewService newService = new LegacyServiceAdapter(legacyService);

        // 现在可以通过NewService接口调用LegacyService的功能
        newService.performAction();
    }
}

适配器和aop区别
目的不同: 适配器模式的主要目的是使不兼容的接口能够一起工作,而AOP 的主要目的是分离关注点,提高代码的模块性和可维护性。
应用场景不同: 适配器模式通常用于整合不同接口的类,使它们能够协同工作;而AOP 主要用于横切关注点的处理,如日志、事务管理等。

2. 数据库

为什么可重复读还会出现幻读

可重复读隔离级别

在一个事务中,第一次快照读的时候生成快照readview,此后在事务中复用这个read view,解决了不可重复读的问题

幻读问题

(1)可重复读是如何解决部分幻读的

当前读的时候会加临键锁

在这里插入图片描述

(2)可重复读不能解决的幻读是什么情况

在这里插入图片描述
A事务第一次看的时候不存在id=5的记录
B添加此条记录并提交
A修改id=5的记录成功,这是不希望看到的(尽管此条操作的出发点很违和)

为什么会出现这种情况?因为创建read view 的事务是能看到所有记录的(read view原则)

在这里插入图片描述
另一种幻读情况:先快照读再当前读,一开始没有触发临键锁的设置,导致其它事务可以插入数据

在这里插入图片描述
在这里插入图片描述

explain

type字段,表示查询执行的类型:

  • const 表中最多只有一行匹配,查询条件为主键索引/唯一索引
  • ref 表中多条匹配,普通索引
  • range 对索引列进行范围查询
  • index 在内存中遍历了所有索引
  • all 遍历了所有记录

possible_keys:可能用到的索引

key:实际使用的索引

extra:查询时的额外信息

  • using filesort:使用了外部索引,没用表内索引
  • using temporary:创建了临时表,一般是使用了order by/group by
  • using index:使用到了覆盖索引
  • using index condition:查询优化器使用到了索引下推
  • using where:使用了where条件匹配,一般表示没走索引

索引下推:联合索引部分字段在where中,在遍历索引的时候,直接筛选掉不符合where条件的联合索引,减少回表操作,参考

驱动表与被驱动表:左表为驱动表,select from A join B on … A是驱动表

索引与order by

总结:要跟where联合使用,无where不生效,跟where在一起满足最左匹配原则
在这里插入图片描述

隐式转换

隐式转换:两边类别不同时,两边都被转换为浮点数进行比较

num1:int
num2:varchar
在这里插入图片描述
23都发生了隐式转换,14没有转换直接命中索引
124瞬间出结果
2:两边转换为浮点数都是10000,转换结果都是唯一的
3:左面如果是’10000a’ / ‘010000’ 都能转换为10000,不是唯一的,所以没命中索引
总结:字符串转数字是不唯一的,数字转字符串是唯一的,只有唯一的情况才能命中索引

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Python面经八股文是指在面试过程中经常会被问到的一些关于Python语言的基础知识问题。这些问题常常是考察面试者对Python语法、数据类型、函数库等方面的理解和应用。 第一个问题通常是问到Python的特点,比如动态语言、解释型语言等。随后可能会问到Python的命名规范,如变量名、函数名等的命名规则。 接下来可能会被问到Python的数据类型,如字符串、列表、字典和元组的特点和用法。还会询问如何进行类型转换和切片操作。 然后可能会被问到Python的函数,包括如何定义函数、函数参数的类型和默认值,以及如何调用函数和返回值等。 接着可能会被问到Python的模块和库的使用,如time、datetime、random等常用模块的函数和方法。还可能问到如何处理文件、异常等知识点。 另外,面试者还可能会被问到Python的面向对象编程的相关知识,如类和对象的概念、实例化对象、继承和多态等。 最后,可能会被问到Python的常用框架和库,如Django、Flask和NumPy等。还可能会问到如何进行数据库操作、网络编程等相关知识点。 在面试中回答这些问题需要准备充分,对Python的基础知识和常用库要熟悉,并能够清晰地表达自己的观点和经验。同时也可以结合项目经验等实际经历进行回答,展示自己的实际应用能力。 ### 回答2: Python面经八股文主要包括以下几个方面: 一、Python基础知识: 1. 数据类型:了解Python中的常见数据类型,包括字符串、列表、元组、字典等,并能灵活运用。 2. 控制结构:熟悉Python的控制结构,如条件语句、循环语句和异常处理等,并能正确使用。 3. 函数和模块:了解函数和模块的概念,在项目中能够定义并调用函数,以及导入和使用模块。 4. 文件操作:了解Python中的文件操作方法,能够对文件进行读写操作。 5. 面向对象编程:理解面向对象编程的概念,能够定义类、创建对象,并掌握继承、多态等特性。 二、Python常用库和框架: 1. Numpy:了解Numpy库的基本用法,包括数组的创建和操作,矩阵运算等。 2. Pandas:熟悉Pandas库的数据处理功能,包括数据的读取、清洗、排序、合并等。 3. Matplotlib和Seaborn:掌握Matplotlib和Seaborn库用于数据可视化的操作,能够生成各种统计图表。 4. Scikit-learn:熟悉Scikit-learn库的机器学习算法,能够进行数据预处理、特征工程和模型训练等。 三、数据库操作: 1. SQL语言:了解SQL语言的基本语法,能够编写简单的SQL查询语句,实现数据的增删改查等操作。 2. MySQL或MongoDB:了解MySQL或MongoDB数据库的基本操作,包括连接数据库、创建表、插入数据等。 3. ORM框架:熟悉Django或SQLAlchemy等ORM框架的使用,能够进行数据库的ORM操作。 四、Web开发: 1. Flask或Django:了解Flask或Django框架的基本使用方法,能够搭建简单的Web应用。 2. RESTful API:熟悉RESTful API的设计原则,能够使用Flask或Django开发和部署API接口。 3. HTML和CSS:掌握基本的HTML和CSS知识,能够进行网页布局和样式设计。 总结起来,Python面经八股文主要包括Python基础知识、常用库和框架、数据库操作以及Web开发等内容。熟练掌握这些知识点,能够在面试中展现出扎实的编程基础和项目经验,提高自己的面试竞争力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值