JAVA基础
1.数组和集合的区别

2.一个类初始化时候调用机制

3.重载和重写的区别

4.讲解一下JAVA中的多态
从对象多态(接收参数类型)-编译做,运行右
和方法多态(重载和重写)
两个方面展开说
5.Exception和Error
Exception:属于可以处理异常分为CheckedException(可检查,编译就出现的错误,IDE发达背景下,几乎不会出错)和RunTimeException(运行后出现的异常,我们一般自定义异常都是基础它,比如数组下标越界等)
Error:我们无法处理的异常,比如OOM(OutOfMemory)
6.抽象类和接口区别
从单继承多实现,关键字不同implements 和 exetends,abstract class 和 interface
is a 和like 关系
再到 接口只有public static final属性
而抽象类就是普通类里面可以有抽象方法,所以什么修饰符都可以有
接口无构造方法,抽象类有构造方法(两者都无法实例化)
线程
1.线程的生命周期,线程的状态?
线程有五大状态,创建 就绪 阻塞 运行 死亡
阻塞又分为三种阻塞
等待阻塞,线程被wait(),放弃所有资源,这样的线程会放入睡眠池,需要notify()唤醒
同步阻塞,锁池中等待锁释放
其他阻塞,sleep()不会放弃我们的锁资源,等待IO等其他阻塞

JAVA集合
JAVA集合里面除了HashMap和HashSet的链表是单链表,其他所有链表都是双向链表方便我们操作数据
1.集合结构

2.ArrayList和LinkedList区别

3.ArrayList扩容机制
ArrayList底层使用数组来实现扩容
刚开启我们创建时候初始容量为0,第一次添加数据扩容到10(我们也可以自定义这个初始长度)
当我们添加到第10时候就会开始扩容,底层用Arrays.copyof将我们原数组复制到新数组
每次扩容都是上一次的1.5倍
4.HashSet以及HashMap扩容机制
记住加载因子是0.75,我们的长度永远是2^n
初始长度默认为16(2^4)
或者一条链表超过8个会强制扩容(直接扩容),且数组长度大于16就会红黑树化
HashMap和HashSet在1.7之前为头插法,1.8后为尾插法

5.Set集合

6.LinkedHashSet
有first和last的双向链表(尾插)
取出顺序和插入顺序一样

7.同时重写HashCode和equals
hashCode和equals
1、hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;
2、如果两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode一定要相同;
3、如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点;
4、两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”
8.HashMap和HasTable区别

9.concurrentHashMap


10.为什么HashMap允许key和value为null,但是HashTable和ConcurrentHashMap不允许?
假设允许插入null值
我们就会有歧义
取值为null1.本身就是null2.没有查到为null
HashMap是给单线程用的,我们可以通过containsKey(key)判断我们这个null是查到的null还是没有这个返回的null
但是多线程环境下,你查key查到了null,你要使用containsKey(key)判断,假设之前没有,则应该返回false,但是你判断前正好有个线程put(key,value),这样就返回了true
这样就区分不了是查到了null还是本身就不存在这个key
多线程
1.ThreadLocal及其内存泄漏?
可以认为ThreadLocal为每个线程属于自己的变量,其他线程访问不到
调用Set或Get方法时会先获取当前线程的Thread,然后获取线程对应的ThreadLocalMap
然后把我们的ThreadLocal作为参数传到map的key中,获取/存储对应value


ThreadLocalMap里面的ThreadLocal指向堆中对象的引用是弱引用
当我们的栈中ThreadLocal被弹出栈(一般就是线程结束时候会这样),对应的强引用不存在
然后我们堆中threadLocal就会被垃圾回收掉(现在只有弱引用了),相当于ThreadLocalMap的Key没了
但是我们value是强引用不会被清除
我们的Value和Thread线程声明周期绑定
!!但如果你这时候使用了线程池,线程池本来就是讲究线程复用,不会删除线程!!
所以这个value就会一直存在导致内存泄漏

下面是使用
我们只建一个ThreadLocal,他可以存到不到线程的map里面做key,很多引用都指向这个threadlocal


2.concurrentHashMap
concurrentHashMap相当于HashMap的多线程安全版本
先介绍一下HashMap的数据结构-哈希表(数组大于64且链表长度大于8红黑树化记得提)

那它是怎么实现并发安全的呢?
JDK1.7和1.8版本后有不同的视线
JDK1.7前会将多个数组索引成一个Segemnt(片段),然后给这个Segemnt加锁,来保证我们的效率的同时又减少堆内存的资源占用(几个链表同用一个锁)

JDK1.8后使用CAS+同步锁(Synchorized)来实现
当我们对应的数组索引为空(无元素)会用CAS(CAS无锁操作效率高)来执行写操作
当我们有元素的话就会使用Synchorized实现串行写入

而并发读取
不会加锁,而会使用volatile修饰保证数据可见性
3.CAS机制及ABA问题
CAS原名CompareAndSwap,是JavaUnsafe里面的一个方法,是一种多线程编程中用到的操作,来保证并发编程中的原子性,本身类似乐观锁,避免我们的锁机制带来的开销


4.并发三大特性
原子性,可见性,
原子性:保证一个操作的的完整性,在该操作执行完前cpu不会调度(不会被中断),要么一次全执行完,要不不执行**(一个操作是不可分割的)**
拿i++举例,i++执行有四步
当线程1和线程2,线程1执行到 i=i+1(工作内存),线程2调度进来,也执行最后使主存i=1
然后线程1再从那个中断点就行运行,也会使主存中的i=1
相当于执行两次i++但只有一次的效果


可见性
就算实现原子性也不能完全就是线程安全比如这个第四步是由操作系统决定,无法加入到我们原子性操作里面(用锁锁住)
可见性:当多个线程访问同一个变量的时候,一个线程修改了该变量的值,其他线程就能立即看到
顺序性
5.线程池
线程池的作用和参数解析
线程池类似于连接池吧
可以实现线程复用,降低我们开辟线程和销毁线程时的资源消耗,提高我们的响应速度,直接获取线程,同时又可以对我们的线程进行一个同一管理
线程池又很多参数

线程池处理流程

线程池中阻塞队列的作用,为什么先添加到队列中,而不是先创建到最大线程?
当我们阻塞队列里面的任务存满的时候,我们新的任务不会说直接舍弃(除非你采用了特定的拒绝策略),阻塞队列会通过阻塞保留我们想要入队的任务,直到有空间可以插入新的任务。
例如,常见的阻塞队列有 LinkedBlockingQueue(无界队列)和 ArrayBlockingQueue(有界队列)。在 ArrayBlockingQueue 中,当队列满时,提交任务的线程会被阻塞,直到有空间可以插入新的任务。
阻塞队列还可以在没有任务(完全无任务)的时候,队列没有,且核心线程也没执行时,使核心线程进入阻塞态(wait等待池)释放系统资源,而当有任务来时,会唤醒我们等待池里面的线程,相当于notify(),让线程回到就绪态;
主要还是创建和销毁线程会影响整体效率,而添加到队列代价小

线程复用原理
线程复用就是将我们的线程和任务做一个解耦的操作,线程是线程,任务是任务
之前我们用线程都是继承Thread或者实现Runnable接口,重写run方法,调用start()方法,会创建一个新的线程来执行run方法,这样就会以线程绑定任务来执行
而我们的线程池相当于执行一个"循环任务",这个循环任务就是检查是否有任务需要被执行,如果有则直接执行对应的run方法(作为一个普通方法区执行),通过这种方法就可以将固定的线程和所有任务的run方法关联起来
使用线程池需要注意什么?
我们使用线程池不要使用JDK自带的Executors获取线程池
因为他所获取的线程池里面的参数不合理(前两阻塞队列最大长度无界(Integer.value)后一个最大线程是无界的)

一般我们都是需要自己new一个threadPoolExecutor,指定里面的参数

线程池的几种状态
shutdown和stop区别记一下
还有几个状态记一下

Spring
1.说一下Spring的理解
Spring就是一个轻量级的框架,其主要功能就是IOC控制反转(解耦)和AOP面向切面编程,封装JavaBean,并且管理各种Bean的声明周期
2.对于AOP的理解
AOP在我认为就是一个Spring提供的增强机制吧
AOP不仅可以对应相同的代码的同一个功能来进行提取转化为一个切面来通知,比如日志,也可以对应开发完成的方法进行功能的增强,比如缓存处理
AOP里面核心概念就是切点,通知,切面(切点+通知),连接点
我们就是可以通过切点匹配需要增强的方法,再通过通知的方式增强代码,比如我们进行统一日志管理吧,就可以在通知里面调用log.info()打印日志信息
然后当我们使用AOP的方法呗IOC容器注入时,会是一个代理对象
包含我们AOP里面的方法吧
我们的Spring事务底层就是用来我们的AOP

3.对于IOC的理解
从IOC是什么
到Bean声明方式,到如何进行依赖注入
到功能 解耦

4.BeanFactory和ApplicationContext有什么区别?
ApplicationContext继承了我们的BeanFactory
BeanFactory使用延迟注入的方式,只有某个bean被调用的时候,才会加载实例化,这样就难以发现Spring配置问题,比如我们bean里面的一个属性没有注入,就会抛出异常
ApplicationContext会在我们容器启动的时候创建所有的非懒加载的bean,这样有利于检查我们所依赖的属性是否注入,会提供运行时候的效率(启动会比较慢,且占用空间会比BeanFactory多)
5.SpringBean的生命周期
首先的话
我们先是通过xml以及注解等方式获取我们要创建的bean
然后将对应bean的beanDefination放入我们容器的BeanDefinitionMap中
然后就是遍历集合进行bean的创建(以下我将会采用单个bean的声明周期来进行讲解)
我这里大致分为五步
(1).实例化(构造对象)
通过我们的BeanDefinition获取到类的构造方法进行构造,(无参可以省略这一步=>然后准备构造参数,就通过class类进行查找),实例化完成
(拿取构造方法先拿@AutoWired,都无先拿无参构造方法)
(2).属性填充
填充我们对象内部用@AutoWired注入的这些依赖
会通过三级缓存机制进行填充(这里可以提一下循环依赖)
Spring三级缓存
缓存查询
实例化
属性填充
初始化
缓存转移
(3).初始化
首先初始化容器相关信息,为实现了各种aware接口(beanNameaware等等)及其方法的设置对应的入beanName,beanFactory等容器信息
然后我们会
初始化声明周期的回调方法
这个回调方法有三种实现方式,对应有一定顺序
BeanPostProcesser意思:Bean的后置处理器,主要是用来创建动态代理对象 的before方法
然后调用我们实现InitialingBean的接口方法
再调用我们自己定义的初始化方法,需要对init-method进行赋值
最后调用我们BeanPostProcesser的after方法
(4).使用bean
(5).销毁bean
销毁bean也可能有回调方法
比如我们实现DisposableBean接口实现对应方法就会调用该方法
指定destory-method后,会再调用对应方法方法
然后完成销毁


6.Spring支持的Bean作用域
我对这方面了解不是很多,我感觉主要讲一下
singleton 和 prototype就可以

7.Spring框架中的单例bean是线程安全的吗?
bean默认单例,所有线程共享,但其内部是没有封装对应多线程一个处理的
即其不是线程安全的
这个安不安全主要是看我们就是是否有状态(就是存储数据)
比如说我的UserService里面有一个属性count,每次我增添用户count++
多线程cpu调度会让这个值和我们总的用户值可能不相同
我们一般建议用ThreadLocal来保存这个线程变量来封装数据
8.FactoryBean和BeanFactory不同
两个都是接口
BeanFactory以Factory结尾,是一个工厂,用来创建bean对象的工厂,主要方法就是通过getBean来获取bean对象
FactoryBean本身就是一个bean,但本身具有factory功能,当一个类实现factorybean,就要实现对应的getObject()方法,最终注册到Spring容器中的bean是我们的getObject()方法的返回的对象
比如我们的mybatis的mapper接口就是通过factorybean的getObject()创建动态代理来返回bean
9.Spring框架用了哪些设计模式?



10.Spring事务的实现方式原理及其隔离级别
我们使用@Transactional
的时候底层是AOP,创建动态代理对象
会在我们开始方法前begin开启事务
当我们有异常的时候会调用aop的afterThrowing方法进行rollback回滚
当我们运行完成就会commit提交事务
隔离级别和我们的数据库的隔离级别是一致的有四种哈
从上往下效率越来越低
不指定默认使用数据库的默认隔离级别
mysql对应repeatable read(可重复读),oracle对应read commited


11.Spring事务传播机制
主要介绍一下什么是事务传播,以及Required和Required_new区别

Spring中事务传播机制是指
就是你a方法开启事务,同时里面调用b方法,b方法也开启事务
那么b方法是新建事务还是加入到a方法的事务呢?

12.Spring的事务什么时候会失效
解释一下这个第一点
(1)比如UserService里面两个方法,用了@Transactional方法A
在方法B用this.A()调用,事务会失效,因为此时是调用UserService本身对象的方法
不是AOP创建代理对象的方法,所以需要把this去掉解决
(2)(3)不解释
(4)加深了AOP的创建在IOC的基础上
(5)你自己catch异常了对应的事务就不会回滚事务就会失效

12.什么是bean的自动装配,有哪些方式?
实话这个感觉现在不常用应该不会考
问的话你就说
@AutoWired装配默认安装类型注入
这里会存在冲突就是一个接口注入但多个实现类
可以配合我们的@Qualifier指定bean名字
或者@Primary(有个属性1-10,表注入优先级,默认为5)指定对应的实现类,优先级高的会先注入
或者用@Resource指定bean名字注入
13.Spring、SpringMVC、SpringBoot这三者之间有什么区别?
基本按照图片中的来
Spring:IOC和AOP
SpringMVC:主要对应我们的web解决方案,mvc就是一个model-view-controller的一套解决方案,我们最原始的方式就是Servlet+JSP的一套解决方案,关于Spring这块的理解我觉得就是使用@RequestMappring来指定请求路径作为controller,view层交给我们前段(以前是jsp),model层就是我们建立的各种pojo类
SpringBoot:底层是我们的Spring,采用约定大于配置,实现了自动配置的一个更加快速的开发包吧算是。

14.SpringMVC的工作流程
首先用户发起请求
请求会被DispatcherServlet拦截下来,然后转发给HandlerMapping,HandleMapping负责解析请求,根据对应url等信息找到匹配的controller类对应方法(有拦截器的话会先执行preHandle方法)
找到对应方法,把请求参数httpservlet/httpResponse传给Controller的方法里
controller方法执行完后返回一个ModelAndView(包含视图名称和模型数据)
最后视图解析器根据名字找到视图再将数据模型传到视图中再渲染成html返回给客户端
15.SpringMVC的主要组件(九大组件)
把HadleMapping和HandleAdapter讲出来就可以
HadleMapping接口,可以有不同实现,主要存储我们的url和handler之间的关系,通过url找handler(处理器)
HandlerAdapter,因为我们的handler有不同类型,比如说注解类型定义,servlet等,不同类型有不同的操作逻辑,所以我们针对于不同类型的handler有不同的适配器,适配器线判断handler类型,然后调用不同操作逻辑

16.Springboot自动配置的原理
SpringBoot启动类注解中封装了@EnableAutoConfiguration
这个注解内封装了@Import({AutoConfigurationImportSelector.class})
@Import可以导入三种类,
1.普通类直接注册为bean,
2.配置类里面@bean标志方法返回值注册为bean,
3.实现了SelectorImport接口的类,里面ImportSelector里面的返回值对应全类名都会注册为bean
而我们就是用了第三种,AutoConfigurationImportSelector这个类就实现了SelectorImport接口
而里面的ImportSelector方法返回值会去读所有项目(不管是你依赖的也好,自己的也好)
META-INF下spring.factories文件内的类名
而这些类名就是第三方提供,想让我们注册为bean的类,一般是以AutoConfiguration为后缀的配置类,里面定义了各种bean
这样就实现了自动配置
Spring底层(自动配置)
17.如何理解SpringBoot的Starter
starter称为起步依赖吧
起步依赖一般不含java代码
里面最重要功能就是配置文件中的依赖项目
比如说myabtis就会帮我们导入所有mybatis需要用的依赖,就可以不用一个一个导入
一般我们的starter会依赖上一个autoconfig的jar包,这个autoconfig的包里面就会定义我们的自动配置类
一般就是定义做一个@configuration的类里面多个@bean修饰的方法,返回值为我们想要注入IOC容器的类
最后将这个类全类名加入我们的MET-INF的sping.factories
达到一个自动配置的效果

18.什么是嵌入式服务器?
就是tomact内嵌到springboot中,不用像之前一样我们下载tomact,然后将程序打成jar包,部署到我们的tomact中,那么繁琐,同时也有利于我们测试
Mybatis
1.Mybatis的优缺点
主要就是我们比较JDBC简便,不用自己手动注册驱动,获取连接,释放连接一系列操作
相当于值拿出来了我们的编写sql语句的这部分让我们手动写
而且自己编写sql的话很灵活,在sql优化方面也会很方便
而且可以在我们的xml里面编写sql,做到了解耦的一个操作
还有动态sql的支持也做的很好,例如where标签,update标签,set标签
但是我们自己写sql工作量还是很大的,很多简单的sql其实是一些类机械性的操作,但是对应的sql语句需要一一编写,影响开发小
所以就会有myabtisplus和hibernate这种框架的出现吧
MybatisPlus是在mybatis上做简单查询的增强
hibernate则是一个完全的ORM框架,通过我们对象和表结构匹配,自动生成sql

2.mybatis和hibernate的对比(不常考)
mybatis对于表结构进行一个设计(先设计表,再创建匹配的对象)
hibernate则是真正面向对象,先去创建对象,然后设计表
所以对于hibernate我们只需要面向我们的pojo类就可以完成数据库操作,不用关注数据库的表结构
开发速度:简单CRUD的项目,hibernate肯定是更快的,复杂大型(复杂sql多)的话其实两者效率差不多,还是hibernate快一点,但是mybatis的结构,xml里面写sql,更易于我们维护和更改


3.#{}和${}的区别是什么?
简单提一下JDBC
#{}是预编译sql底层用preparedStatement,不会出现sql注入,一般sql语句里面的参数用改符号(不会加单引号)
${}是拼接sql就是底层是statement,会有sql注入,一般一些简单的参数表明等用该符号(会加单引号)

4.mybatis插件运行原理及开发流程
不太懂
mybatis的插件指定就是mybatis的拦截器
比如我们的分页拦截器,注册分页拦截器(mp那个操作返回mybatis拦截器)
Mysql
1.索引的基本原理
索引相当于我们每本书的目录
通过索引可以更快的查找到我们需要查找的数据
无索引的话需要整张表进行遍历
索引的原理:把无序的查询变成有序的查询
有索引查询快,但是也会增加我们增添和删除和更新数据的效率(因为你还要对目录进行修改)
2.mysql索引结构,各自优劣
先说一下二叉树和平衡二叉树和B树
普通二叉树:随着新节点不断增加数据全插到一边,查询对应底层数据就类似单链表,效率会低
平衡二叉树
这样会好一点,但是这样第n层只能存储2的(n-1)次方个节点
1000个节点需要10层
而每次向下一层走都需要一次IO操作(读取数据然后比较,相等找到,小于向左,大于向右)
这样就会效率就不高,主要是一个节点只有两个分支,存储数据太少
B树
简而言之就是多叉平衡树,树的高度就会降低,IO操作较少就可以找到数据
但是B树查询效率不太稳定,有些在根节点,或者根节点附近就能找到的,查询就快,有些在叶子节的数据,查询就慢
且不适合做范围查找,可能你查找(16-23)
就会在b树不同层级节点进进出出

B+树(存储引擎:InnoDB)和哈希索引(存储引擎)
B+树就是把所有数据放在叶子节点,解决查询不稳定问题
然后因为非叶子节点不存储数据,对应空间可以存储指向其他节点的指针就能分更多的叉
也可以树的高度压的更低,进一步减少IO次数
最后将叶子节点之间用指针连接起来(双向),解决范围查询(只要找到一个叶子节点,然后顺着指针查,类似循环链表)

哈希索引
哈希索引底层的数据结结构是哈希表
创建索引就是创建一个hash表呗(你可以理解构建一个hashmap)对应的key是对应创建索引字段
通过key的hash值(%hash表长度)确定存储的数组下标,hash冲突,组成链表
对应的value是我们的行数据的地址(反正就是可以通过这个value直接找到我们的对应数据)

优缺点:
优点:不需要类似B+树从根节点往下找,直接算hash值找到对应索引,从链表直接找就可以
等值查询有优势(where name = “XXX”)
缺点:
范围查询速度会相当慢,因为类似于我们hashmap,他是无序的,你用范围查询比如where age<=10 and age>=8
相当于查完8再查9再查10,相当慢,而我们那个B+树查到8根据双向链表就可以查到9或者10有没有(next节点>10就没有,因为是相当于有序链表)
模糊查询like 也不适合,因为模糊查询本质上是范围查询
哈希索引页不支持多列联合索引的最左匹配原则;(这个目前不知道)
哈希索引在有大量的哈希冲突的时候,查询效率也是比较低的,相当于单链表嘛
3.聚簇索引和非聚簇索引的区别?
我先介绍一下这两种索引:
两者都为B+树结构,存储引擎对应索引策略不一样,innodb的主键(第一个唯一索引/隐藏列)是聚簇,其他的是非聚簇,MyISAM就是非聚簇索引(无聚簇索引)
和我之前的想法正好相关联
我之前想每一个叶子节点的会存储对应的行数据吗?
这个就解决了我的问题
聚簇索引,比如我们的id你根据where id = 8
他去查找找到id为8会直接返回我们的行数据
什么索引为聚簇索引
聚簇索引不用我们自己创建,默认为主键,无主键,默认第一个unique索引为聚簇索引
如果连unique都没有,他会创建一个DB_ROW_ID的隐藏列作为聚簇索引
Innodb的索引存储方式就是聚簇索引

非聚簇索引
非叶子节点和聚簇索引一样
但我你叶子节点存放的不是对应的行数据,而是我们的的行数据的地址
根据这个地址去取得我们的行数据(数据和索引不存储在同一个地方)

4.Mysql中锁的类型有哪些?
5.Mysql执行计划?
Mysql执行计划就是sql执行的查询顺序,以及如何使用索引查询,返回的结果集行数
(就是查看sql查询是怎么执行的方便我们优化)
使用方法
EXPLAIN Select * from A where x=?
执行完该sql
出现一张表
下图有该表字段,而我们要学习的就是看字段来判断我们sql怎么优化

下面没提到的字段不重要
id每一次查询语句占一行内容,因为我们查询里面可能有子查询/关联查询,就可能有两次查询
占两行
select_type就可以来标注当前查询是子查询还是子查询的外查询还是关联查询
table 对应查询的表
type:sql优化关键字段

key:查询真正走的索引的名字
rows:按照当前执行计划进行会读取的数据行数sql优化就是把row降低
filtered:返回行数/读取行数 百分比也是查询指标,越大越好
6.!!!事务基本特性和隔离级别!!!
基本特性:ACID 分别对应 原子性 一致性 隔离性 持久性
-
原子性:一个事物要么全成功要么全失败(一个原子,不可分割)
A转账给B,A扣钱和B加钱要同时成功保证成功,不能只扣A的前不加B -
一致性:一致性是我们事务的目的(我觉得叫正确性更好),事务使数据库从一个一致性状态变换到另外一个一致性状态
比如A有500B有100,这是一个一致性状态X,A给B转100,A400B200这是另一个一致性状态Y,而我们的事务就是要将状态转化到状态Y -
隔离性:多个用户并发执行事务,每一个事务不能被其他事务操作数据所干扰,多个事务相互隔离
单独保证原子性还无法满足一致性需求
比如A转B100的时候,C也给B转100,A还没提交事务,C就访问B了取得B=100,然后A提交B=200,C再提交使B=200,导致我们B只+了100(读未提交可以改变这种情况) -
持久性:一旦提交事务,所做的修改会永久保存数据库中(就是会写入数据库中,不能回滚)
这个没啥好说的(让我联想到sentinel的全局事务XA模式,如何在事务提交后又恢复之前的状态)

隔离级别有四种
需要根据不同的业务场景去使用不同隔离级别
分别是
- 读未提交 read uncommit 产生脏读现象
脏读:A事务使age=10,B可以读到未提交中A修改的数据age=10就叫脏读
脏读大多数业务场景不符 - 读已提交 read commit 产生不可重复读现象(解决脏读) --Oracle默认隔离级别
不可重复读:A 修改值为500(A事务提交) C 读取 500(不提交) B 修改值为200(提交) C再次读取值变成200
这样C事务过程中读取一个值有不同的结果,这种现象叫不可重复读(数据也可能被删除)
不可重复读大多数业务场景还可以,因为读到的数据确实是正确的,虽然说前后两次读到不一样 - 可重复读 repeatable read 产生幻读(解决不可重复读)–mysql默认隔离级别
解决方式:你读取后你记录那个值A下次就算有别的事务更改了A你也不变(记录下来重复用)
幻读:就是你本来想前后一样不是吗,A读数据X表中id>=1,假设现在只有id=1,B向X添加数据id=2,A再读取的时候会出现B增加的数据,不符合可重复读的思想(虽然也是读已提交,但是前后数据不一样了)可以配合间隙锁解决 - 串行 serializable 一般不会使用,给每一行读取的数据加锁,导致大量超时和锁竞争问题
7.什么是MVCC?
读未提交不用MVCC,我们的操作本身就是修改了数据中的数据,不用MVCC本来就能达到ru效果(MVCC存在才实现了我们的rr和rc)
MVCC-多版本并发控制,无锁实现我们的rr和rc
读取数据后将数据保存下来,这样读锁和写锁就不冲突
MVCC只会在rc(读为提交)和rr(可重复读)下工作
数据库表中除了我们定义的元素还有隐藏列
分别为
DB_TRX_ID(事务id)
DB_ROLL_PTR
和DB_ROW_ID(这个是聚簇索引那里讲的)和MVCC没关系
DB_TRX_ID 是当前数据最后一条操作的事务唯一值
DB_ROLL_PTR存储最新历史数据地址

历史数据存储在undolog(也是用来回滚的手段)中

这样的话比如说我们的事务级别为读已提交,就可以在undolog中找到最新提交的数据
怎么找呢?
通过Readview就可以定位到undolog的数据
在rc的情况下,每次查询都会新建一个readview(与rr不同)
创建会记录 当前事务id| 未提交事务id | 未开始事务id(编出来的事务,即将分配)(最大的m_ids+1) 三个信息
min_trx_id为未提交事务最小事务ID
man_trx_id为未提交事务最大事务ID+1
creator_trx_id为当前事务
这里我们先用RC举例子
我们查询到数据行后,检查 事务ID(DB_TRX_ID)是否小于最小未提交事务id min_trx_id
如果小于true的话,说明事务已经提交,直接取数据
如果等于 当前事务ID的话也直接取数据(因为是你自己改的,逻辑自洽)
其他情况都不能读取(除了null,null是最开始的时候)
比如你DB_TRX_ID>max_trx_id说明它是我们当前事务开始后的另一个事务修改的(认为没有提交)不会读取数据
DB_TRX_ID在m_ids也不可以,因为这里的事务还没提交

rr
同样的查询只会在第一次创建Readview读取数据(之后就复用这个readvieww)
所以它的那个未提交事务是固定的,最小值都是11,就会事务10改的
8.为什么RR没有解决幻读问题(MVCC)?
按我们上面RR的思想其实是不会出现幻读
正常确实不会出现幻读的问题
因为我们的readview是固定的
我们读取分为两种类别 快照读 和 当前读
快照读就是我们之前MVCC中的读取,读取需要历史信息(select都是快照读)
当前读就是我们就是要当前的数据,最新的数据(update delete insert都是当前读)
这块挺难的我会面补充吧
9.sql优化常见手段,慢查询
Redis
1.Redis过期键删除策略

2.Redis线程模型
redis使用文件时间处理器
里面包含IO多路复用程序,多个socket,文件事件分派器,事件处理器

多路IO复用连接到多个socket,socket负责接收请求,socket接收到后,将请求传入队列当中,事件分派器负责将队列中的请求交给事件处理器进行处理

3.缓存击穿、缓存雪崩、缓存穿透及其解决方案
正常我们使用redis的场景

缓存击穿就是key消失同时突然大量请求去请求该key,直接请求mysql,击穿缓存(很形象)

解决方案:
1.永不过期(热门不过期)
2.双重检查锁,先查redis,如果没有数据的话执行加锁逻辑,再查一遍redis(防止有上一个拿到锁的线程已经更新了缓存)然后再查询mysql更新缓存,这样的话,其他线程就可以通过锁外的redis查询获取缓存数据

3.分布式锁
分布式锁这里可以引申出很多问题,本地锁锁不住分布式部署服务,然后介绍如何实现分布式锁
记问题,好记,说出对应解决方案即可
首先就是
1.set nx 后宕机 需要设置过期时间问题ex
2.锁的误删问题,线程1业务执行时间太长,然后线程2进入后执行,线程1执行给2的锁删了,所以需要幂等性,只有自己线程开的锁可以自己解锁,使用UUID+threadID(分别指定JVM和JVM中的线程)
3.还是锁的误删问题,就是你判断和删除中间如果有阻塞(比如java的fullgc),然后redis是不阻塞,给锁释放了,线程2又来了,获取到,然后你线程1已经判断完了,就把线程2的锁解锁了 需要保证判断和解锁的原子性,引出redis的lua脚本,让redis去执行判断+解锁的一整个命令保证原子性
缓存雪崩
就是缓存中的key的集中失效(或者缓存宕机),导致大量请求去请求到mysql
解决方案:1.我们在存储key的时候设置随机失效时间,保证我们的key不会集中过期-针对于key集中失效
2.给redis做好集群-针对宕机

缓存穿透
缓存穿透就是你请求的数据redis没有,mysql中也没有,一般是用户恶意攻击制造伪请求
解决方法:
1.缓存空对象:你去mysql中查询数据无论这个数据是否存在,你都返回,并存储到redis中,这样他下次以相同的参数请求只会请求到redis里面的数据
2.布隆过滤器

4.如何解决缓存和数据库的一致性问题?
就是读写并行,你更改数据库的数据对应的redis里面的数据也要更改
有四种情况,两种模式
双写模式-更新数据库同时也更新缓存
失效模式-更新数据库删除缓存
你是删除redis中的缓存还是更改redis的缓存(这里明确建议删除,删除成本更小)
你是先操作redis还是先操作数据库

先操作缓存时,数据不一致情况
我们采用延迟双删除的做法

先操作数据库再删除缓存
写数据这个过程中可能其他线程去查到redis里面的是老数据,但是写完数据库后再去删除缓存,可以保证我们的最终一致性(比较推荐这种方法)

缓存一致性问题:
官方回答,我们存入缓存的数据一般都是一致性要求不高,一致性要求很高的建议直接去查数据库啊,一致性的通用解决办法,可以使用我们的,过期时间+读写锁,读写锁其实已经差不多可以保证一个强一致性了(可能)
读锁就加在读缓存的时候,写锁就加在操作mysql和删除redis缓存外面

39万+

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



