高频面试题

1. ArrayList的扩容机制

  • ArrayList的默认初始化容量是10,底层是使用一个Objcet[]来存放元素,向ArrayList添加元素的时候,先让Size(集合所包含元素个数)+1,如果Size+1大于底层Object[]的长度的话就触发扩容(扩容使得新数组容量是旧数组容量的1.5倍,如果新容量大于Integer.MAX_VALUE-8,就让新容量指向 Integer.MAX_VALUE),最后调用 Arrays.copyOf(elementData, newCapacity)在旧数组的基础上复制出一个以新容量为长度的新数组作为ArrayList的底层数组。
    //默认初始化容量
    private static final int DEFAULT_CAPACITY = 10;

   //添加元素有可能会触发扩容,效率较低
    public boolean add(E e) {
        //进行写操作之前让Size+1再检查底层数组容量是否可用,同时modCount++
        ensureCapacityInternal(size + 1);
        elementData[size++] = e;//检查通过后,再添加元素到数组
        return true;
    }

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    } 

    private static int calculateCapacity(Object[] elementData, int minCapacity)     {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//该集合被修改得次数加1
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);//如果给定得容量大于底层数组的容量,就进行扩容
    }
    //扩容方法
    private void grow(int minCapacity) {
        // overflow-conscious code
        //先拿旧数组容量
        int oldCapacity = elementData.length;
        //在旧容量的基础上扩容百分之50,得到新容量
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            //计算出来的新容量如果小于给定容量就让新容量指向给定容量
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            //如果新容量大于Integer.MAX_VALUE-8,就让新容量指向 Integer.MAX_VALUE
            newCapacity = hugeCapacity(minCapacity); 
        elementData = Arrays.copyOf(elementData, newCapacity);
      }
    

2. 为什么HashMap的加载因子是0.75?

  • 加载因子的作用在于判断map是否需要扩容,而扩容是一个比较耗时的操作。
    • 如果加载因子过小,比如 loadFactor=0.5f,初始容量=16的情况下,那么元素超过16*0.5=8个就会扩容到32,假设添加第9个元素成功,那么就会有23个数组位置空闲,空间利用率大概为9/23=39%,因此频繁的扩容不仅耗时而且浪费空间。
    • 如果加载因子过大,比如loadFactor=1.0f,初始容量=16的情况下,那么元素超过16个才会触发扩容,虽然空间利用率大,但是桶中元素的冲突肯定会变多,元素越来越多之后会影响到map的查询时间效率。
    • 总结:loadFactor=0.75f 是空间和时间成本的一种折中(1+0.5)/ 2 = 0.75
  • HashMap相关源码分析

3. HashMap什么时候链表转红黑树?

  • 1.哈希桶上的链表节点个数必须大于等于8
  • 2.可转红黑树的最小哈希表长度必须要大于等于64,如果小于64会进行扩容
// 哈希桶上的链表节点个数必须大于等于8
static final int TREEIFY_THRESHOLD = 8;
// 可转红黑树的最小数组长度必须要大于等于64
static final int MIN_TREEIFY_CAPACITY = 64;
// 哈希桶上的链表节点个数 小于等于6 将重新变回链表
static final int UNTREEIFY_THRESHOLD = 6;
  • 为什么哈希桶上的链表节点个数必须大于等于8才进行转红黑树?

    • 官方做过很多测试,发现链表长度在大于8以后再出现hash碰撞的可能性几乎为0,虽然转化为红黑树后,查找的效率会比链表高,但是转化红黑树这个过程是耗时的,而且在扩容时还要对红黑树重新的左旋右旋保持平衡,相对耗时,所以,阈值设置为8就是为了尽量减少hashmap中出现红黑树(hashmap中链表才是常态)
  • 为什么哈希表长度大于等于64才对哈希桶进行转红黑树呢?

    • 因为链表转变为红黑树的目的是为了解决链表过长,导致查询和插入效率慢的问题,而如果要解决这个问题,也可以通过数组扩容,把链表缩短也可以解决这个问题。所以在数组长度还不太长的情况,可以先通过数组扩容来解决链表过长的问题。

4. JDK1.7 HashMap存在的问题以及1.8的优化?

  • 1.7存在问题一: HashMap随着数据量的增加会使得哈希表上的链表长度过长,进而导致查询效率降低。

  • 1.8优化问题一: 链表长度大于8且哈希表长度大于等于64的的时候会被转化成红黑树,以此来提升查询效率,如果链表长度再次小于6会重新变回链表。

  //当单个位置的数据链表长度超过 8 的时候会将该链表转换为红黑树
    static final int TREEIFY_THRESHOLD = 8;
    //当当前位置的红黑树内容长度小于 6 的时候会重新变回链表
    static final int UNTREEIFY_THRESHOLD = 6;
    //当某个位置的链表在转换为红黑树的时候,如果此时数组长度小于 64 会先进行扩容
    static final int MIN_TREEIFY_CAPACITY = 64;
  • 1.7存在问题二: 多线程下,扩容使用头插法导致循环链表问题。
    使用头插法的原因:是因为HashMap的编写人员认为新加入的数据更常用,新加入的元素放在前面会更容易查询到。
  • 1.8优化问题二: 采用了尾插法防止了环形链表的产生,扩容后节点顺序不会反掉,不存在死循环问题。
  • 1.7 单线程 扩容正常转移节点
 oldTable :  table[0]   a -> b -> c 
 newTable:   table[0]   c -> b -> a
 Entry<K,V> e 
 Entry<K,V> next

​                    e   =  a             e  = b                      e = c

​                    next ->  b          next - > c                  next  -> null 

​                    a.next = null       b.next = a                  c.next = b

​                    newTable[0] =  a    newTable[0]  = b            newTable[0] = c

  链表结果 :         a -> null           b -> a  -> null              c -> b -> a -> null
  • 1.7 多线程 扩容异常导致循环链表
// jdk 1.7 扩容时节点转移方法
void transfer(Entry[] newTable) {
      Entry[] src = table; 
      int newCapacity = newTable.length;
      for (int j = 0; j < src.length; j++) { 
          Entry<K,V> e = src[j];           
          if (e != null) { // B1: 两个线程都先进入if
              src[j] = null; 
              do { 
                  Entry<K,V> next = e.next; 
                 int i = indexFor(e.hash, newCapacity);// 设a b c 都落在同一个桶上
                 e.next = newTable[i]; // B2:线程1歇脚点
                 newTable[i] = e;  
                 e = next;             
             } while (e != null);
         }
     }
 }


已有:线程1 + 线程2 并发执行
前提条件:原来的桶中链表元素都 落在 新哈希表的同一桶上
假设2个线程都添加了元素,都触发了扩容机制,然后都构建了自己的Entry[] newTable
    1.两个线程都进入B1,都进了if代码块。
    2.此时线程2放弃了CPU执行权,线程1继续执行
    3.线程1执行到B2(未执行),线程1被挂起
    4.此时线程1: e->a 、next->b
    5.线程2再次获取到CPU执行权,构建新哈希表的某个链表节点newTable[0]
    6.线程2 newTable2[0] -> b -> a 到这个步骤后再次丢失执行权
    7.线程1抢到CPU执行权,继续执行B2步骤代码  
       a.next=null; newTable1[0]=a; e=b;
       再次循环: Entry<K,V> next = b.next; //即 next指向了a
                 b.next = a;  
                 newTable1[0] = b;  
                 e = a;
       再次循环: a.next = newTable1[0] 即  a.next = b       
       到此循环链表形成:b -> a -> b

5. mysql索引的底层数据结构?

数据结构模拟插入
mysql的锁机制

  • B+树B树的基础上,为所有的叶子节点加上了链式指针,所有叶子节点形成了一个从小到大的有序的链表,大大加快了区间查找的效率,同时非叶子节点不存储data只做索引作用,所有的叶子的节点都存储了完整的数据行信息。
  • B树(B-)不管是 叶子节点 还是 非叶子节点 都存储了data,要进行范围查询的话需要进行递归遍历,因此B树的区间查找效率是比较低的。
    在这里插入图片描述

6. 线程池原理?

线程池总结

  • 三大核心:核心线程数 + 任务阻塞队列 + 最大线程数
    在这里插入图片描述

7. Spring Bean 的生命周期?

在这里插入图片描述

  • 1.Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化。
  • 2.Bean实例化后对将Bean的引用和值注入到Bean的属性中。
  • 3.如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法。
  • 4.如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入。
  • 5.如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来。
  • 6.如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法。
  • 7.如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用。
  • 8.如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法。
  • 9.此时Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。
  • 10.如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用。

8. spring的bean是线程安全的吗?

  • 在spring中,并没有提供Bean的线程安全策略,因此spring容器中的bean并不具备线程安全的特性。

  • 考虑Spring Bean的线程安全与否要结合Bean的Scope作用域来分析,常见的作用域就是singleton和prototype。

  • prototype(多例bean): 对于prototype中作用域中的bean,因为每次getBean的时候,都会创建一个新的对象,线程之间不存在Bean共享问题,因此prototype作用域下的bean不存在线程安全问题。

  • singleton(单例bean)(默认作用域): 对于singleton作用域中的bean,所有的线程都是共享一个单例实例的bean,因此是存在线程安全问题的,但是如果单例bean是一个无状态的bean,即多线程操作中不会对bean的成员变量进行查询以外的操作(不存在多个线程同时写这个成员变量的场景),那么这个单例bean就是线程安全的。

    • 有状态的bean:就是有实例变量的对象,可以保存数据,是非线程安全的。
    • 无状态的bean:就是没有实例变量的对象,不能保存数据,是线程安全的。
  • 解决方案:

    • 1.最简单的解决方案就是将有状态的bean的作用域由singleton改为prototype。
    • 2.在类中定义一个ThreadLocal的成员变量,将需要可变的成员变量保存在ThreadLocal中,采用ThreadLocal解决线程安全问题,为每一个线程提供一个独立的变量副本,不同线程只操作自己线程上的副本变量(推荐)。

9. Spring AOP原理?

  • Spring AOP的动态的代理有两种实现方式,分别为JDK Proxy 和Cglib动态代理。
    • 如果被代理的类实现了接口,那么将采用JDK Proxy,核心是InvocationHandler,Proxy.newProxyInstance。
    • 如果被代理的类没有实现接口,那么将使用Cglib动态代理。
      • Cglib全称 Code Generation Library,是一个强大的代码生成类库,
        它可以在程序运行时动态生成某个类的子类 ,所以Cglib是通过继承的方式做的动态代理,因此如果某个类被final修饰,那么它是无法使用Cglib做动态代理的。

10. Spring事务的实现原理?

  • Spring事务底层实现:数据库连接 Connection 和 AOP的方法拦截。
    • 1.Spring对使用了@ Transactional注解的Bean,会为这个Bean开启代理对象作为SpringBean。
    • 2.当调用代理对象的方法时,首先会判断该方法上是否加上了@ Transactional注解,如果有那么就被AOP拦截,则利用事务管理器创建一个数据库连接对象Connection ,并且修改Connection的autocommit自动提交事务属性为false,改为手动提交事务模式。
    • 3.执行业务代码中的sql更新/插入/删除语句,无异常直接提交事务,出现异常就会回滚事务。

11. 什么是Spring的循环依赖?Spring是怎么解决这个问题的?

  • 循环依赖的形成?简单来说就是a中注入了b,b中又注入了c,c中又注入了a。
// @Service
public class A {
    // @Autowired
    private B b;
    public A() {
    }
    public B getB() {
        return b;
    }
}

// @Service
public class B {
  // @Autowired
    private C c;
    public B() {}
    public C getC() {
        return c;
    }
}

// @Service
public class C {
   // @Autowired
    private A a;
    public C() {}
    public A getA() {
        return a;
    }
}
  • Spring的解决思路:三级缓存(为什么需要三级缓存?每一级缓存有何意义?)
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {

    ## 成品单例池:存放的对象已经完成 属性注入 和 后续的一些钩子 ,表示Bean已经被完整的创建,
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
    
    ## 单例生产工厂:创建Bean的原始工厂,表示Bean还未实例化
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
    
    ## 半成品单例池:表示Bean已经实例化,但是并没有完成属性注入 和钩子 等等
    private final Map<String, Object> earlySingletonObjects = new HashMap(16);
    
    @Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        //todo 成品单例池
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
            synchronized(this.singletonObjects) {
                //todo 半成品单例池
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    //todo 单例生产工厂
                    ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }

        return singletonObject;
    }
}
  • 模拟解决循环依赖问题:分为 反射实例化 和 属性注入,当被注入的属性对象没有被构建,就先递归构建被注入的对象(反射实例化后添加到半成品缓存)。
	// 临时存放半成品(属性注入还没完成的) Bean === earlySingletonObjects
	private static final Map<String,Object> cacheMap  = new HashMap<>(3);
	
	// 存放 属性注入完成 的Bean === singletonObjects
	private static final Map<String,Object> WZBeanMap = new HashMap<>(3);
	
	@SuppressWarnings("unchecked")
	private static <T> T getBean(Class<T> beanClass) throws Exception {
	         // 用类名的小写 简单代替 bean的命名规则
	         String beanName = beanClass.getSimpleName().toLowerCase();
	         // 如果存在半成品的Bean或者完整的Bean,那么直接返回
	         if (cacheMap.containsKey(beanName) || WZBeanMap.containsKey(beanName)) {
	             T result;
	             return  (result = (T)cacheMap.get(beanName)) != null ? result: (T) WZBeanMap.get(beanName);
	         }
	
	         // todo 将对象本身实例化
	         T object = beanClass.newInstance();
	         // 放入缓存
	         cacheMap.put(beanName, object);
	        // 把所有字段当成需要注入的bean,创建并注入到当前bean中
	         // todo 属性注入
	         Field[] fields = object.getClass().getDeclaredFields();
	         for (Field field : fields) {
	             field.setAccessible(true);
	             Class<?> fieldClass = field.getType();// 获取需要注入字段的class
	             String fieldBeanName = fieldClass.getSimpleName().toLowerCase();
	             // 如果需要注入的bean,已经在缓存Map中,那么把缓存Map中的值注入到该field即可
	             // 如果缓存没有 递归继续创建
	             field.set(object,   cacheMap.containsKey(fieldBeanName)
	                               ? cacheMap.get(fieldBeanName)
	                               : getBean(fieldClass));
	         }
	         WZBeanMap.put(beanName,object);
	         cacheMap.remove(beanName);// 属性填充完成 移除掉缓存中的bean
	         // 属性填充完成,返回
	         return object;
	}
  • 模拟初始化Bean
 public static void main(String[] args) throws Exception {
 
        // 假装项目初始化实例化所有bean
        A a  = getBean(A.class);
        B b  = getBean(B.class);
        C c  = getBean(C.class);

       // 初始化完成后 检查
        System.out.println(a.getB() == b);
        System.out.println(b.getC() == c);
        System.out.println(c.getA() == a);
        // 重复调用 检查
        System.out.println("=================================");
        System.out.println(getBean(A.class).getB() == getBean(B.class));
        System.out.println(getBean(B.class).getC() == getBean(C.class));
        System.out.println(getBean(C.class).getA() == getBean(A.class));
}

	//控制台输出
	true
	true
	true
	=================================
	true
	true
	true
  • debug查看循环依赖解决情况: 已经完美解决循环依赖问题。
    在这里插入图片描述

12. SpringBoot 自动配置原理?

  • ① SpringBoot启动类中的@SpringBootApplication注解中包含了@EnableAutoConfiguration注解。

  • ② @EnableAutoConfiguration注解中包含了@Import(AutoConfigurationImportSelector.class)。

  • ③ AutoConfigurationImportSelector类中–>selectImports()方法。

  • ④ selectImports()方法中–>getCandidateConfigurations方法。

  • ④ getCandidateConfigurations方法中–>SpringFactoriesLoader.loadFactoryNames方法。

  • ⑤ SpringFactoriesLoader.loadFactoryNames方法中–>loadSpringFactories方法。

  • ⑥ loadSpringFactories方法中–classLoader.getResources(“META-INF/spring.factories”)

  • ⑦ 扫描所有jar包路径下的spring.factories文件

  • ⑧ 找到spring.factories文件,获取文件中列表清单,加载自动配置类。实现自动配置。

  • ⑨ 自动配置类是否生效又取决于条件注解。如@ConditionalOnBean,@ConditionalOnMissingBean,@ConditionalOnClass, @ConditionalOnProperty

在这里插入图片描述

13. 怎么理解java面向对象三大特性之一的多态?

  • 面向接口编程,方便扩展,方法入参使用接口提高了方法的兼容性。

14. synchronized关键字底层原理是什么?

  • synchronized底层原理
  • monitor,它内置在每一个对象中,任何一个对象都有一个monitor与之关联,synchronized在JVM里的实现就是基于进入和退出monitor来实现的,底层则是通过成对的MonitorEnter和MonitorExit指令来实现,因此每一个Java对象都有成为Monitor的潜质。所以我们可以理解monitor是一个同步工具。
    • monitorenter,如果当前monitor的进入数为0时,线程就会进入monitor,并且把进入数+1,那么该线程就是monitor的拥有者(owner)。
    • 如果该线程已经是monitor的拥有者,又重新进入,就会把进入数再次+1。也就是可重入的。
    • monitorexit,执行monitorexit的线程必须是monitor的拥有者,指令执行后,monitor的进入数减1,如果减1后进入数为0,则该线程会退出monitor。其他被阻塞的线程就可以尝试去获取monitor的所有权。
    • 使用 javap -v 绝对路径\Test.class 反编译 Test.class 为 jvm指令。
public class Test {
    static long count = 0;
    public static final  Object LOCK = new Object();
    public void lock(){
        synchronized (LOCK){
            count++;
            System.out.println("count = " + count);
        }
    }
}
  public void lock();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=3, args_size=1
         0: getstatic     #2                  // Field LOCK:Ljava/lang/Object;
         3: dup
         4: astore_1
         5: monitorenter
         6: getstatic     #3                  // Field count:J
         9: lconst_1
        10: ladd
        11: putstatic     #3                  // Field count:J
        14: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        17: new           #5                  // class java/lang/StringBuilder
        20: dup
        21: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
        24: ldc           #7                  // String count =
        26: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/
String;)Ljava/lang/StringBuilder;
        29: getstatic     #3                  // Field count:J
        32: invokevirtual #9                  // Method java/lang/StringBuilder.append:(J)Ljava/lan
g/StringBuilder;
        35: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/la
ng/String;
        38: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/Str
ing;)V
        41: aload_1
        42: monitorexit
        43: goto          51
        46: astore_2
        47: aload_1
        48: monitorexit
        49: aload_2
        50: athrow
        51: return
  • monitorexit指令出现了两次,第1次为同步正常退出释放锁;第2次为发生异常退出释放锁;
  • synchronized锁的优化
    • JDK1.5之前,synchronized是属于重量级锁,重量级需要依赖于底层操作系统的Mutex Lock实现,然后操作系统需要切换用户态和内核态,这种切换的消耗非常大,所以性能相对来说并不好。
    • JDK1.6之后,开始对synchronized进行优化,增加了自适应的CAS自旋、锁消除、锁粗化、偏向锁、轻量级锁这些优化策略。锁的等级从无锁,偏向锁,轻量级锁,重量级锁逐步升级,并且是单向的,不会出现锁的降级。

15. Lock锁的底层原理是什么?

  • CAS + AQS
  • CAS :(expect 期待值) + (内存中的值 )+ (update 待修改的值,新值 )
    • 当且仅当 expect = 内存中的值 ,才将内存中的值修改为 update。
  • AQS:内部维护一个被volatile修饰的锁状态变量state和一个双向链表,state初始值为0,如果某个线程利用CAS将state从0成功设置成了1,当前线程也被设置成了锁的拥有者,那么就加锁成功。加锁失败就将当前线程放入链表的尾部,然后阻塞,等待其他线程释放锁之后将自己唤醒,被唤醒之后就开始竞争CPU执行权了。

16. java反射的原理?Class.forName的加载机制?

  • 首先 .java 文件通过JDK编译成 .class 字节码文件,程序运行的时候,jvm会去调用业务逻辑对应需要的字节码文件,生成对应的Class对象,并调用其中的属性和方法完成业务逻辑。
  • 而java反射则是在程序运行期间,主动让jvm取加载某个 .class 文件生成Class对象,并调用其中的方法属性。
  • Class.forName的加载机制 >>> jvm的类加载机制

17. ElasticSearch的倒排索引?

18. 线上内存溢出问题怎么排查?

  • ①先去线上下载日志,如果查看错误日志能够定位到哪个类对象导致内存溢,那么我们只需要针对问题修改bug就好。
  • ② 线上下载 慢查询日志,查看超时的SQL是不是出现了全量查询等。
  • ③ 但是很多时候我们单凭日志无法定位出内存溢出问题,此时就需要我们使用JMAP获取到JVM堆内存的快照dump文件,然后再利用 MAT 或者jdk自带的 jvisualvm.exe 来分析dump文件来定位大内存类对象。
  • 线上出现OOM的原因:
    • ① 查询丢失条件出现全量查询导致OOM
    • ② 树查询死循环导致OOM
  • 线上内存溢出问题排查步骤

19. 项目中什么情况下会导致mysql的锁等待过程?

  • 一个事务A当中的更新SQL先锁住了一行,随后执行了一个慢查询,紧接着 另外一个事务尝试更新事务A中锁的行,就会造成锁等待的情况发生,因为此时 事务A还没提交。
# 现有A事务 锁住了一行  事务不提交
START TRANSACTION;
UPDATE bbc t set t.area = area+1 where t.`name` = 'Albania';
 select 慢查询导致事务未提交...
 
# 新事务B 更新这一行的时候 就触发了 锁等待的一个问题
START TRANSACTION;
UPDATE bbc t set t.area = area+1 where t.`name` = 'Albania';

S1. 事务的概念是什么?

  • 事务本质是一组具有原子性的SQL操作(要么全部成功,要么全部失败)。

S2. 事务的特性有几个?

  • ACID
  • ① 原子性(atomicity):指处于同一个事务中的多条SQL语句是不可分割的,要么全部提交成功,要么全部提交失败回滚。
  • ② 一致性(consistency):事务必须使数据库从一个一致性状态变换到另外一个一致性状态。比如转账,转账前两个账户余额之和为2k,转账之后也应该是2K。
  • ③ 隔离性(isolation):指多线程环境下,一个事务所做的修改在最终提交以前,对其它事务是不可见的。
  • ④ 持久性(durability):事务一旦提交,则其所做的修改就会永久保存到数据库中。

S3. 对数据库进行操作时可能存在的问题有哪些?

  • ① 脏读:指一个线程中的事务读取到了另外一个线程中事务未提交的数据。
  • ② 不可重复读:指一个线程中的事务读取到了另外一个线程中事务提交的update的数据,读取到之前查询一次,读取到之后查询一次,两次查询结果不一样。
  • ③ 幻读:指的是当A事务在读取某个范围内的记录时,B事务又在该范围内插入了新的记录,当A事务再次读取该范围的记录时,会产生幻行(指一个线程中的事务读取到了另外一个线程中事务提交的insert数据)。

S4. 什么是事务的隔离级别?事务的隔离级别有几个?

  • 概念: 指的是一个事务对数据的修改与另一个并发的事务的隔离程度。当多个事务同时访问相同数据时,如果没有采取必要的隔离机制,就可能发生脏读,不可重复读和幻读的问题。
  • ① READ UNCOMMITTED 读未提交(最低级别)
  • ② READ COMMITTED 读已提交 >>> 解决了脏读
  • ③ REPEATABLE READ 可重复读 (MySQL的默认) >>> 解决了脏读和不可重复读
  • ④ SERIALIZABLE 可串行化 (最高级别) >>> 解决了脏读,不可重复读和幻读
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值