1、悲观锁、乐观锁和分布式锁的实现和细节
悲观锁:认为线程安全问题一定会发生,所以在操作数据之前先获取锁,保证线程串行执行,例如synchronized,lock
细节:
悲观锁适合插入数据
锁的粒度要尽量小,只锁住需要串行执行的代码
配合事务使用时,要先提交事务再释放锁
乐观锁:认为线程安全问题不一定会发生,因此不加锁,只是在操作数据前判断是否有其他线程对数据做了修改,如果没有被修改则说明线程安全,更新数据,反正说明出现了线程安全问题,可以重试或者返回异常,例如给表加字段version,cas操作
细节:
乐观锁适合更新数据
更新前要先查询version,更新时比较version是否相同
分布式锁:满足分布式系统或集群模式下多进程可见且互斥的锁,常见的实现有redis和zookeeper,redis通常利用setnx方法
细节:
锁的误删,比如线程1拿到锁但是出现了阻塞导致锁自动释放,在线程2拿到锁后执行业务逻辑时,线程1反应过来,继续执行,最后将本已经不属于他的锁误删了
锁的误删解决:设置锁的唯一标识,每个线程在获取锁时,设置锁的value为线程唯一标识(可以用uuid实现),释放锁时判断锁的value是否跟自身线程唯一标识一致,一致才能释放
2、什么是死锁,死锁产生的条件?如何避免死锁?
死锁就是一组互相竞争资源的线程,因为互相等待又互不相让资源,导致永久阻塞无法进行下去的情况
死锁产生的条件有四个:
-
互斥条件:资源x和y只能分别被一个线程占用
-
占有且等待:线程t1占有资源x后,等待资源y被释放,同时自己不释放资源x
-
不可抢占:其他线程不能强行抢占线程t1占有的资源
-
循环等待:线程t1等待线程t2占有的资源,线程t2等待线程t1占有的资源
避免死锁的方法(破坏对应的条件):
-
一次性申请所有资源(破坏互斥条件)
-
占有资源的线程,在申请其他资源时,如果申请失败,可以主动释放自己的资源(破坏占有且等待条件)
-
按照顺序去申请资源,然后反序释放资源,破坏循环等待条件
3、ConcurrentHashMap底层原理
ConcurrentHashMap是在HashMap的数据结构上,增加了CAS操作和Synchronized互斥锁来保证线程安全,并且使用volatile关键字修饰了node中的next和val字段来保证多线程环境下某个线程新增或修改节点对于其他线程是立即可见的。
在进行添加操作时:
-
计算hash值,定位该元素应该添加到的位置
-
如果不存在hash冲突,即该位置为null,则使用CAS操作进行添加
-
如果存在hash冲突,即该位置不为null,则使用synchronized关键字锁住该位置的头节点,然后进行添加操作
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取
4、线程池的核心参数和执行原理?
线程池的核心参数有七个:
corePoolSize:核心线程数
maximumPoolSize:最大线程数量,核心线程+救急线程的最大数量
keepAliveTime:救急线程的存活时间,存活时间内没有新任务,该线程资源会释放
unit:救济线程的存活时间的单位
workQueue:工作队列,当没有空闲核心线程时,新来的任务会在此队列排队,当该队列已满时,会创建应急线程来处理该队列的任务
treadFactory:线程工厂,可以定制线程的创建,线程名称,是否是守护线程等
handler:拒绝策略,在线程数量达到最大线程数量时,实行拒绝策略 拒绝策略:
线程池执行原理:
5、线程池常见的阻塞队列?
常见的阻塞队列有:
ArrayBlockingQueue:基于数组结构的有界阻塞队列,FIFO
LinkedBlockingQueue:基于链表的有界阻塞队列,FIFO
两者区别(链表与数组的区别)
ArrayBlockingQueue | LinkedBlockingQueue |
底层是数组 | 底层是链表 |
强制有界 | 默认无界,传递容量参数后有界 |
初始化时即创建好node对象数组 | 插入时才创建node对象 |
两把锁(头尾) | 一把锁 |
LinkedBlockingQueue的优点是锁分离,很适合生产和消费频率差不多的场景,这样生产和消费互不干涉的执行,能达到不错的效率
6、线程池核心线程数如何确定?
没有固定答案,先设定预期,比如我期望的CPU利用率在多少,负载在多少,GC频率多少之类的指标后,再通过测试不断的调整到一个合理的线程数
公式:
分三种情况
高并发,任务处理时间短 ——》CPU核数+1,减少线程上下文切换
并发不高,任务处理时间长 IO密集型任务——》CPU核数*2+1
读写多,DB操作多,CPU占用少,并且IO数据传输时,是不占用CPU的,所以就可以多释放CPU资源,给其他线程运行,
CPU密集型任务——》CPU核数+1
CPU占用高,如计算任务,视频解码任务,这些任务线程上下文切换开销大,所以要尽量减小开销,提高CPU效率
7、线程池的种类有哪些
在JUC的Executor类中,提供了多种创建线程池的方法,主要有四种
1、固定线程数的线程池
核心线程数与最大线程数一样,没有救急线程
阻塞队列是LinkedBlockingQueue,最大容量是Integer.MaxValue
适合任务量已知,相对耗时的任务
2、单例线程的线程池
核心线程数和最大线程数都是1,没有救急线程
阻塞队列是LinkedBlockingQueue,最大容量是Integer.MaxValue
适合按顺序执行的任务,与单线程的区别是,单线程运行完了就会销毁,而线程池创建的线程运行结束不会销毁,而是等待下一个任务,可以重复使用,减少了创建线程和销毁线程的时间,提高资源利用率。
3、可缓存的线程池
核心线程数为0,最大线程数为Integer.MaxValue,救急线程存活时间为1分钟
阻塞队列为SynchronousQueue:不存储元素的阻塞队列,每一个插入操作必须等待一个移除操作
适合任务比较密集,且任务执行时间短的情况,因为使用的是救急线程,在一定时间没有新任务后就会销毁,节省资源,同时能应付任务密集的时间段。
4、可以定时执行的线程池
核心线程数自定义,最大线程数为Integer.MaxValue
适合需要定时执行任务的场景
8、线程池使用场景
1、批量将pgsql数据导入ES
计算数据总条数——》固定每页200条,计算总页数N——》将页数N设置为CountdownLatch的count——》创建N个线程批量导入,每次导入完调用countdown方法——》主线程中调用await方法,线程都执行完后,主线程结束
2、异步调用
用户搜索需要保存搜索记录,但保存功能不能影响正常搜索——》搜索时通过线程异步调用保存记录功能
关键注释:@EnableAsync加在SpringBoot启动类上,@Bean将自定义线程池注入到容器中(核心8,最大8), @Async("线程池名称")加在需要异步调用的方法上,
9、对ThreadLocal的理解,ThreadLocal内存泄漏问题
ThreadLocal是java.lang包中的一个类,它实现了线程之间的资源隔离,让每个线程都有自己的独立资源
ThreadLocal的底层实现的关键是它的静态内部类ThreadLocalMap
-
每个Thread对象都有一个成员变量threadlocals,它指向一个ThreadLocalMap,该Map以ThreadLocal为key,需要存储的线程变量为value
-
当调用ThreadLocal的set()方法时,会先拿到当前线程对象的ThreadLocalMap,然后以当前ThreadLocal为key将变量存储到map中,调用get方法和remove方法时也是通过这个key去操作。
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取
ThreadLocal内存泄漏问题:
回答这个问题,我们首先要知道java中的四种引用
强引用:强引用是最常见的,只要把一个对象赋值给一个引用变量,那么这个对象就被强引用了,强引用的对象只要不为null,就不会被回收
软引用:软引用相对弱化一些,需要用softReference对象构造方法去创建软引用,当内存充足时,软引用的对象不会被回收,当不足时就会被回收
弱引用:弱引用又更弱了一些,需要用weakReference的构造方法创建弱引用,当发送GC时,只要是弱引用的对象就会被回收
虚引用:虚引用要配合引用队列使用,它的主要作用是跟踪对象垃圾回收的状态,当对象被回收时,通过引用队列做一些通知类工作
在ThreadLocalMap中,Key ThreadLocal是一个弱引用,但是值value是一个强引用,当垃圾回收时,弱引用ThreadLocal会被回收,而value不会,这就导致value成了一个无法被访问也无法回收的变量,造成内存泄漏
解决办法:当使用完ThreadLocal变量后,及时使用remove方法进行清除。
-
static class Entry extends WeakReference<ThreadLocal<?>> {
-
/** The value associated with this ThreadLocal. */
-
Object value;
-
Entry(ThreadLocal<?> k, Object v) {
-
super(k);
-
value = v;
-
}
-
}
三、JVM虚拟机
1、JVM的主要组成部分及其作用![](https://i-blog.csdnimg.cn/blog_migrate/48306b30de632648438b905337aad175.png)
类加载器:将java字节码文件加载到内存中
运行时数据区:就是我们常说的JVM的内存,我们的所有程序都被加载到这运行
执行引擎:负责将字节码翻译成底层系统指令,然后交由系统执行
本地方法接口:与本地库交互实现一些基础功能
2、运行时数据区的组成部分及其作用
JVM运行时数据区由方法区(元空间),堆,程序计数器,虚拟机栈,本地方法栈组成
-
方法区/元空间:方法区是一个线程共享的区域,里面存储了类信息,常量,静态变量等待信息,虚拟机启动时创建,虚拟机关闭时释放,内存无法满足分配请求时,会报OOMError:Metaspace
-
堆:Java堆是一个线程共享的区域,里面存储了实例对象、数组等等,内存不够时抛出OOM异常
-
堆分为年轻代和老年代
-
年轻代被分为三块,eden区和两个严格相同的Survivor区
-
老年代主要用来保存生命周期长的对象,主要是老的对象
-
-
-
程序计数器:用来记录当前线程正在执行的字节码指令地址,是线程私有的
-
虚拟机栈:虚拟机栈是java方法执行时的内存结构,线程私有,虚拟机会在每个java方法执行时开启一个栈帧,用于存储方法参数,局部变量,返回地址,操作数栈(中间计算结果,比如i+j的值)等信息,当方法执行完毕时,该方法会从虚拟机栈中出栈。
-
本地方法栈:本地方法栈是线程私有的,为虚拟机使用的native方法提高服务
如果栈的深度超过了虚拟机允许的最大深度,就会抛出StackOverflowError异常; 如果在扩展栈时无法申请到足够的内存,就会抛出OutOfMemoryError异常。
3、垃圾回收是否涉及栈?
垃圾回收主要指的是堆内存,栈帧内存在栈帧出栈后就释放
4、栈内存越大越好吗?
每个虚拟机栈内存默认为1M
不一定,机器内存是固定的,栈内存越大,可以同时活动的栈帧就越少,效率反而降低
5、方法内部的局部变量是否线程安全?
只要局部变量没有离开方法的作用范围,就是线程安全的
如果局部变量引用了一个对象,并且逃离了方法的作用范围,就有可能线程不安全
6、什么情况下会栈溢出?
栈帧过多:比如太多层级的递归调用
栈帧过大:出现少
7、堆栈的区别是什么?
1、存放内容
堆中存放的是对象实例和数组,该区域更关注的是数据的存储,
(静态变量放在方法区,静态对象仍然放在堆中)
栈中存放的是局部变量,栈帧,操作数栈,返回结果等。该区更关注的是程序方法的执行。
然而实际上,对象并不总是在堆中进行分配的,这里就需要介绍一下JVM的逃逸分析技术了。JVM会通过逃逸分析技术,对于逃不出方法的对象,会让其在栈空间上进行分配。
2、程序的可见度
堆是线程共有的,栈是线程私有的。
3、异常错误
如果栈内存没有可用的空间存储方法调用和局部变量,JVM会抛出java.lang.StackOverFlowError。而如果是堆内存没有可用的空间存储生成的对象,JVM会抛出java.lang.OutOfMemoryError
4、物理地址
堆的物理地址是不连续的,性能相对较慢,是垃圾回收区工作的区域。在GC时,会考虑物理地址不连续,而使用不同的算法,比如复制算法,标记-整理算法,标记-清除算法等。
栈中的物理地址是连续的,LIFO原则,性能较快。
5、内存分别
堆因为是不连续的,所以分配的内存是在运行期确认的,因此大小不固定,一般堆大小远远大于栈。
栈是固定大小的,所以在编译期就确认了。
8、运行时常量池是什么?和字符串常量池的区别
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取
运行时常量池是方法区的一部分,它可以看做是一张表,用于存放编译器生成的各种字面量和符号引用,在类被加载时,它的常量池信息就会被放入运行时常量池,并且还会保存符号引用对应的直接引用。
而字符串常量池是存放在堆里的,在HotSpot虚拟机中,它的底层是一个c++的hashtable,它将字符串的字面量作为key,实际堆中创建的String对象的引用作为value。
当创建一个String对象时,会拿着字面量尝试在字符串常量池中获取对应String对象引用,如果是首次执行,就会在堆中创建一个String对象,并保存到字符串常量池中,然后返回。
9、什么是直接内存?
直接内存不属于JVM内存,是操作系统的内存,常见于NIO操作,用于数据缓冲区,拥有较高的读写性能,且不受JVM内存回收影响
BIO(同步阻塞IO)
发送请求后线程一直阻塞,直到数据处理完并返回
NIO(同步非阻塞IO)
通过一个线程轮询大量socket,当有socket准备就绪时通知客户端,客户端调用函数接收。
AIO(异步非阻塞IO)
-
每个请求都会绑定一个Buffer;
-
通知操作系统去完成异步的读(这个时间你就可以去做其他的事情)
-
读完之后会通知客户端来读取数据
10、对象什么时候可以被垃圾回收?如何标记垃圾?
当没有任何一个强引用指向对象时,对象就可以被垃圾回收
主流的虚拟机一般通过可达性分析算法分析一个对象是否能被回收,也有系统采用引用计数法判断
可达性分析算法:
1、算法主要思想是先确定一些肯定不能被回收的对象作为GCRoot, GCRoot对象可以是:
虚拟机栈中引用的对象
本地方法栈中Native方法引用的对象
方法区中静态属性,常量引用的对象
2、然后以GCRoot为根节点,去向下搜索,找到它们直接引用或间接引用的对象
3、在遍历完之后,如果发现有一些对象不可达,那么就认为这些对象已经没用用了,需要被回收
引用计数法:
就是为每一个对象添加一个引用计数器,用来统计指向当前对象的引用次数,一旦这个引用计数器变为0,就意味着它可以被回收了。
11、垃圾回收算法有哪些?
1、标记清除算法:标记清除算法分为两个步骤,标记-清除
标记:遍历内存空间,对需要被回收的对象打上标记,通常使用可达性分析算法
清除:再次遍历空间,将被标记的内存进行回收
缺点:
遍历两次,效率低
因为清除后没有进行整理,容易形成碎片化不连续的的内存空间
2、标记整理算法:标记整理算法分为三个步骤,标记-整理-清除
标记同上
整理:将所有存活的对象压到内存的一端,并按顺序排列
清除掉其他空间
缺点:
效率低,速度慢
3、复制算法:将内存分为等大的两块,每次只使用其中一块,当触发GC时,将存活的对象全部移到另一块内存的一端,然后将当前内存空间一次性回收,如此循环往复
缺点:内存利用率低
12、什么是分代收集算法
在java8中,内存被分为两份:新生代和老年代
新生代内存使用复制算法,老年代内存使用标记整理算法
当对象在新生代内存中经历了一定的垃圾回收后,它将被晋升到老年代。
分代收集算法重复利用了对象生命周期的特点,提高了回收效率
大对象直接进入老年代
什么是大对象呢,这个是由jvm定义的参数值决定的,但是这个参数只在Serial和ParNew垃圾收集器中生效 :-XX:PretenureSizeThreshold
当我们新分配的对象大小大于等于这个值,就会直接在老年代中分配
长期存活的对象将进入老年代
在每个对象的头信息中,都包括一个年龄计数器
对象在经过一次minor gc之后,如果仍然存活,并且能够被 survior所容纳 ,那么这个年龄计数器就会加一,当计数器的值达到了默认值大小(一般默认值为15),就会进入到老年代。
对象动态年龄判断后决定是否进入老年代
当survior区域的存活对象的总大小占用了survior区域大小的50%(可以通过参数指定),那么此时将按照这些对象的存活年龄从从到大排序,然后依次累加,当累加到对象大小超过50%,则将大于等于当前对象年龄的存活对象全部挪到老年代。
详细解释:
首先要知道Minor GC 和 Full GC的区别
普通GC(minor GC):只针对新生代区域的GC,指发生在新生代的垃圾回收动作,因为大多数Java对象存活率都不高,所以Minor GC非常频繁,一般回收速度也比较快。
全局GC(major GC or FullGC) :指发生在老年代的垃圾收集动作,出现了Major GC,经常会伴随至少一次的MinorGC(不是绝对的)。Major GC的速度一般要比Minor GC慢10倍以上。
该算法将内存分为新生代、老年代、新生代中又分伊甸园、幸存区from、幸存区to,对象创建之初,会存在于伊甸园中,当伊甸园满了之后,会触发一次Minor GC,伊甸园中还存活的对象会被转入幸存区from,当伊甸园再次触发Minor GC时,GC会扫描伊甸园和幸存区from,对这两个区进行垃圾回收,垃圾回收后,幸存区from中还存活的对象利用复制清除算法复制到幸存区to(如果有对象的年龄达到了老年代区,则复制到老年代区),复制完之后把幸存区from清除掉,之后把from区和to区交换位置(from变to,to变from,保证幸存区to为空),最后把伊甸园中存活下来的对象放入幸存区from,(如果有对象的年龄达到了老年代区,则复制到老年代区),同时把这些对象的年龄+1即可。
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取
13、垃圾收集器有哪些?
串行收集器:GC时只会有一个线程在工作,Java应用中的线程都要STW,等待垃圾回收结束
并行收集器:GC时会有多个线程参与垃圾收集,Java应用中的线程都要STW,等待垃圾回收结束,JVM默认
CMS(Concurrent Mark Sweep)收集器:并发收集器,进行垃圾回收时不会暂停Java应用线程,STW时间短,CMS收集器采用的是标记清除算法,所以不需要移动存活对象的位置,GC可以和Java应用程序同时运行
G1(Garbage-First)收集器:基于区域划分的收集器,适用于大内存应用。
14、什么是G1垃圾收集器
G1是JDK9默认的垃圾收集器,代替了CMS收集器。它的目标是达到更高的吞吐量和更短的GC停顿时间。
G1垃圾回收器将堆内存划分为多个区域,每个区域都可以充当Eden,Survivor,old,humongous(专为大对象准备)
G1垃圾回收过程可以分为几个主要阶段:
1、初始标记:标记GCRoot直接关联的对象,需要STW
2、并发标记:对GCRoot开始对堆中对象进行并发标记,需要STW
3、重新标记:解决一些漏标错标问题,需要STW
4、混合收集:将不需要回收的eden,Survivor对象放入新的Survivor区,old对象放入新的old区,如果Survior对象达到老年年龄后也会放入新old区。然后将区域内存回收
要注意的是,混合收集不会处理所有区域,而是根据停顿时间目标去筛选出回收价值高(存活对象少)的区域。
15、什么是类加载器?类加载器有哪些?
类加载器是一个负责加载类的对象,因为JVM虚拟机只能运行二进制文件,类加载器的作用就是将字节码文件加载到JVM中运行。
JVM中有三个内置的类加载器
BootStrapClassLoader(启动类加载器):最顶层的加载器,由C++实现,主要用来加载Java的核心类裤(JAVA_HOME/lib)
ExtensionClassLoader(扩展类加载器) :主要负责加载JAVA_HOME/lib/ext下的扩展类
AppClassLoader(应用程序类加载器) :主要负责加载ClassPath下的类,也就是开发者自己写的Java类
另外,还有自定义ClassLoader,通过继承ClassLoader实现。
16、什么是双亲委派模型?为什么类加载采用双亲委派?
当一个类加载器尝试加载一个类时,会先委托上一级加载器去加载,如果上一级还有上级,就会委托上一级的加载器的上级去加载,如果所有上级加载器都不能加载该类,则类加载器尝试自己加载类。
作用:
1、防止重复加载:防止已经在上级加载器中加载的类重复地被子加载器再次加载
2、为了安全,防止核心类库API被篡改
3、保证类的一致性
17、类装载的执行过程是怎样?
1、加载
加载过程需要完成三步
-
通过类的全限定名来获取类的二进制流
-
解析类的二进制流为方法区的类数据结构(Java类模型)
-
在堆中创建一个该类的Class对象,作为方法区中该类的数据访问入口。
2、验证
验证类是否符合JVM规范
①文件格式验证
②元数据验证
③字节码验证
④符号引用验证:检查符号引用对应的类或方法是否在常量池中存在
3、准备
该阶段为类的静态变量分配内存,并设置初始值,这里所说的初始值“通常情况”下是数据类型的零值
4、解析
将常量池的符号引用替换为直接引用
符号引用:以一组符号来描述所引用的目标,有时候JVM不知道引用的类或者方法内存地址在哪,于是就先用符号引用代替直接引用
直接引用:直接指向目标的指针
5、初始化
对类的静态变量,静态代码块进行初始化操作
//6、使用:使用类执行代码
//7、卸载:用户代码执行完毕后,销毁Class对象
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取
18、用于JVM调优的参数有哪些?
1、设置堆空间大小
-Xms 512m 设置初始化大小,默认是物理内存的1/64
-Xmx 1g 设置最大大小,默认是物理内存的1/4
2、虚拟机栈大小
-Xss 256k 默认1M
3、年轻代eden区和survivor区大小比例
-XXSurvivorRatio 8 表示eden区:survivor区=8:2
4、年轻代晋升到老年代阈值
-XX:MaxTenuringThreshold=10 默认为15
5、设置垃圾回收器
-XX:+Use[垃圾回收器名]
19、JVM调优的工具有哪些?
命令:
jsp 进程状态信息
Jstack 线程内的堆栈信息
Jmap(使用较多) 查看堆的信息,堆的大小配置,堆内存各部分的使用情况
jstat 垃圾回收信息
工具:
jconsole:用于对JVM内存,线程,类的监控
visualVM:用于对JVM内存,线程,类的监控
20、Java内存泄漏的排查思路?
首先通过jmap命令或者设置JVM参数区获取dump文件
然后将dump文件放入VisualVM中去分析
通过查看堆的信息,定位到哪行代码出了问题,然后再去修改代码
21、CPU彪高排查方案与思路
1、使用top命令,查看哪个进程CPU使用率较高
2、使用ps H -eo pid,tid,%cpu | grep 进程id 命令查看进程中的线程状态
3、找到关键线程,然后使用jstack命令去查看进程中哪个线程出了问题,然后找到对应代码并修改
四、Mysql数据库
1、InnoDB和MyISAM的区别和使用场景
事务:InnoDB支持事务,而MyISAM不支持
外键:InnoDB支持外键,而MyISAM不支持
锁:InnoDB支持行级锁(并发量高)和表锁,MyISAM只支持表锁
索引:InnoDB不支持全文索引,MyISAM支持全文索引(全文索引是基于分词的索引,能更快速地在文本中检索一段信息内容)
使用场景:
InnoDB适合需要高并发或事务的场景,
MyISAM适合读操作远远大于写操作且不需要事务的场景
属性 | InnoDB | MyISAM |
事务 | 支持事务 | 不支持事务 |
---|---|---|
外键 | 支持外键 | 不支持外键 |
锁 | 支持行锁(并发更高)和表锁 | 只支持表锁(不会死锁) |
索引 | 不支持全文索引 | 支持全文索引(查询更快) |
适用场景 | 并发量高或者需要事务 | 读远多于写且不需要事务 |
2、如何防止sql注入?
在编写sql语句时,使用参数传递的方式去给变量赋值而不是直接嵌入到sql语句中
在mybatis的mapper中,我们可以用#{value}的方式去传递参数防止sql注入。
3、怎样实现幂等
幂等:也就是相同条件下对一个业务的操作,不管操作多少次,结果都是一样
实现方案:唯一索引(通常是主键),乐观锁(通常是version字段),token+redis(令牌-缓存)
token:token是服务端生成的一串字符串,当客户端登录后,服务端将token作为令牌发送给客户端,之后客户端访问服务端时,只需要校验token即可,不需要再验证用户和密码,redis作为缓存保存令牌到服务端。
4、一条sql语句的执行流程
Mysql分为server层和存储引擎层
- 建立连接:server层的连接器验证客户端发来的用户名和密码是否正确,如果错误就返回错误提示,如果正确就校验用户的权限
- 查询缓存:当执行sql查询语句时,mysql会先去缓存中查询是否有记录,如果有记录就直接返回,没有的话就去数据库中查询,然后将查询记录记录到缓存中
- 分析器:分析器的作用是对sql语句进行词法分析和语法分析,词法分析就是对sql语句中的关键词进行分析,比如查询到select关键词就知道这是一条查询语句,此外还会分析被操作的表,条件语句的字段等,语法分析就是判断这条语法是否正确。
- 优化器:对sql语句进行优化
- 执行器:调用存储引擎的接口,执行sql语句,执行之前会判断一下有没有权限
5、Mysql的事务特性(ACID)
- 原子性:单个事务是不可分割的最小工作单元,事务中的所有操作,要么全都执行成功,要么全都执行失败。通过undolog日志解决
- 一致性:数据库总是从一个一致性的状态转换到另外一个一致性的状态。如果事务成功完成,那么所有变化都将成功应用,如果失败,就会回滚到原始状态。通过undolog日志解决
- 持久性:一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,修改的数据也不会丢失。通过redolog日志解决
- 隔离性:事务与事务之间互不影响。通过加锁实现
6、 Mysql事务的隔离级别,脏读、不可重复度,幻读。
脏读:事务读取到了未提交的数据,比如A查询到了B修改了但未提交的数据
不可重复读:事务对同一内容的两次查询结果不一致,比如A查询到数据不存在,B插入并提交,A再次查询时发现存在
幻读:事务在插入前检查到数据不存在,插入时却发现数据已经存在无法插入,比如A发现数据不存在准备插入,但B此时插入数据并提交
不可重复读重点在于update,而幻读的重点在于insert或delete。
Mysql事务的隔离级别从低到高为:
- 读未提交RC:所有的事务都可以读取其他事务未提交的结果,会发生脏读,不可重复读,幻读
- 读已提交RU:事务开始时,只能读取到已提交的修改,会发生不可重复读,幻读
- 可重复读RR:事务开始时,在同一条件的查询返回的内容是一致的,会发生幻读,mysql默认隔离级别
- 可串行化:所有事务依次执行,事务之间不能互相干扰,能防止脏读,不可重复读,幻读
7、 锁的类型,行锁和表锁,共享锁和独占锁
锁的类型 | 是否会死锁 | 并发量 | 适用场景 |
表锁 | 不会死锁 | 并发量低 | 1、读多写少(上一次锁连续读) 2、写特别多(如果用行锁,会导致其他事务长时间锁等待,锁冲突频率变高) |
行锁 | 会死锁 | 并发量高 | 并发量高的场合 |
页面锁 | 会死锁 | 并发量中 |
行锁:行锁就是加在单行上的锁,只存在于InnoDB引擎中,分为共享锁(读锁)和独占锁(写锁)
类型 | 作用 |
共享锁(读锁) | 获得共享锁的事务被允许读一行,并阻止其他事务获得该行的独占锁 |
独占锁(写锁) | 获得独占锁的事务被允许写一行,并阻止其他事务获得该行的共享锁和独占锁 |
读锁和写锁的
- 共同点: 自动加锁
- 不同点:共享锁可能会死锁,独占锁不会 / 读锁读取数据后释放,写锁要事务结束后才释放
- 语法:SELECT...+LOCK IN SHARE MODE / INSERT...+FOR UPDATE
此外还有意向共享锁和意向独占锁,这是InnoDB自动添加的,事务在获取读锁和写锁前必须先获取该表的对应意向锁。
表锁:表锁是加在整个表的锁
表锁也有两种锁,共享锁和独占锁,作用与行锁的基本相同
语法:LOCK TABLE {TABLENAME} READ / LOCK TABLE {TABLENAME} WRITE
8、内关联,全关联,左关联,右关联的区别
内关联(INNER JOIN):所有的查询结果在关联的两张表中都有记录
全关联:联结了那些在相关表中没有关联的行,分为左关联和右关联
左关联(LEFT JOIN):从From子句的左边表中选择所有行,可以没有右边的表的行
左关联(RIGHT JOIN):从From子句的右边表中选择所有行,可以没有左边的表的行
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取
9、索引,索引的分类
索引的作用相当于是图书的目录,它是对数据表中一列或多列值进行排序的一种存储结构
索引的分类:普通索引,主键索引,复合索引,唯一索引,外键索引,全文索引
普通索引:
mysql表中最基本的索引
语法:CREAT INDEX index_name ON TABLE table_name(column)
主键索引:
在Mysql创建表指定主键的时候,会自动在主键上建立一个主键索引,标志着主键具有唯一性且不为空
复合索引:
复合索引也叫组合索引,在建立索引的时候使用表中的多个字段,就比如说使用身份证号和手机号建立索引
语法:CREAT INDEX index_name ON TABLE table_name(column1,column2)
唯一索引:
唯一索引标志着该字段具有唯一性
语法:CREAT UNIQUE INDEX index_name ON TABLE table_name(column)
如果创建表的时候加索引语法为:
CREATE TABLE tablename(
propname1 type1,
……
propnamen type..n,
UNIQUE INDEX|KEY index_name (column [(length)] [ ASC | DESC ] ) );//简化为:UNIQUE INDEX index_name (column);
10、索引的优缺点和使用规则
索引的优点主要体现在:
- 提高了用户查询的速度,提高了性能
- 降低了查询中分组和排序的时间
- 加速表与表之间的连接
- 通过索引的唯一性,可以确保表中数据的唯一性
索引的缺点主要体现在:
- 空间:索引占用了磁盘空间
- 时间:当数据量较大时,索引的创建和维护也是非常耗费时间的
- 每次对数据进行增删改查时,索引也要进行动态维护,降低了数据的维护速度
总结,以下条件不适合添加索引:
- 在查询中很少使用的列
- 数据值很少的列
- 写操作远多于读操作的列(修改频率大于查询频率的列)
使用索引的规则:
- 对查询频率高的字段创建索引
- 对经常需要分组、排序和联合操作的字段创建索引
- 尽量使用唯一索引
- 多使用短索引
- 索引的数目不要不多(不然会降低增删改的效率)
11、索引的原理,B+树的优点
首先我们了解一下B树(Balance Tree),即平衡树的意思,下图即是一颗B树
B树的每一个节点代表一个磁盘块,每一个节点上面存储了多个键值对和指针
B树相对于平衡二叉树,每个节点存储了更多的键(Key)和数据(Data),并且每个节点拥有更多的子节点,子节点的个数就是B树的阶数,比如上图就是的B树就是三阶B树
基于这个性质,B树查询数据读取磁盘的次数会减少,查找的效率会比平衡二叉树高。
B+树是对B树的进一步优化,下图是一颗B+树
在B+树中,非叶子节点不会存储数据(Data),只会存储键值(Key),而所有的数据都存储在叶子节点上,并且所有数据都是按照顺序排列的
在数据库中,页(节点)的大小是固定的,InnoDB默认为16kb,页如果不存储数据,就可以存储更多的键值,树的阶数会提升,
这样一来我们查询数据访问磁盘的次数会进一步减少,查询的效率更高,根节点默认存储在内存,所以我们访问一次三阶B+树索引的数据只需要2次磁盘IO
因为B+树索引中所有的数据都存储在叶子节点上,并且按顺序排列,所以对数据的范围查找,分组,排序都会更加简单
MyISAM中的B+树索引实现与innodb中的略有不同。在MyISAM中,B+树索引的叶子节点并不存储数据,而是存储数据的文件地址。
B+树相对于B树的特点:
- 只有叶子节点才会存放数据,非叶子节点只存放索引
- B+树中每个节点都通过双向链表连接,叶子节点中的数据通过单向链表连接
- 非叶子节点中的索引会出现在子节点中,而且是子节点中索引的最大(最小)值
- 非叶子节点的索引树=子节点数
B+树相对于B树的优点:
- 普通查询,范围查询,分组查询,排序的速度更快
- 插入和删除的速度更快
12、聚集索引和非聚集索引
以主键为B+树索引的键值构建的B+树索引就是聚集索引
以主键以外的字段作为B+树索引的键值构建的B+树索引就是非聚集索引
非聚集索引与聚集索引的区别在于非聚集索引的叶子节点不存储表中的数据,而是存储该列对应的主键,想要查找数据我们还需要根据主键再去聚集索引中进行查找,这个再根据聚集索引查找数据的过程,我们称为回表。
13、联合索引的原理
联合索引是对多个列进行索引
联合索引也是通过B+树实现的,特点如下:
- B+树的键值数量不是1,而是2或2以上
- B+树在对第一个索引排序的基础上,再去对第二个索引排序,再去对第三个,依次排序
- 联合索引遵循最左匹配原则
最左匹配原则:在联合索引中查询时,从左边的字段开始匹配,若条件中的字段顺序与联合索引中字段顺序一致,就可以走索引查询,否则不走
14、索引什么时候会失效
- 使用LIKE模糊查询时%或者_放在最前面(会变为全表查询)
- 使用or语句or的两边没有同时使用索引
- 联合索引查询时没有遵守最左匹配原则
- 索引列数据类型隐式转化(varchar不加单引号的话可能会自动转换为int型)
- 对索引列进行计算或使用函数(改变了列值,在索引中找不到)
- order by使用错误(排序与索引中的排序不一致)
- 全盘扫描速度比索引快
- Where 子句中使用参数
15、高性能数据库优化实战经验:
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取
1.打破范式设计,冗余少量字段方便查询,需要注意源表和冗余表保证同一事务写。
2.关联关系在业务层面约束,不依赖数据库外键
3.字段拓展性,如模板信息这种结构不清晰的字段使用json类型,json检索的问题我的想法是少量key使用虚拟列并建立索引,多条件检索直接异构es
4.冷热分离,源表拆分成多张表,可以把频繁变更的字段放在主表,使用率较低的放在副表,判断依据可以是创建时间、业务域
5.服务拆分在分片字段选择上尽量考虑使用本地事务,让同业务的不同sql命中同一个分表,以避免使用分布式事务
6.尽量使用单表维度sql,原因:join性能差,后期分库分表更方便,前瞻性设计要考虑使用哪种ID主键策略
16、sql语句中group by的使用
group by 根据字面意思就是对数据进行分组,所谓分组就是将数据集划分为若干个不同的小区域,针对每个小区域单独数据处理
注意:group by语句中select 查询的字段要么就是分组依据,要么在聚合函数中
select 类别,sun(分数) from 成绩 group by 类别 order by sum(分数) desc
17、sql书写顺序
-
SELECT <字段名>
-
FROM <表名>
-
JOIN <表名>
-
ON <连接条件>
-
WHERE <筛选条件>
-
GROUP BY <字段名>
-
HAVING <筛选条件>
-
UNION
-
ORDER BY <字段名>
-
LIMIT <限制行数>;
18、关联语句join与合并语句union
Join是关联语句,可以把两个表通过连接条件合并一个数据集
inner join:内连接,只有在两个表中都符合连接条件的记录才会被收集
left join:左连接的结果包括左表中的所有记录和右表中满足连接条件的记录
right join:右连接的结果包括右表中的所有记录和左表中满足连接条件的记录
full join:全连接的结果是左右表的并集
cross join:左右表做笛卡尔积
union (all):union能将多个select的记录进行合并,这多个记录必须列数一致,列名一致,列数据类型对应一致,如果是union all则返回的记录集包括重复记录