常见八股文集合

本文最后更新于:2024年11月8日 下午

Java

== 和 equals的区别

  1. ==针对基本类型时比较的是对象的值,针对引用类型比较的是对象的指向的内存地址是否相等。

  2. equals针对的比较对象是引用类型, 在Java中Object对象是所有对象的父类,所以每个类都会有个equals的方法,如果你没有重写它那些它与==的效果是一样的,可以通过源代码查看。

例子

1
2
3
4
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2); // false
System.out.println(s1.equals(s2)); // true

String StringBuilder StringBuffer的区别

  • String:不可变的字符序列,线程安全,适用于字符串不经常改变的场景。
  • StringBuilder:可变的字符序列,线程不安全,适用于字符串经常改变的场景。
  • StringBuffer:可变的字符序列,线程安全,适用于字符串经常改变的场景。

1、String类型的字符串对象是不可变的,一旦String对象创建后,包含在这个对象中的字符系列是不可以改变的,直到这个对象被销毁。
2、StringBuilder和StringBuffer类型的字符串是可变的,不同的是StringBuffer类型的是线程安全的,而StringBuilder不是线程安全的
3、如果是多线程环境下涉及到共享变量的插入和删除操作,StringBuffer则是首选。如果是非多线程操作并且有大量的字符串拼接,插入,删除操作则StringBuilder是首选。毕竟String类是通过创建临时变量来实现字符串拼接的,耗内存还效率不高,怎么说StringBuilder是通过JNI方式实现终极操作的。
4、StringBuilder和StringBuffer的“可变”特性总结如下:
(1)append,insert,delete方法最根本上都是调用System.arraycopy()这个方法来达到目的
(2)substring(int, int)方法是通过重新new String(value, start, end - start)的方式来达到目的。

List、Set、Map的区别

  • List:有序,可重复,底层是数组(或链表)
  • Set:无序,不可重复,底层是哈希表
  • Map:无序,key不可重复,value可重复,底层是哈希表

ArrayList和LinkedList的区别

  • ArrayList:底层是数组,查询快,增删慢
  • LinkedList:底层是链表,查询慢,增删快

ArrayList的扩容策略

ArrayList的扩容策略是每次扩容为原来的1.5倍,当数组长度小于10时,每次扩容为原来的2倍。
最大容量为Integer.MAX_VALUE - 8。

ArrayList的默认大小
如果使用无参构造函数ArrayList()创建一个空的ArrayList对象,那么它的初始容量为10。

[补充] 为什么扩容为1.5倍或两倍

  1. 为了减少扩容的次数,提高性能。如果每次扩容为原来的大小,那么会导致扩容的次数过多,影响性能。
  2. 如果扩容倍数太大(比如3倍或更高),虽然减少了扩容次数,但会导致每次扩容后的内存空间利用率较低,即可能浪费较多的内存。
  3. 从内存角度,一般内存以块进行分配,如果扩容为2的倍数,可以更好的利用内存块。1.5倍的扩容倍数在某些情况下可以避免数组过快地达到内存上限,同时还能有效利用内存块,从而减少内存碎片。

介绍一下Stream流的并行API Parallelstream

ParallelStream通过内部使用Fork/Join框架实现并行处理。当你调用Collection的parallelStream方法时,你会得到一个ParallelStream对象。

HashMap和HashTable的区别

  • HashMap:线程不安全,允许key和value为null
  • HashTable:线程安全,不允许key和value为null

为什么线程不安全

  1. 数据竞争
    HashMap 在多线程环境下进行读写操作时,多个线程可能会同时访问和修改内部数据结构(如数组和链表),这会导致数据竞争和不一致性。例如,两个线程同时插入元素时,可能会覆盖彼此的修改,导致数据丢失。

  2. 扩容操作
    当 HashMap 的容量达到一定阈值时,会触发扩容操作。扩容操作会创建一个新的更大的数组,并将旧数组中的元素重新哈希并放入新数组中。在多线程环境下,如果多个线程同时触发扩容操作,可能会导致死循环、数据丢失或其他不可预知的行为。

  3. 链表和树结构
    在 HashMap 中,哈希冲突的解决方案是使用链表或红黑树。当多个线程同时操作同一个桶(bucket)时,可能会导致链表或树结构的不一致。例如,两个线程同时在同一个桶中插入元素,可能会导致链表断裂或树结构损坏。

  4. 迭代器
    HashMap 的迭代器在多线程环境下也是不安全的。如果在迭代过程中有其他线程修改了 HashMap,迭代器会抛出 ConcurrentModificationException 异常。这是因为 HashMap 的迭代器是快速失败的(fail-fast),它会检测到集合的结构被修改。

ConcurrentHashMap,HashMap和HashTable的区别

  • HashMap:线程不安全,效率高,允许key和value为null
  • ConcurrentHashMap:线程安全,效率高,允许key和value为null
  • HashTable:线程安全,效率低,不允许key和value为null

ConcurrentHashMap的实现原理

ConcurrentHashMap是Java中的一个线程安全的哈希表,它的实现原理是分段锁。ConcurrentHashMap中有一个Segment数组,每个Segment都是一个哈希表,每个Segment都是一个独立的锁。通过Segment数组,ConcurrentHashMap将整个哈希表分成了多个小的哈希表,每个小的哈希表都有一个独立的锁,这样就可以实现对每个小的哈希表的并发访问,从而提高了并发访问的效率。这样就可以实现对每个Segment的并发访问,从而提高了并发访问的效率。

hashmap的扩容原理

ashMap 在插入元素时,如果当前元素数量超过了阈值(threshold),就会触发扩容。阈值的计算公式为:
threshold = capacity * loadFactor
其中,capacity 是 HashMap 的容量,loadFactor 是负载因子,默认值为 0.75。当 HashMap 中的元素数量超过了 threshold,就会触发扩容。扩容的过程是:

  1. 创建一个新的 Entry 数组,长度是原来的 2 倍。
  2. 将原来的 Entry 数组中的元素重新计算 hash 后,放入新的 Entry 数组中。
  3. 将新的 Entry 数组赋值给原来的 Entry 数组。

java的反射机制

Java的反射机制是指在运行时动态获取类的信息,比如类的属性、方法、构造器等。Java的反射机制主要是通过Class类来实现的,Class类是Java中的一个类,它是一个类的类,它是一个类的模板,它是一个类的元数据,它是一个类的描述。

ThreadLocal的使用场景有哪些?原理?内存泄漏?

ThreadLocal,即线程本地变量。如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是操作自己本地内存里面的变量,从而起到线程隔离的作用,避免了线程安全问题。

ThreadLocal原理

  • Thread对象中持有一个ThreadLocal.里面有个ThreadLocalMap的成员变量。
  • ThreadLocalMap内部维护了Entry数组,每个Entry代表一个完整的对象,key是ThreadLocal本身,value是ThreadLocal的泛型值。
  • 每个线程在往ThreadLocal里设置值的时候,都是往自己的ThreadLocalMap里存,读也是以某个ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。

内存泄漏
因为ThreadLocalMap中使用的 key 为 ThreadLocal 的弱引用:

弱引用:只要垃圾回收机制一运行,不管JVM的内存空间是否充足,都会回收该对象占用的内存。

弱引用比较容易被回收。因此,如果ThreadLocal(ThreadLocalMap的Key)被垃圾回收器回收了,但是因为ThreadLocalMap生命周期和Thread是一样的,它这时候如果不被回收,就会出现这种情况:ThreadLocalMap的key没了,value还在,这就会造成了内存泄漏问题。

解决内存泄漏问题,使用完ThreadLocal后,及时调用remove()方法释放内存空间。

发生oom,该怎么排查

  • 查看堆内存使用情况,使用jmap命令查看堆内存使用情况,查看是否有内存泄漏。
  • 查看GC日志,查看GC日志,查看GC的情况,查看是否有频繁的Full GC。
  • 可以用jprofiler等工具查看内存使用情况,查看内存泄漏的地方。

java 接口类和抽象类的区别

java为什么不支持重载运算符

JVM

JVM的内存结构

JVM的内存结构大致分为五个部分,分别是程序计数器、虚拟机栈、本地方法栈、堆和方法区。除此之外,还有由堆中引用的JVM外的直接内存。

JDK1.7以前的内存区域

JDK1.8以后的内存区域

1.8之后的内存区域中,永久代被元空间取代,字符串常量池和运行时常量池被合并为一个运行时常量池。

堆的内存结构

Java堆是Java虚拟机管理的内存中最大的一块,主要用于存放对象实例。Java堆是垃圾收集器管理的主要区域,因此也被称为GC堆。从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以Java堆还可以细分为:新生代和老年代;再细致一点有:Eden、Survivor、Old 等空间。进一步划分的目的是更好地回收内存,或者更快地分配内存。

对象什么时候进入Survivor区
当Eden区满时,会触发Minor GC,将Eden区中的存活对象复制到S0或S1中,然后清理掉Eden区中的所有对象。在多次Minor GC之后,存活时间较长的对象会被移动到老年代。

对象什么情况下一直不会进入老年代
如果对象在Eden区被创建,那么它在第一次Minor GC后,会被移动到Survivor区,如果在Survivor区被创建,那么它在第二次Minor GC后,会被移动到老年代。

JVM的垃圾回收器

JVM的垃圾回收器主要分为两大类:串行垃圾回收器和并行垃圾回收器。

Serial垃圾收集器
Serial垃圾回收器是串行垃圾回收器,它是单线程的,只会使用一个线程来进行垃圾回收。Serial垃圾回收器适用于单核CPU的环境。

新生代采用标记-复制算法,老年代采用标记-整理算法。

ParNew垃圾收集器

ParNew垃圾回收器是Serial垃圾回收器的多线程版本,它是并行垃圾回收器,适用于多核CPU的环境。

Parallel Scavenge 垃圾收集器

Parallel Scavenge垃圾回收器是并行垃圾回收器,它适用于多核CPU的环墋,它的特点是吞吐量优先,适用于后台运行的应用。

Serial Old垃圾收集器

Serial Old垃圾回收器是Serial垃圾回收器的老年代版本,它是单线程的,适用于单核CPU的环境。

Parallel Old垃圾收集器

Parallel Old垃圾回收器是Parallel Scavenge垃圾回收器的老年代版本,它是并行垃圾回收器,适用于多核CPU的环境。

CMS垃圾收集器

CMS垃圾回收器是并发垃圾回收器,它是一种以获取最短回收停顿时间为目标的收集器,适用于对响应时间有要求的应用。

G1垃圾收集器

G1全称为Garbage First ,G1垃圾回收器是一种面向服务端应用的垃圾回收器,它是一种并发的、基于标记-整理算法的垃圾回收器,适用于多核CPU的环境。

ZGC垃圾收集器

ZGC垃圾回收器是一种低延迟的垃圾回收器,它是一种并发的、基于标记-整理算法的垃圾回收器,适用于对响应时间有要求的应用。

垃圾回收算法

  1. 标记-清除(Mark-and-Sweep)
    工作原理:首先,GC 会遍历所有的对象,标记出所有可达的对象(即仍在使用的对象)。然后,GC 会遍历堆内存,清除所有未被标记的对象(即不可达的对象),释放这些对象占用的内存。
    优点:简单且高效,适用于一般场景。
    缺点:可能会导致内存碎片化,因为清除后留下的空闲内存块大小不一。
  2. 标记-整理(Mark-Compact)
    工作原理:与标记-清除类似,首先标记所有可达对象。不同的是,在清除阶段,GC 会将存活的对象向一端移动,整理堆内存,使得所有的空闲内存集中在一起。
    优点:避免了内存碎片化问题,适合需要长时间运行的应用程序。
    缺点:相比标记-清除算法,移动对象的开销更大。
  3. 复制算法(Copying or Scavenge)
    工作原理:将内存划分为两块相等的区域,每次只使用其中一块。当一块区域满时,将存活的对象复制到另一块区域,清除原来区域的所有对象。
    优点:避免了内存碎片化,复制过程中可以自然地整理内存。
    缺点:内存利用率低,因为始终有一半的内存处于空闲状态。
  4. 分代收集算法(Generational GC)
    工作原理:将堆内存划分为不同的代(通常是年轻代和老年代)。新创建的对象通常放在年轻代,GC 会频繁地回收年轻代中的对象,而老年代中的对象生命周期较长,GC 对其回收频率较低。
    优点:优化了垃圾回收的效率,因为大多数对象的生命周期很短(即“朝生夕灭”对象),可以快速回收。
    缺点:需要额外的逻辑来管理对象的分代和晋升。
  5. 增量收集算法(Incremental GC)
    工作原理:将垃圾回收过程拆分成多个小的步骤,分散到正常程序执行的过程中,以避免长时间的停顿。
    优点:减少了应用程序的停顿时间,适用于对响应时间要求高的应用。
    缺点:整体回收效率可能会降低,且实现较为复杂。
  6. 并行收集算法(Parallel GC)
    工作原理:在多核 CPU 环境下,利用多线程同时执行垃圾回收工作,以加快回收过程。
    优点:提高了垃圾回收的效率,适合多处理器或多核系统。
    缺点:在某些情况下,可能会导致应用程序线程的响应时间变长。
  7. 并发收集算法(Concurrent GC)
    工作原理:垃圾回收器在应用程序运行的同时进行垃圾回收工作,减少因垃圾回收引起的应用停顿时间。
    优点:大大降低了垃圾回收的停顿时间,适用于对响应时间敏感的应用。
    缺点:由于垃圾回收与应用程序并发执行,可能会导致整体性能下降。

jvm如何知道对象是否可以被回收

JUC

Runable和Callable有什么区别

  1. Runnable是Java中的一个接口,它只有一个run方法,没有返回值,不能抛出异常。
  2. Callable是Java中的一个接口,它有一个call方法,有返回值,可以抛出异常。

启动一个线程是用start()还是run()方法

启动一个线程是用start()方法,而不是run()方法。如果你调用run()方法,那么这个方法就会在当前线程中执行,而不会启动一个新的线程。

线程池原理?各个参数的作用

1
2
3
4
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

几个核心参数的作用:

  • corePoolSize:线程池核心线程数最大值
  • maximumPoolSize:线程池最大线程数大小
  • keepAliveTime:线程池中非核心线程空闲的存活时间大小
  • unit:线程空闲存活时间单位
  • workQueue:存放任务的阻塞队列
  • threadFactory:用于设置创建线程的工厂,可以给创建的线程设置有意义的名字,可方便排查问题。
  • handler: 线城池的饱和策略事件,主要有四种类型。

四种饱和拒绝策略

  • AbortPolicy(抛出一个异常,默认的)
  • DiscardPolicy(直接丢弃任务)
  • DiscardOldestPolicy(丢弃队列里最老的任务,将当前这个任务继续提交给线程池)
  • CallerRunsPolicy(交给线程池调用所在的线程进行处理)

工作队列的类型

  • ArrayBlockingQueue:基于数组的有界阻塞队列
  • LinkedBlockingQueue:基于链表的有界阻塞队列
  • SynchronousQueue:不存储元素的阻塞队列
  • PriorityBlockingQueue:具有优先级的无界阻塞队列

几种线程池

  • newFixedThreadPool:固定大小的线程池
  • newSingleThreadExecutor:单个线程的线程池
  • newCachedThreadPool:可缓存的线程池,线程池的大小是没有限制的
  • newScheduledThreadPool:定时任务的线程池

Cpu密集型和IO密集型 N+1 2N 具体是如何计算的

  • CPU密集型:CPU密集型是指CPU的使用率非常高,而I/O操作非常少的程序。CPU密集型程序的特点是CPU使用率很高,而内存和硬盘I/O操作的使用率很低。
  • IO密集型:IO密集型是指I/O操作非常频繁的程序。IO密集型程序的特点是CPU使用率很低,而内存和硬盘I/O操作的使用率很高。

其中n+1和2n是上文中线程池设置线程的数量;

主要是因为,如果线程数量太多的话,线程的竞争大,会导致大量上下文切换,因为CPU是分配给时间片给线程来处理任务的,时间片一到,那么线程就得保存当前任务,等待下一次CPU分配时间片,那么下一次线程获取到CPU时间片之后,会重新加载任务来处理,保存到加载的过程就称之为上问下切换。

CPU n+1
如果程序的计算量特别高,那么就属于CPU密集型,线程数量可以设置为N(CPU的核心数)+1。

I/O密集型(2N)
如果程序的是网络传输或者I/O操作比较多,那么线程池应该设置为2N,2n可以保证在运行时,有足够的线程在等待,从而最大的利用线程池。

线程池的核心线程会被回收吗?

一般情况下,线程池的核心线程是不会被回收的,只有非核心线程会被回收。

乐观锁悲观锁

  • 乐观锁:乐观锁是一种乐观的思想,它认为并发访问的情况是比较少的,所以在读取数据的时候不加锁,只在更新数据的时候加锁,这样可以提高并发访问的效率。
  • 悲观锁:悲观锁是一种悲观的思想,它认为并发访问的情况是比较多的,所以在读取数据的时候加锁,这样可以保证并发访问的安全性。

公平锁和非公平锁的区别

公平锁(Fair Lock)
定义:公平锁是指按照线程请求锁的顺序来分配锁,先请求的线程先获得锁。
实现:公平锁通常通过维护一个有序的等待队列来实现,确保每个线程按照其请求的顺序获得锁。

优点:

  • 避免线程饥饿:每个线程都会按照顺序获得锁,不会有线程长期得不到锁。
  • 适用于需要严格顺序访问资源的场景。
    缺点:
  • 性能较低:由于需要维护等待队列和顺序分配锁,公平锁的性能通常比非公平锁低。
  • 可能导致频繁的上下文切换,增加系统开销。

非公平锁(Non-Fair Lock)
定义:非公平锁是指线程获取锁的顺序不一定按照请求的顺序,任何线程都有机会在锁释放时立即获得锁。
实现:非公平锁通常不维护等待队列,线程在尝试获取锁时会直接竞争锁。

优点:

  • 性能较高:由于不需要维护等待队列,非公平锁的性能通常比公平锁高。
  • 适用于对性能要求较高的场景。
    缺点:
  • 可能导致线程饥饿:某些线程可能长期得不到锁,特别是在高并发环境下。
  • 不适用于需要严格顺序访问资源的场景。

乐观锁的实现

乐观锁的实现主要是通过CAS操作和版本号来实现的,CAS操作是一种乐观锁的实现方式,它的实现是基于CPU的原子操作指令。
CAS操作包括了3个操作数:

  • 需要读写的内存位置(V)
  • 进行比较的预期值(A)
  • 拟写入的新值(B)

当一个线程获取锁的时候,会尝试使用CAS操作来获取锁,如果CAS操作失败,那么就会尝试重新获取锁,直到获取锁成功。

版本号机制是在每次修改数据的时候,都会修改版本号,这样在使用CAS进行检查的时候,就可以检查版本号是否发生变化。

乐观锁的优缺点
与悲观锁相比,乐观锁适用的场景受到了更多的限制,无论是CAS还是版本号机制。

例如,CAS只能保证单个变量操作的原子性,当涉及到多个变量时,CAS是无能为力的,而synchronized则可以通过对整个代码块加锁来处理。再比如版本号机制,如果query的时候是针对表1,而update的时候是针对表2,也很难通过简单的版本号来实现乐观锁。

当竞争不激烈 (出现并发冲突的概率小)时,乐观锁更有优势,因为悲观锁会锁住代码块或数据,其他线程无法同时访问,影响并发,而且加锁和释放锁都需要消耗额外的资源。
当竞争激烈(出现并发冲突的概率大)时,悲观锁更有优势,因为乐观锁在执行更新时频繁失败,需要不断重试,浪费CPU资源。

悲观锁的实现

悲观锁的实现主要是通过synchronized关键字来实现的,synchronized是Java中最基本的同步手段,它保证了线程的安全性,但是它的性能开销也是比较大的。

volatile关键字

volatile是Java中的一个关键字,它的主要作用是保证变量的可见性和防止 JVM 的指令重排序。
在并发控制方面,主要是运用乐观锁的一种实现方式,底层有CAS操作,保证了变量的可见性,但是它并不能保证原子性。

synchronized关键字

synchronized是Java中的一个关键字,它的主要作用是保证线程的安全性。
synchronized可以修饰代码块,也可以修饰方法,它的底层是通过monitor对象来实现的,monitor对象是每个Java对象都有的一个对象,它是一个互斥锁,保证了线程的安全性。

加锁对象是谁

  • 实例方法:加锁对象是当前实例对象
  • 静态方法:加锁对象是当前类的Class对象
  • 代码块:加锁对象是括号里面的对象
  • 对象锁:加锁对象是当前对象

Spring框架

Spring的IOC和AOP

  • IOC:控制反转,是一种设计思想,它的核心思想是将对象的创建和对象之间的依赖关系的管理交给Spring容器来管理,而不是由程序员来管理。
  • AOP:面向切面编程,是一种设计思想,它的核心思想是将程序的业务逻辑和系统服务分离,通过对系统服务的封装,将系统服务的功能切面化,然后通过切面的方式将系统服务的功能插入到业务逻辑中。

@Resource和@Autowired的区别是什么?

  • @Resource:是JavaEE提供的注解,它是按照名称进行装配的,如果没有指定name属性,那么它会按照类型进行装配。
  • @Autowired:是Spring提供的注解,它是按照类型进行装配的,如果有多个类型相同的bean,那么它会按照名称进行装配。

什么情况下@Transaction注解不生效?

@Transaction注解不生效的情况主要有两种:一种是在同一个类中,一个方法调用另一个方法,事务注解不生效;另一种是在同一个类中,一个方法调用自己,事务注解不生效。

Spring的事务传播行为

  • PROPAGATION_REQUIRED:如果当前没有事务,就新建一个事务,如果当前存在事务,就加入这个事务。
  • PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入这个事务,如果当前不存在事务,就以非事务执行。
  • PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入这个事务,如果当前不存在事务,就抛出异常。
  • PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,就把当前事务挂起。

计算机网络

tcp三次握手,四次挥手

TCP是面向连接的,可靠的,基于字节流的传输层协议。TCP连接的建立和断开都需要经过三次握手和四次挥手。

TPC的三次握手(建立连接)

  1. SYN:客户端发送一个SYN(同步)包到服务器,以请求建立连接。这个包包含一个随机的序列号A。
  2. SYN-ACK:服务器收到SYN包后,会发送一个SYN-ACK(同步-确认)包到客户端。这个包包含一个自己的随机序列号B,和对客户端序列号A的确认(即A+1)。
  3. ACK:客户端收到SYN-ACK包后,会发送一个ACK(确认)包到服务器。这个包包含对服务器序列号B的确认(即B+1)。

TCP的四次挥手(断开连接)

  1. FIN:当数据传输完毕,客户端会发送一个FIN(结束)包到服务器,请求断开连接。
  2. ACK:服务器收到FIN包后,会发送一个ACK(确认)包到客户端,确认收到了FIN包。但是,服务器可能还有数据需要传输到客户端,所以不会立即关闭连接。
  3. FIN:当服务器数据都传输完毕,会发送一个FIN(结束)包到客户端,请求断开连接。
  4. ACK:客户端收到FIN包后,会发送一个ACK(确认)包到服务器,确认收到了FIN包。然后,客户端会等待一段时间(两个MSL最大段生存时间),确保服务器收到了ACK包,然后关闭连接。

tcp和udp的区别

连接性:TCP是一种面向连接的协议,这意味着在数据传输之前,必须先建立连接。而UDP是无连接的,它只是发送数据,而不关心数据是否到达。

可靠性:TCP提供了数据传输的可靠性。它通过确认(ACK)、重传、错误检测等机制确保数据正确无误地从发送端传输到接收端。而UDP不提供这种可靠性,它只是简单地发送数据,不进行错误检测和修复。

顺序:TCP保证数据的顺序,即数据会按照发送的顺序到达接收端。而UDP不保证数据的顺序,数据报可能会乱序到达。

速度:由于TCP的可靠性和顺序保证机制,它的速度通常比UDP慢。而UDP由于其简单的协议结构,通常比TCP快。

用途:TCP通常用于需要高可靠性的应用,如Web服务器、邮件服务器等。而UDP通常用于对实时性要求高的应用,如视频流、VoIP等,这些应用可以容忍一些数据丢失,但需要快速传输。

头部大小:TCP的头部最小为20字节,而UDP的头部固定为8字节,因此UDP的开销更小。

流控制和拥塞控制:TCP有流控制和拥塞控制机制,可以防止发送端发送速度过快导致接收端或网络无法处理。而UDP没有这些控制机制。

对称加密和非对称加密

  • 对称加密:对称加密是一种加密方式,它使用相同的密钥来加密和解密数据。对称加密的优点是加密和解密速度快,缺点是密钥的传输比较困难。
  • 非对称加密:非对称加密是一种加密方式,它使用一对密钥来加密和解密数据,这对密钥分别是公钥和私钥。公钥用来加密数据,私钥用来解密数据。非对称加密的优点是密钥的传输比较容易,缺点是加密和解密速度比较慢。

https的加密过程

https是基于http的一种安全传输协议,它使用了SSL/TLS协议来加密数据。https的加密过程主要分为两个部分:握手和数据传输。

时序图

sequenceDiagram
    participant Client
    participant Server
    Client->>Server: 请求https
    Server->>Client: 返回证书
    Client->>Server: 生成随机数,使用公钥加密
    Server->>Client: 使用私钥解密
    Client->>Server: 生成对称密钥,使用公钥加密
    Server->>Client: 使用私钥解密
    Client->>Server: 使用对称密钥加密数据
    Server->>Client: 使用对称密钥解密数据

http1.0和http1.1的区别

  • 缓存处理:HTTP1.0需要使用header里的If-Modified-Since来判断缓存是否失效,HTTP1.1使用更多的缓存控制策略,如Entity tag,If-Match,If-None-Match等。
  • 长连接:HTTP1.0需要使用Connection: keep-alive来保持长连接,HTTP1.1默认支持长连接。
  • 分块传输编码:HTTP1.1支持分块传输编码,可以先传输响应头,再传输响应体。
  • Host头处理:HTTP1.1必须要有Host头。
  • 请求管道:HTTP1.1支持请求管道,可以同时发送多个请求,而不需要等待响应。
  • 错误通知的管理:HTTP1.1新增了24个错误状态响应码,如409(Conflict)、410(Gone)等。

JWT是什么,它与其他验证方式的优缺点对比

JWT(JSON Web Token)是一种基于 JSON 的开放标准(RFC 7519),用于在各方之间传输信息。它通常用于身份验证和信息交换。JWT 由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)

优点

  • 自包含性:JWT 是自包含的,所有必要的信息都包含在令牌本身中。这意味着服务器不需要存储会话信息,从而减轻了服务器的负担。
  • 无状态:由于 JWT 是无状态的,服务器不需要在内存中保存会话数据,这使得 JWT 非常适合分布式系统和微服务架构。
  • 跨域支持:JWT 可以在不同的域之间传输,适用于跨域认证场景。
  • 灵活性:JWT 可以携带任意的 JSON 数据,适用于多种场景。
  • 安全性:JWT 可以使用签名和加密技术来确保数据的完整性和保密性。
    缺点
  • 令牌大小:由于 JWT 包含了所有必要的信息,其大小可能会比传统的会话 ID 大,可能会影响传输效率。
  • 不可撤销:一旦 JWT 被签发,就无法撤销,除非在服务器端实现额外的机制来处理令牌的撤销。
  • 安全风险:如果密钥管理不当,JWT 可能会被伪造。此外,JWT 通常

Redis

Redis的数据结构

Redis支持五种数据结构:String、List、Set、Hash、ZSet。
此外,还有三种特殊的数据结构:HyperLogLog、Geo、Stream。

为什么Redis速度快

  • Redis是基于内存的,内存的读写速度非常快。
  • Redis是单线程的,避免了线程切换和锁的开销。
  • Redis在网络IO方面使用了多路复用技术,可以处理多个客户端的请求。

讲一下缓存三兄弟(缓存穿透、缓存击穿、缓存雪崩)

  • 缓存穿透:指的是查询一个不存在的数据,由于缓存中没有,所以每次都会去数据库中查询,这样会导致数据库压力过大。
  • 缓存击穿:指的是一个热点数据突然失效,导致大量请求直接打到数据库上,这样会导致数据库压力过大。
  • 缓存雪崩:指的是缓存中的大量数据同时失效,导致大量请求直接打到数据库上,这样会导致数据库压力过大。

缓存穿透的解决方法

  1. 布隆过滤器:将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉。
  2. 空对象缓存:如果一个查询返回的数据为空(不管是数据不存在还是数据被删除),我们也可以将这个空结果进行缓存,但是需要设置一个较短的过期时间。

缓存击穿的解决方法

  1. 设置热点数据永不过期。
  2. 加互斥锁:在缓存失效的时候,加互斥锁,只允许一个线程去查询数据库,其他线程等待。

缓存雪崩的解决方法

  1. 加互斥锁:在缓存失效的时候,加互斥锁,只允许一个线程去查询数据库,其他线程等待。
  2. 设置不同的过期时间:不同的数据设置不同的过期时间,避免大量数据同时失效。

Redis的持久化机制(RDB和AOF)

Redis的持久化机制有两种:RDB快照和AOF日志。

  • RDB快照:是指在指定的时间间隔内将内存中的数据保存到磁盘上,生成一个快照文件。RDB快照是一个二进制文件,它保存了某个时间点上的所有数据。(有点像mysql的binlog)
  • AOF日志:是指在每次写操作时,将写操作的命令追加到AOF文件的末尾。AOF日志是一个文本文件,它保存了所有的写操作命令。(像mysql的redolog)

Redis宕机哪种恢复的快

RDB快照恢复的速度比AOF日志快,因为RDB快照是一个二进制文件,它保存了某个时间点上的所有数据,所以恢复速度比较快。

Redis的大key问题怎么解决

Redis的大key指的是在Redis中某个key的value非常大,这样会导致Redis总体的内存占用过大,同时影响Redis的性能。

解决方法

  1. 分布式:将大key拆分成多个小key,分布在不同的key上。例如,如果一个 hash 结构的数据量过大,可以将其拆分成多个 hash;
  2. 压缩:对大key进行压缩,减小内存占用。
  3. 分页:对大key进行分页,分多次获取。
  4. 将数据分布到多个 Redis 实例中,以减小单个实例的内存压力和操作延迟。可以使用 Redis Cluster 或者客户端分片来实现。
  5. 对于不再需要的数据,定期进行清理,避免数据量持续增长。可以使用 Redis 的过期机制(TTL)来自动清理过期数据。

Redis集群的三种模式

Redis集群有三种模式:主从复制、哨兵模式、集群模式。这些也是Redis的高可用方案。

  • 主从复制:主从复制是指一个主节点可以有多个从节点,主节点的数据会同步到从节点上,从节点可以用来读取数据。
  • 哨兵模式:哨兵模式是指在主从复制的基础上,增加了哨兵节点,哨兵节点用来监控主节点和从节点的状态,当主节点宕机时,会自动将从节点切换为主节点。
  • 集群模式:集群模式是指多个Redis节点组成一个集群,每个节点都是一个主节点,每个主节点都有多个从节点,数据会分片存储在不同的节点上。

哨兵模式的选举机制

哨兵模式的选举机制是通过投票的方式来选举主节点。Raft算法是一种分布式一致性算法,它的核心思想是通过投票的方式来选举主节点。

Raft算法的选举过程主要分为两个阶段:选举阶段和日志复制阶段。

MySQL

MySQL的索引有哪些

MySQL的索引按照物理存储维度划分主要有以下几种:

  • B+树索引:最常见的索引类型,适用于等值查询、范围查询、排序查询。
  • 哈希索引:适用于等值查询,不适用于范围查询和排序查询。
  • 全文索引:对文本的内容进行分词,进行搜索。目前只有 CHAR、VARCHAR ,TEXT 列上可以创建全文索引。一般不会使用,效率较低,通常使用搜索引擎如 ElasticSearch 代替。

MySQL的索引按照应用维度划分主要有以下几种:

  • 主键索引:加速查询 + 列值唯一(不可以有 NULL)+ 表中只有一个。
  • 普通索引:仅加速查询。唯一索引:加速查询 + 列值唯一(可以有 NULL)。
  • 覆盖索引:一个索引包含(或者说覆盖)所有需要查询的字段的值。
  • 联合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并。
  • 全文索引:对文本的内容进行分词,进行搜索。目前只有 CHAR、VARCHAR ,TEXT 列上可以创建全文索引。一般不会使用,效率较低,通常使用搜索引擎如 ElasticSearch 代替。

MySQL的事物是基于什么实现的

MySQL的事务是基于InnoDB存储引擎实现的,InnoDB存储引擎是MySQL的默认存储引擎,支持事务、行级锁、外键等特性。

对于事务的实现,InnoDB存储引擎主要通过以下几个方面实现:

  • 事务日志: InnoDB存储引擎通过事务日志(redo log)实现事务的持久性,当事务提交时,将事务的操作记录到事务日志中,保证事务的持久性。
  • MVCC: InnoDB存储引擎通过多版本并发控制(MVCC)实现事务的隔离性,通过保存数据的多个版本,实现事务的隔离性。
  • 锁机制: InnoDB存储引擎通过行级锁实现事务的隔离性,通过锁机制保证事务的隔禽性。

事务日志

InnoDB存储引擎通过事务日志(redo log)实现事务的持久性,当事务提交时,将事务的操作记录到事务日志中,保证事务的持久性。

MySQL的日志主要包括:

  • 错误日志:记录MySQL的错误信息。
  • 查询日志:记录MySQL的查询信息。
  • 慢查询日志:记录MySQL的慢查询信息。
  • 事务日志:记录MySQL的事务信息。
  • 二进制日志:记录MySQL的数据更改信息。

其中比较重要的是事务日志二进制日志,事务日志用于实现事务的持久性,二进制日志用于实现数据的备份和恢复。

归档日志(binlog)

MySQL的归档日志(redo log)是MySQL的二进制日志,用于记录MySQL的数据更改信息,主要用于数据的备份和恢复。

而 binlog 是逻辑日志,记录内容是语句的原始逻辑,类似于“给 ID=2 这一行的 c 字段加 1”,属于MySQL Server 层。

他有三种格式

  • Statement 格式:记录的是 SQL 语句,如 insert、update、delete 等。
  • Row 格式:在Statement的基础上,记录的是行的内容,如记录的now()的具体数值。
  • Mixed 格式:根据具体的 SQL 语句来决定使用哪种格式。

重做日志(redo log)

MySQL的重做日志(redo log)是InnoDB存储引擎的事务日志,用于实现事务的持久性,主要用于事务的恢复。

redo log 是物理日志,记录的是“在某个数据页上做了什么修改”,属于 InnoDB 存储引擎层。

回滚日志(undo log)

MySQL的回滚日志(undo log)是InnoDB存储引擎的事务日志,用于实现事务的隔离性,主要用于事务的回滚。

MVCC

MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。

MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读。

InnoDB存储引擎通过多版本并发控制(MVCC)实现事务的隔离性,通过保存数据的多个版本,实现事务的隔离性。

MVCC主要通过以下几个方面实现:

  • 保存数据的多个版本:InnoDB存储引擎保存数据的多个版本,每个版本对应一个时间戳。
  • 读取数据的版本控制:读取数据时,根据事务的隔离级别,读取对应的版本。
  • 写入数据的版本控制:写入数据时,根据事务的隔离级别,生成新的版本。

MVCC主要用于实现事务的隔离性,通过保存数据的多个版本,实现事务的隔离性。

锁机制

InnoDB存储引擎通过行级锁实现事务的隔离性,通过锁机制保证事务的隔离性。

MySQL的锁主要有两种:共享锁和排他锁。

  • 共享锁:是一种读锁,它允许多个事务同时对同一行数据进行读操作,但是不允许对同一行数据进行写操作。
  • 排他锁:是一种写锁,它不允许其他事务对同一行数据进行读操作和写操作。

索引有哪些种类

MySQL的索引按照数据结构维度可分为:

  • B+树索引:是一种多路搜索树,是一种平衡的多路搜索树,能够保持数据稳定有序,其插入和查询的时间复杂度都是O(logN)。
  • 哈希索引:是一种哈希表,能够快速定位到数据,其插入和查询的时间复杂度都是O(1)。
  • 全文索引:是一种全文搜索索引,能够对文本进行全文搜索。
  • R-Tree索引:是一种空间索引,能够对空间数据进行搜索。

按照底层数据结构维度可分为:

  • 聚类索引:是一种按照数据存储的物理顺序进行排序的索引。
  • 非聚集索引:是一种按照数据存储的逻辑顺序进行排序的索引。

按照索引的应用维度可分为:

  • 主键索引:是一种唯一索引,能够保证数据的唯一性。
  • 普通索引:是一种普通的索引,能够提高查询速度。
  • 唯一索引:是一种唯一索引,能够保证数据的唯一性。
  • 覆盖索引:是一种覆盖查询索引,能够减少查询的IO操作。
  • 联合索引:是一种联合索引,能够提高查询速度。
  • 全文索引:是一种全文搜索索引,能够对文本进行全文搜索。

Mysql索引优缺点

优点

  1. 提高查询速度:索引可以大大提高查询速度。
  2. 加速表和表之间的连接:通过索引可以加速表和表之间的连接。
  3. 唯一性约束:可以保证表中每一行数据的唯一性。

缺点

  1. 索引会占用磁盘空间:索引会占用磁盘空间。
  2. 索引会降低写操作的速度:因为每次写操作都需要更新索引。
  3. 索引会降低查询速度:因为每次查询都需要查找索引。

什么时候能用索引

  • 经常作为查询条件的字段,如果需要同时查找多个字段,可以建立联合索引
  • 经常放到GROUP BY或者ORDER BY后面的字段,进行GROUP BY或者ORDER BY都会对数据进行排序,这些字段就可以建立一个索引,这样就不需要每次都进行排序了

什么情况下不可以使用索引?

  • 不经常作为查询条件的字段
  • 不经常放到GROUP BY或者ORDER BY后面的字段
  • 重复率高的字段,比如性别,建立索引并不会明显提高查询效率,毕竟索引也需要占空间。
  • 表数据量很少的时候,全表查也非常快,你创建维护索引反倒需要开销。
  • 经常需要更新的字段,如果建立了索引,索引也需要频繁维护(B+树是要保证数据有序的),会影响数据更新的效率。

怎么检测索引是否被使用

可以通过show status命令来查看索引的使用情况。

1
2
SHOW STATUS LIKE 'Handler_read%';

也可以通过explain命令来查看SQL语句的执行计划。

1
EXPLAIN SELECT * FROM table WHERE id = 1;

MySQL联合索引

MySQL的联合索引,也被称为复合索引或多列索引,是一种在多个列上创建的索引。联合索引可以提高查询性能,特别是在你需要从多个列中检索数据时。

联合索引的工作原理是,它会根据索引中的列顺序,首先对第一列进行排序,然后在每个相同的第一列值中,对第二列进行排序,以此类推。因此,你可以在查询中使用任何前缀列(即索引中的第一列,第一列和第二列,等等)来利用联合索引。

如果有a,b,c 组合为联合索引,查询条件是a和b,这个查询会不会用到索引,a和c呢

  • 查询条件是a和b,这个查询会使用到联合索引a,b,c。
  • 查询条件是a和c,这个查询不会使用到联合索引a,b,c。

联合索引的使用基于最左前缀原则,即查询条件必须是联合索引的最左边的列的子列,才能使用到联合索引。同时最左匹配原则会一直向右匹配直到遇到范围查询(>、<) 就会停止匹配。对于 >=、<=、BETWEEN、like 前缀匹配这四种范围查询,并不会停止匹配。
如(a,b),查询条件是a和b,会使用到索引;查询条件是b,不会使用到索引。

如果存了个NULL,这个NULL会走索引吗

NULL值不会走索引,因为NULL值在索引中是不存储的。

如果回表过多该怎么解决

“回表”指的是当查询使用了非聚簇索引(也叫辅助索引或二级索引)时,MySQL 需要先通过索引查找到主键值,然后再通过主键值回到聚簇索引(也就是数据表本身)获取所需的完整记录。回表操作增加了查询的I/O开销,可能会导致性能下降。

1
2
3
4
CREATE INDEX idx_name ON table_name(user_id,order_id);

SELECT * FROM table_name WHERE user_id = 1; // 查询全部字段要回表查询
SELECT user_id FROM table_name WHERE user_id = 1 ; // 只查询user_id不需要回表查询

解决方法

  1. 覆盖索引:覆盖索引是指索引包含了查询所需的所有字段,不需要回表查询。
  2. 调整查询:调整查询的字段,只查询需要的字段,避免回表查询。

什么是索引下推

索引下推(Index Condition Pushdown, ICP)是一种优化技术,用于减少数据库在查询过程中访问表的次数,从而提高查询性能。这个技术是 MySQL 5.6 引入的,主要用于辅助索引扫描(即二级索引扫描)时的查询优化。

索引下推的概念

传统的索引扫描:在 MySQL 5.6 之前,查询过程中即使某些查询条件可以在索引中评估,MySQL 也会先通过索引查找到主键值,然后再回表(即访问数据行)进行剩余的条件过滤。
索引下推:MySQL 5.6 引入的索引下推技术,允许在扫描索引时,将部分查询条件推送到索引扫描过程中进行评估。只有那些在索引扫描过程中满足条件的记录,才会进行回表操作。这大大减少了回表次数,提高了查询性能。

索引下推的工作机制

当一个查询使用了索引下推,MySQL 会在索引的叶子节点中先应用查询条件,过滤掉不符合条件的记录,然后再将符合条件的记录的主键值用于回表查找数据行。
例如,对于 WHERE 子句中的某些过滤条件,MySQL 可以在索引扫描时就直接应用这些条件,而不是在回表后再应用。

索引下推的优势

减少回表次数:通过在索引扫描阶段应用部分过滤条件,减少了回表次数,从而降低了I/O操作的开销。
提高查询效率:尤其在大表查询中,索引下推能够显著减少需要读取的数据行数,从而提高查询效率。

MySql的四个隔离级别以及默认的隔离级别

MySQL的四个隔离级别分别是:读未提交(Read Uncommitted)、读提交(Read Committed)、可重复读(Repeatable Read)、串行化(Serializable)。

  • 读未提交(Read Uncommitted):允许一个事务读取另一个事务未提交的数据。
  • 读提交(Read Committed):允许一个事务读取另一个事务已提交的数据。
  • 可重复读(Repeatable Read):保证在同一个事务中多次读取同样的数据时,数据是一致的。
  • 串行化(Serializable):最高的隔离级别,它通过强制事务串行执行,避免了幻读的问题。

默认的隔离级别是:可重复读(Repeatable Read)。

隔离等级区分

脏读 不可重复读 幻读
读未提交
读提交
可重复读
串行化

脏读

脏读是指一个事务读取到另一个事务未提交的数据,导致数据不一致。

不可重复读

不可重复读是指一个事务多次读取同一数据,但是在读取的过程中,另一个事务对数据进行了更新,导致多次读取的数据不一致。

幻读

幻读是指一个事务多次读取同一数据,但是在读取的过程中,另一个事务对数据进行了插入或删除,导致多次读取的数据不一致。

MySQL的事物是基于什么实现的

MySQL的事务是基于InnoDB存储引擎实现的,InnoDB存储引擎是MySQL的默认存储引擎,支持事务、行级锁、外键等特性。

对于事务的实现,InnoDB存储引擎主要通过以下几个方面实现:

  • 事务日志: InnoDB存储引擎通过事务日志(redo log)实现事务的持久性,当事务提交时,将事务的操作记录到事务日志中,保证事务的持久性。
  • MVCC: InnoDB存储引擎通过多版本并发控制(MVCC)实现事务的隔离性,通过保存数据的多个版本,实现事务的隔离性。
  • 锁机制: InnoDB存储引擎通过行级锁实现事务的隔离性,通过锁机制保证事务的隔禽性。

事务日志

InnoDB存储引擎通过事务日志(redo log)实现事务的持久性,当事务提交时,将事务的操作记录到事务日志中,保证事务的持久性。

MySQL的日志主要包括:

  • 错误日志:记录MySQL的错误信息。
  • 查询日志:记录MySQL的查询信息。
  • 慢查询日志:记录MySQL的慢查询信息。
  • 事务日志:记录MySQL的事务信息。
  • 二进制日志:记录MySQL的数据更改信息。

其中比较重要的是事务日志二进制日志,事务日志用于实现事务的持久性,二进制日志用于实现数据的备份和恢复。

归档日志(binlog)

MySQL的归档日志(binlog)是MySQL的二进制日志,用于记录MySQL的数据更改信息,主要用于数据的备份和恢复。

而 binlog 是逻辑日志,记录内容是语句的原始逻辑,类似于“给 ID=2 这一行的 c 字段加 1”,属于MySQL Server 层。

他有三种格式

  • Statement 格式:记录的是 SQL 语句,如 insert、update、delete 等。
  • Row 格式:在Statement的基础上,记录的是行的内容,如记录的now()的具体数值。
  • Mixed 格式:根据具体的 SQL 语句来决定使用哪种格式。

重做日志(redo log)

MySQL的重做日志(redo log)是InnoDB存储引擎的事务日志,用于实现事务的持久性,主要用于事务的恢复。

redo log 是物理日志,记录的是“在某个数据页上做了什么修改”,属于 InnoDB 存储引擎层。

回滚日志(undo log)

MySQL的回滚日志(undo log)是InnoDB存储引擎的事务日志,用于实现事务的隔离性,主要用于事务的回滚。

他有三个隐藏的字段,分别是

  • trx_id:事务的ID
  • roll_ptr:回滚指针,指向回滚段中的某个回滚段
  • db_row_id:行的ID
    trx_id 和 db_row_id 用于标识一行数据,roll_ptr 用于标识回滚段中的某个回滚段。

MVCC

InnoDB存储引擎通过多版本并发控制(MVCC)实现事务的隔离性,通过保存数据的多个版本,实现事务的隔离性。

MVCC主要通过以下几个方面实现:

  • 保存数据的多个版本:InnoDB存储引擎通过保存数据的多个版本,实现事务的隔离性。
  • 通过版本号实现事务的隔离性:InnoDB存储引擎通过版本号实现事务的隔离性,通过版本号判断数据的可见性。
  • 通过undo log实现事务的隔离性:InnoDB存储引擎通过undo log实现事务的隔离性,通过undo log记录数据的修改前的版本,实现事务的回滚。
  • 通过回滚段实现事务的隔禽性:InnoDB存储引擎通过回滚段实现事务的隔离性,通过回滚段记录数据的修改前的版本,实现事务的回滚。
  • 通过锁机制实现事务的隔离性:InnoDB存储引擎通过锁机制实现事务的隔离性,通过锁机制保证事务的隔离性。

MySQL的锁机制

Mysql的锁按照粒度可以分为:

  • 全局锁
  • 表级锁
  • 行级锁

全局锁

MySQL的全局锁是一种用于保护整个数据库实例的锁。它是MySQL中最高级别的锁,也是最强大的锁之一。

全局锁的作用是在备份、恢复或执行某些维护任务时保护数据库的完整性。当全局锁处于激活状态时,其他会话无法对数据库做任何写操作,只能进行读操作。

全局锁的特点包括:

  • 全局性:全局锁会锁定整个数据库实例,而不是单个表或行。
  • 独占性:全局锁是互斥的,一次只能由一个会话持有。
  • 高优先级:全局锁的优先级很高,其他锁无法与之竞争。

全局锁的使用场景包括:

数据库备份:在进行数据库备份时,需要保证备份数据的完整性,可以使用全局锁来锁定整个数据库,确保不会有其他会话对数据库进行写操作。
数据库恢复:在进行数据库恢复操作时,为了保证恢复的完整性,可以使用全局锁来防止其他会话对数据库进行修改。
维护任务:在进行需要独占数据库资源的维护任务时,可以使用全局锁来确保任务的顺利进行。

用法

1
FLUSH TABLES WITH READ LOCK;

表级锁

MySQL表锁是一种用于控制并发访问数据库表的机制。当多个会话同时对同一个表进行读写操作时,可能会发生数据不一致或数据丢失的问题。为了解决这个问题,MySQL引入了表锁来确保数据库的一致性。

MySQL提供了两种类型的表锁:共享锁(Shared Lock,也称S锁)和排他锁(Exclusive Lock,也称X锁)。

  • 共享锁(Shared Lock):多个会话可以同时获取共享锁,并且可以同时读取表中的数据,但是不能进行写操作。共享锁适用于并发读取操作,它可以防止其他会话获取到排他锁,从而保证读操作的一致性。
  • 排他锁(Exclusive Lock):只有一个会话能够获取到排他锁,并且可以进行读写操作。排他锁适用于更新和删除操作,它可以防止其他会话同时对表进行读写操作,确保数据的一致性和完整性。

MySQL表锁的使用方法如下:

  • 在需要对表进行读操作的地方,可以使用共享锁(LOCK TABLES tableName READ)。
  • 在需要对表进行写操作的地方,可以使用排他锁(LOCK TABLES tableName WRITE)。
  • 使用完毕后,需要释放锁(UNLOCK TABLES)。

用法

1
2
3
LOCK TABLES table_name READ;
LOCK TABLES table_name WRITE;
UNLOCK TABLES;

行级锁

MySQL数据库的行级锁是一种用于并发控制的锁机制,只有在innoDB引擎中有,它可以在多个会话同时访问同一张表的不同行时提供数据一致性和并发性保证。

行级锁是MySQL中最细粒度的锁,它只锁定表中的一行数据,而不是整个表或页面。这意味着其他会话可以同时访问同一张表的其他行,而不会被阻塞。

行级锁同样有两种类型:共享锁(S锁)和排他锁(X锁)。

  • 共享锁(S锁):也称为读锁,多个会话可以同时获取S锁,并且互不干扰。多个会话获取S锁后,可以并发地读取被锁定的行数据,但无法修改或删除这些行。只有当所有S锁都被释放后,其他会话才能获取X锁进行修改操作。
  • 排他锁(X锁):也称为写锁,只有一个会话可以获取X锁,并且其他会话无法获取任何锁。获取X锁的会话既可以读取被锁定的行数据,也可以修改或删除这些行。只有当X锁被释放后,其他会话才能获取任何锁。

默认情况下,InnoDB在REPEATABLE READ(可重复读)事务隔离级别运行,InnoDB使用 next-key锁进行搜索和索引扫描,以防止幻读。

1.针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁。

2.InnoDB的行锁是针对于索引加的锁,不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,此时就会升级为表锁。

索引失效的情况有哪些

  • 模糊查询的前导通配符: 当使用模糊查询(如 LIKE ‘%abc’)时,索引失效,因为通配符在前面会导致索引无法使用。
  • 未使用索引字段进行过滤: 如果查询条件没有使用到创建的索引字段,数据库可能不会使用该索引。
  • 数据类型不匹配: 如果查询条件的数据类型与索引字段的数据类型不匹配,数据库无法使用索引。
  • 使用函数操作: 如果查询条件中对字段进行了函数操作(如 LOWER(column)),索引可能失效,因为数据库无法直接使用索引。
  • OR 运算: 在 OR 运算中,如果其中一个条件使用了索引,而另一个条件没有使用索引,整个查询可能会导致索引失效
  • 使用 NOT 运算: NOT 运算通常会使索引失效,因为数据库无法使用索引来高效处理 NOT 运算。
  • 表连接中的索引失效: 如果在表连接查询中,连接条件中的字段没有索引,可能导致索引失效。

为什么数据库不使用红黑树或者AVL树作为索引结构

数据库不使用红黑树或者AVL树作为索引结构的原因主要有以下几点:

  • 平衡二叉树的高度不稳定: 红黑树和AVL树是平衡二叉树,插入和删除操作可能导致树的高度发生变化,从而导致树的平衡性受到影响。而B+树是多路搜索树,插入和删除操作不会导致树的高度发生变化,保持了树的平衡性。
  • 磁盘IO的特性: 数据库索引通常存储在磁盘上,磁盘IO是数据库性能的瓶颈。B+树的多路搜索树结构可以减少磁盘IO次数,提高查询性能。而红黑树和AVL树的高度不稳定,可能导致磁盘IO次数增加,降低查询性能。
  • 范围查询的效率: B+树的多路搜索树结构适合范围查询,可以通过中序遍历实现范围查询。而红黑树和AVL树的平衡性可能导致范围查询效率低下。
  • 存储空间的利用率: B+树的多路搜索树结构可以提高存储空间的利用率,减少磁盘IO次数。而红黑树和AVL树的高度不稳定,可能导致存储空间的浪费。

RabbitMQ 消息队列

RabbitMQ如何解决数据丢失问题,如何保证消息一致性

  • 消息持久化:RabbitMQ 提供了消息持久化功能,可以将消息持久化到磁盘上,即使 RabbitMQ 重启,消息也不会丢失。
  • 生产者确认机制:RabbitMQ 提供了生产者确认机制,可以确保消息成功发送到 RabbitMQ 服务器。QOS 预取机制:RabbitMQ 提供了 QOS 预取机制,可以确保消费端消费的消息数量是可控的。
  • 消费端确认机制:RabbitMQ 提供了消费端确认机制,可以确保消息成功消费。
  • 事务机制:RabbitMQ 提供了事务机制,可以确保消息的生产和消费是原子性的。

算法

排序

快排

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void quickSort(std::vector<int>& nums, int left, int right) {
if (left >= right) {
return;
}

int i = left, j = right;
int pivot = nums[left];

while (i < j) {
while (i < j && nums[j] > pivot) {
j--;
}
if (i < j) {
nums[i++] = nums[j];
}

while (i < j && nums[i] < pivot) {
i++;
}
if (i < j) {
nums[j--] = nums[i];
}
}

nums[i] = pivot;
quickSort(nums, left, i - 1);
quickSort(nums, i + 1, right);
}

LRU

设计模式

单例模式

饿汉式

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {
// 在类加载时创建实例
private static final Singleton instance = new Singleton();

// 私有构造函数,防止外部实例化
private Singleton() {}

// 提供全局访问点
public static Singleton getInstance() {
return instance;
}
}

懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton {
// 静态变量保存单例实例
private static Singleton instance;

// 私有构造函数,防止外部实例化
private Singleton() {}

// 提供全局访问点
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

双锁单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton {
private static volatile Singleton instance;

private Singleton() {}

public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

常见八股文集合
https://www.liahnu.top/2024/03/20/常见八股文集合/
作者
liahnu
发布于
2024年3月20日
许可协议