jvm类加载机制怎么理解
我们加载class文件的方式有以下几种:第一种是从本地系统中加载,第二种是从网络下载class文件,第三种是从zip,jar等归档文件中加载class文件,第四种是将java源文件动态编译成class文件,也就是运行时计算而成。接下来就说下类加载机制,所谓的类加载机制就是虚拟机将class文件加载到内存中,并对数据进行效验,转换和初始化,最终形成虚拟机可以直接使用的类型,即java.lang.class。我们将类加载的流程分为以下几个步骤,分别是装载,链接,初始化,使用,卸载,其中,链接又分为准备,验证,解析。首先装载的过程中,我们会通过类的全限定名来获取类的二进制字节流这时就需要一个工具去获取这个二进制字节流,而我们的java中恰好就有这么一段代码模块,可以实现通过类的全限定名来获取该类的二进制字节流的这个动作,我们将实现这个动作的代码模块称为“类加载器”,通过类加载器将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构,也就是我们拿到我们字节流中的有用信息,拿到有用信息之后,我们需要在堆中生成一个代表这个类的java.lang.class对象,作为方法区中对这些数据的访问入口,这就是装载阶段所做的工作,这个时候我们的运行时数据区的方法区以及堆中已经有数据了,方法区有类信息、静态变量、常量而堆中有代表被加载类的java.lang.class对象,接下来就到了链接阶段,链接阶段又分为验证、准备、解析,验证是为了确保class文件中的字节流所包含的信息完全符合虚拟机的要求,准备是为我们类的静态变量分配内存,并将其初始化为默认值,比如int类型赋默认值0,string赋默认值null,解析阶段则是将类中的符号引用转化为直接引用,它的意思是说将常量池中的原有的引用关系替换成内存中的指针,解析完成后就到了初始化阶段,初始化阶段是执行类的构造方法的过程,假设这个类还没被加载,则先加载并链接该类,假设该类的直接父类还没有被初始化,则先初始化其父类,假设类中还有初始化语句,则依次执行这些初始化语句,这后我们就可以使用了
聊下你对JVM的类加载器的理解
类加载器由上而下我们依次划分为Bootstrap ClassLoader启动类加载器,负责加载bin目录下的rt.jar这个是c++代码实现的,下一级是ExtClass Loader扩展类加载器,负责加载java平台中扩展功能的一些jar包,再下一级是AppClassLoader系统类加载器,负责加载classpath中指定的jar包,最后一级是CustomClassLoader自定义类加载器,通过java.lang.classLoader的子类自定义加载class,属于应用程序根据自身需要自定义的classloader,在我们实际应用中,我们会通过一种叫双亲委派的机制来加载一个类,它是一种向上委托,向下加载的方式,首先当我们呢加载一个类时,会向classLoader类中的loaderClass传递该类的全限定名,这是会先判断该类是否被加载过,如果被加载过,则不会被加载,如果没有被加载过,则先向上委托给它的父类加载器,直到委托给bootStrapClassLoader,然后再看该类是否归对应的类加载器加载,如果都不能加载,则会报classNotFoundException类找不到异常,打破双亲委托机制的方式一般是重写类加载器,重写ClassLoader的loadclass方法,不去实现它的递归方法parent.loadClass方法
什么是运行时数据区
java虚拟机是一种抽象的计算机,只要是计算机,那必然要有相应的内存来存储计算,而我们的运行时数据区就是这样一块内存,它一共划分为五部分,分别是方法区、堆、程序计数器、本地方法栈、java虚拟机栈,方法区是在java虚拟机线程之间共享的,它是在虚拟机启动时就会被创建,并且它在逻辑上是堆的一部分,因此也叫非堆,它里面保存的数据有运行时常量池、字段和方法数据,以及方法和构造函数的代码,如果方法区中的内存无法满足分陪请求,则会抛出内存溢出异常;堆和方法区一样,也是线程之间共享,它里面保存类的实例和数组,如果内存不足时则抛出outOffMemoryError内存不足,在java中,有两类方法,一类叫做java方法,一类叫做native方法,我们一般用来执行方法的数据结构叫做栈,栈它的一个特性是先进后出,我们在栈中执行java方法的叫做java虚拟机栈,执行native方法的叫做本地方法栈,在我们线程中,不可能只执行一个方法,因此有一个最小的执行单位叫做栈帧,计算机会通过cpu分配给每一个线程一个时间片来执行方法,因此我们就需要有一个计数器来统计线程中栈帧的执行位置,这个计数器就叫做程序计数器,它是每个栈私有的
为什么java的堆要进行分代设计
java对堆进行分带是为了更有效的利用内存空间,假设我们不对堆进行分带,我们就会在堆中一直生成对象,这时堆中的对象会越来越多,有新生的对象,也有死亡的对象,这些被称为垃圾,这时我们就要回收掉不再使用的内存,为新生的对象腾出足够的内存,这时我们就需要对整个堆进行扫描回收,这样极大的降低了实际工作的效率,这时我们就会发现对象有一个属性叫做生命周期,其实大部分的对象在堆内存中存在的时间都很短,由此发现98%的对象都会在第一次GC时就会被回收,因此我们可以将堆划分为两部分,生命周期比较长的放在一块内存中,叫做old区,生命周期比较短的放在yong区,这样垃圾回收一直会在一个区域进行扫描,最后则会进入到老年代,在yong区每次存活的对象年龄就会增加一次,直到15次垃圾回收依然存活,则会放入到old区,这时old区和Yong区划分的依据已经完成,这时又会发现新的问题,在young区分配对象和回收时,由于内存空间的不连续性,就会产生空间碎片,导致明明有足够的空间而无法分配内存,因此就需要将young区分为伊甸区和S区,伊甸区用来为新生对象分配内存,经过一次垃圾回收后依旧存活的对象则放入到S区,这时发现S区对象的回收年龄不足,又不能放入到old区,则只能将S区分为S0和S1,用于对象回收之间的转移,这就是堆要进行分代设计的原因
为什么Eden:S0:S1是8:1:1
因为在Yound区中,伊甸区是用来存放新生对象的,伊甸区不够大的情况下就导致触发垃圾回收,YoungGC,而GC是需要消耗线程资源的,伊甸区越小,触发GC的次数就越多,因此,根据jvm官方的数据显示98的对象在第一次GC的时候就会被回收,只有2%的对象会在S区进行转换,直至年龄足够后放到old区,考虑到那些2%的对象要在S区进行等待,所以将内存进行扩大,占到10%,所以Eden:S0:S1是8:1:1
如何确定一个对象时垃圾
在java中,我们通过两种方式判断一个对象是否为垃圾,第一种方式为引用计数法,对于某个对象而言,只要应用程序中持有该对象的引用,就说明该对象不是垃圾,如果一个对象没有任何指针对其引用,那么它就是垃圾,会被回收,但是它有一个弊端,那就是如果两个对象相互持有引用,那就导致永远不会被回收,就会导致内存泄漏,第二种方式是可达性分析算法,它通过GCRoot的对象,开始向下寻找,看某个对象是否可达,如果不可达,则被定义为垃圾,可以被回收,能作为GCroot的有类加载器,Thread,虚拟机栈的本地变量表,static成员,常量引用,本地方法栈的变量等
垃圾收集算法
标记清除,它的意思是先标记,在内存中找到需要回收的对象,并且把他们标记出来,然后再清除掉被标记的回收对象这种算法需要对所有对象扫描一遍,因此比较耗时,还会产生大量的不连续的内存碎片
标记复制,它的算法是将内存划分为两块相等的区域,每次只使用其中一块,当其中一块内存用完后,就将还存活的对象复制到另一块上,然后将已经使用的这块内存直接清除掉,这种算法的缺点是空间利用率低
标记整理,它的算法过程与清除算法差不多,都是先进行标记,但是后续不是直接对可回收对象进行清除,而是让所有存活对象都向一端移动,然后清除掉边界以外的内存
分带收集算法
Young区:复制算法,对象在被分配之后,大部分生命周期比较短,因此使用复制效率比较高
Old区一般使用标记清除或者整理,因为old区对象的存活时间比较长,复制来复制区没有必要,因此先做个标记再清理
java中线程创建的方式
线程的创建分为四种方式,第一种为继承Therad类,重写run方法,第二种是实现Runnable接口,重写run方法,第三种就是实现Callable接口,重写call方法,然后配合FutureTask可以获取线程的返回结果,第四中就是基于线程池构建线程,其实他们的本质都是间接或直接实现的Runnable接口
java中线程的状态
在java中,线程具有六种状态,分别是New状态,这种状态下线程刚刚被创建,还不能被cpu调度,当线程启动之后在java层面不被称为就绪和运行,统称为Runnable可运行状态,这时候我们的cpu可能真正调度线程,也可能没有,当我们在持有锁失败的时候,这时候我们的线程就要等待了,这时的状态称为Block阻塞状态,当我们的线程被挂起是就进入到Waiting等待状态,还有一个就是线程执行了sleep方法或者join方法,这时线程的状态就是Timed_waiting,线程执行完之后就进入了结束状态
线程结束的方式
结束线程的方式一般有三种,第一种是使用stop的方式让线程强制结束,由于这种方式太过暴力且过时了,一般不再使用,第二种是使用共享变量的方式终止线程,这种方式是通过修改共享变量的值来终止一个线程,第三种是使用java提供的自带共享变量interrupt方式来终止线程,它的默认值是false,在我们执行它的内部方法后就会变为true,我们可以通过判断true或false来判断线程是否继续执行
java中sleep和wait的区别
首先所归属的类不一样,sleep属于Thread类中的static方法。wait属于Object类的方法
其次它们在进程中属于不同的状态,sleep属于Timed_waiting,到睡眠时间后自动会被唤醒,wait则属于Waiting状态,需要手动唤醒
最后是对锁的响应不同,sleep方法在持有锁或不持有锁的时候都可执行,wait方法只有在持有锁时才可以执行,执行后会释放锁资源
并发编程的三大特性
原子性、可见性、有序性
原子性是指一个操作是不可分割的,不可中断的,一个线程在执行时,另一个线程不会影响到该线程,在并发编程中可以使用以下几种方式来保证原子性,第一种是使用synchronized关键字,第二种是使用CAS,它是在比较并交换时是一个原子性操作,第三种是使用Lock锁来保证原子性
可见性的问题是基于Cpu出现的,cpu的处理速度是非常快的,相比于cpu来说,区主内存中获取数据的这个操作就太慢了,因此cpu提供了L1,L2,L3的三级缓存,每次区主内存中拿完数据后,就会存储到cpu的三级缓存中,由于现在的cpu都是多核的,而且每个线程的工作内存都是独立的,这就导致如果某一个线程对数据做出修改还没有同步到主内存中,而其他线程拿到了修改前的数据进行操作,因此可见性就显得极为重要,在java中,解决可见性的方式有几下几种,第一种是使用volatile关键字,第二种是使用synchronized关键字,第三种是使用Lock锁,第四种是使用final关键字来修饰属性,它修饰的属性在运行期间是不允许修改的,这样就间接保证了可见性
有序性指的是在class文件在执行前会被转换成cpu可以识别的指令,这些指令在不影响最终结果的前提下,cpu会对这些指令进行重排,但是在并发情况下,指令重排会是程序的最终结果出现问题,volatile关键字可以禁止指令重排
什么是CAS,有什么优缺点
CAS,compare and swap比较并交换,它在替换内存中某一个位置的值时,首先查看内存中的值与预期值是否一致,如果一致,则是执行替换的操作,但是CAS只能保证对一个变量的操作是原子性的,无法实现对多行代码实现原子性,CAS还有一个问题是ABA问题,我们一般是通过引入版本号的方式来解决ABA的问题
java中锁的分类
我们一般将锁分为四类,第一类是可重入锁和不可重入锁,第二类是乐观锁和悲观锁,第三类是公平锁和非公平锁,第四类时互斥锁和共享锁,首先来说第一类重入锁,当前线程获取到锁后再次尝试获取同一把锁是可以直接拿到的就叫做可重入锁,如果无法获取则被称为不可重入锁,我们在java中常用的synchronized,reentrantLock,ReentrantReadWriteLock都是可重入锁,而乐观锁和悲观锁的区别是在线程获取不到锁资源是如果直接挂起则是 悲观锁,如果尝试再次让cpu调度,重新自旋获取锁资源的时乐观锁,在java中synchronized,reentrantLock,ReentrantReadWriteLock都是悲观锁,而Atomic原子性类中都是基于CAS实现的乐观锁,公平锁和非公平锁的区别是其他线程是否可以插队获取锁资源,例如线程A获取到锁资源,线程B没有获得锁资源,线程来了以后,看到B在排队等候锁资源,它排到B的后面去获取锁资源则被称为公平锁,而C线程在来了以后,不管B是否在等待获取锁资源,先去竞争一波,拿到则插队成功,没有拿到则依然要排到B的后面,这种情况称为非公平锁,ReentrantLock,ReentrantReadWriteLock都可实现公平锁和非公平锁,互斥锁和共享锁的区别是同一时间点,一个锁能否被多个线程共享,只有一个线程持有则是互斥锁,可以被多个线程共享的称为共享锁
对synchronized的理解
synchronized的锁是基于对象实现的,他可以当做类锁和对象锁,如果用synchronized关键字的类被static修饰,则使用的时当前类.class作为锁,如果没被static修饰,则使用的是当前对象作为锁,另外,在jdk1.6的时候对synchronized做了优化,分别是锁消除,锁膨胀,锁升级,锁消除的意思是如果代码不存在操作临界资源的情况下,不会触犯synchronized锁,所膨胀是指如果在一个循环中,频繁的获取和释放资源,则会将锁的范围扩大,避免不必要的消耗,锁升级是指锁资源逐渐从无锁状态到偏向锁到轻量级锁在到重量级锁的过程
ReentrantLock和synchronized的区别
核心区别:
- ReentrantLock是个类,synchronized是关键字,当然都是在JVM层面实现互斥锁的方式
效率区别:
- 如果竞争比较激烈,推荐ReentrantLock去实现,不存在锁升级概念。而synchronized是存在锁升级概念的,如果升级到重量级锁,是不存在锁降级的。
底层实现区别:
- 实现原理是不一样,ReentrantLock基于AQS实现的,synchronized是基于ObjectMonitor
功能向的区别:
- ReentrantLock的功能比synchronized更全面。
- ReentrantLock支持公平锁和非公平锁
- ReentrantLock可以指定等待锁资源的时间。
什么是AQS
AQS就是abstractQueuedSynchronizer抽象类,它是JUC包下的一个基类,JUC下很多内容都是基于AQS实现的,例如ReentrantLock,CountDownLatch,阻塞队列等,在AQS中提供了一个由voliatile修饰采用CAS方式修改的int类型的state变量,在没有加锁是state值为0,加锁之后值为大于0的数字,其次AQS维护了一个双向链表
JDK中提供了哪些线程池
JDK给我们提供了五种线程池,分别是newFixedThreadPool,我们只用传入一个int类型的数字到当这个线程池的最大线程数和核心线程数,newSingleThreadExecutor,单例线程池,线程池中只有一个工作线程处理任务,newCachedThreadPool,缓存线程池newScheduleThreadPool,定时任务线程池,可以以一定周期去执行一个任务newWorkStealingPool
线程池的核心参数
最大线程数,核心工作线程,非核心工作线程在阻塞队列中的等待时间,等待时间单位,阻塞队列,线程工厂,拒绝策略
JDK提供的几种拒绝策略
在JDK中提供了五种拒绝策略,分别是AbortPolicy,直接抛出一个异常,CallerRunPolic将任务交给调用者处理,DiscardPolic直接丢弃该任务,DiscardOldPolic将阻塞队列中的最早任务丢掉,然后将当前任务再次交给线程池处理,最后一种就是我们自定义自己的拒绝策略
Redis为什么快
redis快有以下三种原因,第一种是纯内存访问,在计算机的世界中,CPU的速度是最快的其次是内存,最后才是硬盘,因为redis是基于内存的,所以它的速度是非常快的,第二种是因为redis是单线程操作,避免了频繁的切换上下文所带来的资源消耗,第三种是由于redis内部采用了很多高效的机制,其中一种就是渐进式ReHash,在Redis中使用的时全局哈希表存储数据,当我们呢一直向哈希表中存储数据时,就会发生链表扩容,这时就需要将原有的元素进行移动到扩容后的表中,如果redis要一次性将所有元素移动到扩容表中,势必会发生卡顿,降低redis的响应速度,因此redis在每次请求时,只复制其中一个索引上的数据到新表中,这种将一次大量的拷贝开销分摊到多次请求的操作就叫做渐进式ReHash,这也是redis速度快的一个原因
为什么要使用Redis
使用redis的原因有两个,一个是高性能,一个是高并发,在我们发送一个请求获取数据的时候,这个请求最终都会到我们的数据库中,例如mysql,MySQL的数据都是存储在磁盘中的,将磁盘中的数据加载到内存必然会有一定的时间,这时我们如果使用redis,因为redis是基于内存访问的,内存到内存的响应速度必然大于磁盘到内存的响应速度,第二个原因是redis的并发量大于mysql,mysql的并发支持量大约是1000/s,而redis的并发量轻松可以达到100000/s
redis适合什么场景
resdis有五种基本类型List,Hash,String,set,ZSet.适合用来做缓存,计数器,排行榜,分布式锁,最新列表,消息队列等,这些都和redis的数据类型有关系
Redis6.0之前为什么不使用多线程
redis不使用多线程的原因有三点,第一个是CPU不是redis的性能瓶颈,它受制于内存和网络带宽,第二个是如果我们想要提高redis的读写性能的话,redis提供了一个叫做Pipeline批量处理技术,它可以使redis每秒处理100万个请求,第三点是因为单线程的维护成本较低,它不用处理多线程并发带来的安全问题和线程切换所带来的开销,因此对于一般的公司来说,单线程的redis来说已经够用了,所以这也是它使用单线程的原因
Redis6.0之后为什么使用多线程
对于小数据量来说,数据到内存的响应时间大概是100纳秒,QPS大约是十万,但是随着公司的体量越来越大,就需要更大的QPS,这就需要用到多线程,但是Redis的多线程是I/O的多线程,在redis内部执行命令还是单线程
怎么理解redis的事务
事务通俗的来讲是一组操作,要么都成功,要么都失败,在redis中,事务是通过multi开始,通过exec命令截止,但是redis的事务功能很弱,它只能对基本的语法错误进行判断回滚,如果只是运行时错误则不会进行回滚
redis的过期策略和内存淘汰机制
redis对于设置了过期时间的key采用的是定时删除和惰性删除策略,因为在redis中,所有设置了过期时间的key在时间到了之后,就会自动删除,但是如果同一时间过期了大量key,那么删除时间就会变长,这样就有可能导致线上读写出现卡顿,因此redis就采用定时扫描的方式来解决这一问题,首先redis会将设置了过期时间的key单独放置在一个字典中,然后就是默认每十秒钟扫描一次这张表,但并不是全部扫描,而是随机从这张表中抽取20个key,删除掉过期的key,并判断过期key所占比例是否超过四分之一,如果超过则再抽取20个key进行判断,否则就等候十秒钟后操作,如果一个过期的key一直到被访问的时候都没有被删除,那么redis则会在访问时对这个key进行检查并删除,并且不会返回任何东西,这种方式就时惰性删除,redis就是通过定期删除这种集中处理加惰性删除这种零散处理的方式来删除过期key的。
当我们的内存超出我们的物理限制时,这时redis的性能就急剧下降,相当于不可用,这时我们就需要淘汰掉一些key让redis继续提供读写服务,redis提供了一下几种缓存淘汰策略,分别是:第一种是默认情况,不淘汰,这种状态下redis只能提供读和删除服务,不能提供写服务,第二种是volatile-lru淘汰掉过期时间中最少使用的key,第三种是volatile-ttl,淘汰掉过期时间中寿命最短的,第四种是volatile-random随机淘汰掉过期时间中的一个key,第五中是allkeys-lru,在全体key中淘汰掉最少使用的key,第六种是allkeys-random在全体key中随机淘汰key
LRU算法
在一个链表中,链表中的所有元素都是按照一定的顺序排列,当链表满的时候,会踢掉尾部的元素,当一个元素被访问时,它在链表中的位置则会被放到头部,因此链表的排列顺序就是元素被访问的时间顺序。
什么是缓存雪崩,缓存穿透、缓存击穿
- 缓存雪崩:指在同一时段大量的缓存失效,导致数据源负载剧增,可能引起服务崩溃。
- 解决方案:将缓存失效时间分散开,比如可以在原有的失效时间基础上增加一个随机值,比如 1-5 分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
- 缓存穿透:指查询不存在的数据,缓存中没有数据,每次都会查询数据库,可能引起数据库崩溃。
- 解决方案:1.查询返回的数据为空,仍把这个空结果进行缓存,但过期时间会比较短;2.布隆过滤器:将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对 DB 的查询。
- 缓存击穿:指一个缓存数据的过期时间临近,导致大量请求查询数据库,可能引起数据库崩溃。
- 解决方案:设置热点数据的缓存时间较长,并且使用分布式锁或者互斥锁,确保只有一个线程去数据库查询。
使用redis如何设计分布式锁
想要实现分布式锁,则必须要求redis具有互斥能力,在redis中,setnx命令则具有此功能,它只有在可以不存在时才可以设置,否则什么也不做,但是当我们某一个线程成功加锁后去访问数据库了,这时如果这个线程挂掉了,那么这个锁就成了死锁,同样失去了我们加锁的本意,因此我们就需要给这把锁加上一个过期时间,这时又有一个问题出现了,就是如果在加锁的时间范围内我们的线程已经成功将数据从数据库拿到并写到redis中还好,如果还没写到缓存中锁已经过期,那么加锁就失去了意义,我们又不能精确每次加锁的时间,所以就需要在分布式锁中加入看门狗守护线程,让看门狗帮我们去查看线程是否完成任务,如果没有完成,则延长过期时间,如果完成,则什么也不做
Redis持久化方式有哪些,有什么区别
redis的持久化分为两种,一种叫做RDB,是redis DataBase的缩写,另一种叫做AOF是Append only file,RDB持久化是当前进程数据生成快照保存到硬盘的过程,是记录某一时刻的状态,但这种持久化方式如果同步的数据比较大时,会造成一定程度的阻塞 ,还有一个问题是RDB可能会造成数据丢失,因为RBD数据持久化是有时间间隔的,不可能每秒如果都进行持久化操作,这样太消耗资源,这样如果redis在持久化之前发生宕机,那么就因为没有快照记录就发生数据丢失无法恢复了,AOF持久化是以独立日志的方式记录每次写命令,AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主要方式,但是AOF的持久化性能没有RDB高,一般情况下,我们在生产环境上采用的是混合方式
什么事BufferPool
BufferPool,缓冲池,简称BP,其作用用来缓存表数据与索引数据,减少磁盘I/O操作,提升效率,BufferPool由缓存数据页和对缓存数据页进行描述的控制块组成,它是以Page页为单位,默认大小是16k
使用索引一定可以提升效率吗
不一定,因为索引是一种将数据库中的数据按照特殊形式存储的数据结构,通过索引,我们能够显著的提高数据的查询效率,从而提升服务器的性能。索引的优点有一,提高数据检索效率,降低数据库的IO成本,二,通过索引对数据进行排序,降低了数据排序的成本,降低了CPU的消耗,索引创建的缺点有,一创建索引和维护索引需要耗费时间,这种时间会随着数据量的增加而增加,二,索引创建需要占用物理空间,当我们创建的索引过多时,索引所占用的物理空间可能超过实际数据存储空间,因此一张表所创建的索引尽量不能超过五个
创建索引是为了提升效率,因此我们创建索引最好遵守以下原则:第一,在我们经常搜索的列上创建索引,可以加快搜索速度,第二,在经常用在连接的列上创建索引,可以加快连接速度,第三在经常需要根据范围搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的,第四在需要进行排序的列上创建索引,因为索引已经排好序,这样查询可以利用索引的排序,加快查询时间,第五在经常使用where子句的列上创建索引,加快条件的判断
说下聚簇索引和非聚簇索引
聚簇索引和非聚簇索引的区别是叶子结点是否存放一整行记录
聚簇索引是将数据和索引放在一块,索引结构的叶子节点保存了行数据,非聚簇索引是将数据与索引分开存储,索引结构的叶子节点指向了数据的对应位置,在InnoDB中主键使用的时聚簇索引,MyISAM不管是主键索引还是二级索引都使用的是非聚簇索引
索引有哪几种类型
索引有五种类型,分别是普通索引,唯一索引,主键索引,复合索引,全文索引
为什么Like以%开头索引会失效
在B+树中的索引是按照首字母大小进行排序,因此%在右边让我们的b+树可以进行有序查找,所以是可以用到索引的,但是%在左边则是因为B+树尾部的数据是没有顺序的,所以不能按照索引顺序进行查询,因此不能使用索引
InnoDB与MyISAM的区别
事务和外键上的区别:InnoDB支持事务和外键,具有安全性和完整性,适用与插入和修改操作的数据库,MyISAM不支持事务和外键,它提供高效的存储和检索,适合大量查询操作的数据库;索引结构上得到区别,InnoDB使用的是聚簇索引,它的索引和数据存储在一起,MyISAM使用的时非聚簇索引,索引和数据是分开存储的;存储文件不同,InnoDB引擎对应两个文件,一个是存储表结构的frm文件,一个是存储索引和数据的ibd文件,MyISAM则有三个文件,一个是存储表结构的frm文件,一个是存储索引的myi文件,一个是存储表数据的myd文件
B树和B+树的区别是什么
explain主要有哪些字段
通过,explain加sql语句,我们可以知道一条sql是否可以被优化,它里面的主要字段有id,如果我们有一个复合sql语句的话,那么它可以通过id看出我们复合语句中sql的执行先后顺序,第二个字段是select_type和table,一个是显示我们要优化的sql是增删改查的哪种,另一个是显示我们要堆那张表执行这个sql,没有什么实际意义,第二个重要字段是type,通过·这个字段显示的内容,我们可以知道sql执行的性能,最常见的性能显示类型从快到慢依次为system系统表,实际优化做不到这种程度、const常量连接,一般索引为主键或唯一索引并且是一个常量、eg_ref主键索引或非空唯一索引的扫描,ref非主键非唯一索引的扫描,range,范围扫描,index索引树扫描,all,全表扫描,这种情况下的sql必须要优化,接下来的字段为possible_key和key,一个表示我们sql中可能用到的索引,一个·表示实际用到的索引,其他的字段都对我们优化sql的意义不是很大
介绍一下Mysql中事务的特性
在关系型数据库中,一个逻辑单元要成为事务,必须满足4个特性,即所谓的ACID,即原子性、一致性,隔离性、持久性,原子性的意思是我们对数据库的操作要么都成功,要么都失败,其中undolog的日志是实现原子性的基础,一致性指的是数据库中的数据影满足完整性约束,它是由原子性,隔离性、持久性共同保证,隔离性指的是数据库事务在执行时不能被其他事务干扰,它的隔离性有四个级别,分别是读未提交、读已提交、可重复度、序列化,他们分别解决了并发状况下的脏读,不可重复读和幻读的问题,隔离性的实现依靠MVCC和加锁来实现的,持久化指的是一个事务一旦提交,它对数据库的改变应该是永久行的,不会因后续的操作或者故障有影响,mysql数据库事务的持久性依靠redolog来保证