java面试题
01、谈谈你对spring的理解
Spring是一种轻量级的框架,他的出现就是为了解耦。把创建对象的过程全部交给spring容器去管理。
Spring框架最大的两个特性就是:ioc + aop
01-1、IOC
IOC(控制反转)是一种设计思想,就是将原本在程序中new对象的过程交给spring容器去管理。
ioc容器它的底层其实就是一个Map,Map中存放着各种各样的对象。
以前创建对象的主动权和时机是由自己把控的,ioc让对象的创建由spring自动生产,通过java的反射机制,根据配置文件在运行时动态的创建对象以及管理对象,并调用对象的方法的。
spring的ioc有三种注入方式 :
- 构造器注入
- set方法注入
- 注解进行注入
01-2、AOP
AOP就是面向切面编程,能够将哪些与业务无关,但却为业务模块所共同调用的逻辑和责任(如事务处理、日志、权限控制)等封装起来,减少系统重复代码,降低模块之间的耦合度,并且有利于未来业务的拓展性。
AOP的实现是基于动态代理的。
动态代理有两种方式:JDK动态代理+ Cglib动态代理
-
JDK动态代理只提供接口的代理,不支持类的代理。要求被代理类要去实现接口。JDK动态代理的核心是InvocationHandler接口和Proxy类,在获取代理对象时,使用Proxy类来动态创建目标类的代理类(即最终真正的代理类,这个类继承自Proxy类,并使实现了我们定义的接口),当代理对象调用真正对象的方法时,InvocationHandler通过invoke()方法反射来调用目标类中的代码,动态的将横向逻辑和业务交互在一起。
-
如果被代理类没有实现接口,那么spring AOP会选择使用Cglib来动态代理目标类。cglib是一个代码生成的类库,可以在运行时动态的生成 指定类的一个子类对象,并覆盖其中特定的方法来添加增强代码,从而实现Aop。
Cglib是通过继承的方式做的动态代理,因此如果某个类被标记为finnal,那么他是无法使用cglib来做动态代理的。
02、谈谈Spring Bean的生命周期
简单来说,Spring Bean的生命周期分为四个阶段:
实例化(Instantiation)—> 属性赋值 (Populate) —> 初始化(Initialization) —> 销毁(Destruction)
03、谈谈Spring中 Bean的作用域
- singleton:默认作用域,单例bean,每个容器中只有一个Bean
- prototype:为每一个Bean请求创建一个实例
- request:为每一个request请求创建一个实例,在请求完成后,bean会失效并被垃圾回收器回收。
- session:同一个会话共享一个实例,不同会话使用不同的实例。
- global-session:全局作用域,所有会话共享一个实例。
04、谈谈Spring如何解决循环依赖的问题
1、Spring解决的单例模式下的setter方法依赖注入引起的循环依赖问题,主要是通过两个缓存来解决的,请看下图:
解决: 二级缓存 + 三级缓存
public class ClassA {
private ClassB classB;
public ClassB getClassB() {
return classB;
}
public void setClassB(ClassB classB) {
this.classB = classB;
}
}
public class ClassB {
private ClassA classA;
public ClassA getClassA() {
return classA;
}
public void setClassA(ClassA classA) {
this.classA = classA;
}
}
2、对于通过构造方法进行依赖注入时产生的循环依赖问题没办法自动解决,那针对这种情况,我们可以使用@Lazy
注解来解决。
类A和类B都是通过构造器注入的情况,可以在A 或者B 的构造函数的形参上加个@Lazy注解实现延迟加载。
@Lazy实现原理是:当实例化对象时,如果发现参数或者属性有@Lazy注解修饰,那么就不直接创建所依赖的对象了,而是使用动态代理创建一个代理类。
比如,类A的创建:A a=new A(B),需要依赖对象B,发现构造函数的形参上有@Lazy注解,那么就不直接创建B了,而是使用动态代理创建了一个代理类B1,此时A跟B就不是相互依赖了,变成了A依赖一个代理类B1,B依赖A。但因为在注入依赖时,类A并没有完全的初始化完,实际上注入的是一个代理对象,只有当他首次被使用的时候才会被完全的初始化。
05、谈谈你对mybatis框架的理解
我在开发 渭师学生综合素质管理系统 项目的时候,使用过mybatis。
我对mybatis项目的认识是:他其实就是orm持久层框架,其实就对jdbc一个封装而得的框架,使用的好处其实就可以把jdbc从连接的开辟,事务管理以及连接关闭,和sql执行,对数据的映射到pojo整个过程的一个封装而已。他的整个过程的执行流程是:
- 首先会引入mybatis依赖,然后会 定义xml核心配置文件 放在类路径resources下,这个文件里面就描述了数据源、mapper映射、别名的映射、属性配置等,定义好之后,那么接下来就是创建一个sqlSessionFactory对象。但是在创建这个对象之前,我们会进行xml文件的解析,解析过程会使用到SqlSessionFactoryBulider里面提供了一个bulid方法,这个方法做了一个非常核心的事情:初始化Configuration对象,并且把对应类的属性 的 对象全部初始化。并且解析核心xml文件:
- 把解析核心的xml配置文件的内容放入到Configuration对象的属性中。其中就包括别名的映射,在初始化阶段别名映射会自动注册一些常用的别名,如果我们自己也配置了,则也会自动注入到Configuration对象的TypeAliasRegistry的map中。
- 并且在配置文件中的数据源和事务解析以后放入到Environment,给后续的执行,提供数据连接和事务管理。
- 然后在解析xxxxMapper.xml配置文件,根据配置文件解析的规则,会解析里面对应的节点,比如:<select <update <insert <delete <cache <cache-ref <resultMap等,然后把每个解析的节点放入一个叫MapperStatement对象,sql语句就放在这个对象的sqlSource属性中。
- 并且把解析的每一个节点对应的MapperStatement同时放入到Configruation全局的Map中(mapperstatements)中。以节点的id和命名空间+id作为key,以MapperStatement对象作为value,给后续执行提供一个参考和方法。
后续执行如下:
06、请你谈谈你对SpringMVC执行流程理解
- 当用户发送一个请求的时候,回去匹配前端控制器DispatcherServlet的请求路径路径(在web.xml中指定),web容器将该请求转交给DispatcherServlet去处理。
- DispatchServlet收到请求后,会根据 请求信息 交给处理器映射器(HandlerMapping)
- HandlerMapping 会根据用户的url请求 , 查找匹配该url的 Handler, 并返回一个执行链
- DispatchServlet 再去 请求 处理器适配器(HanderAdapter) 调用相应的 Handler进行处理并返回ModelAndView给 DispatcherServlet
- DispatchServlet 将 ModelAndView 请求 ViewReslover(视图解析器)解析,并返回具体的View
- DispatchServlet 对 View进行 渲染视图(将数据模型填充到试图中)
- DispatcherServlet将页面相应给用户
07、ArrayList和LinkedList的区别
- ArrayList的底层是数组,LinkedList的底层是链表
- ArrayList的查询效率快,增删改效率比较慢;LinkedList的增删改效率快,查询效率慢。
08、实现线程有几种方式
- 继承Thread并重写run方法,并调用start方法,无返回值
- 实现Runnable接口,并用其初始化Thread,然后创建Thread实例,并调用start方法,无返回值
- 实现Callable接口,并用其初始化Thread,然后创建Thread实例,并调用start方法,可以有返回值
- 使用线程池创建
09、多线程中start方法和run方法的区别
- 调用start方法方可启动线程
- 而run方法只是Thread类中一个普通方法的调用,还是在main线程中执行
t.start(); //该行代码相当于是启动线程,
t.run(); //该行代码相当于是使用Thread这个类中的run方法而已.
10、线程都有那些状态?
12、怎么停止线程
- 不推荐使用JDK提供的stop() 、destroy()方法。
- 推荐线程自己停止下来。设置一个公开的方法。
- 建议使用一个标志位 进行终止变量,当flag=false ,则终止线程运行。
// 1.设置一个标志位
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag){
System.out.println("run....Thread"+i++);
}
}
// 2.设置一个公开的方法停止线程 , 转换标志位
public void stop(){
this.flag = false;
}
public static void main(String[] args) {
TestStop testStop = new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 1000; i++) {
System.out.println("main"+i);
if (i == 900){
// 调用自己写的stop方法 , 切换标志位, 让线程停止
testStop.stop();
System.out.println("线程该 停止了");
}
}
}
13、线程跟进程的区别
进程:一个程序,qq.exe,music.exe ,程序的集合
一个进程往往包含了多个线程,至少包含一个
Java默认有两个线程? 2个 main线程、GC线程
14、List、Set、Map是线程安全吗?
答案 : 都存在安全隐患
推荐使用java.util.concurrent包下的:
- CopyOnWriteArrayList 、Vector
- CopyOnWriteArraySet
- ConcurrentHashMap
HashSet底层是什么? hashset的本质 就是 map的key,因为key是无法重复的!
public HashSet() {
map = new HashMap<>();
}
// add. hashset的本质 就是 map的key,因为key是无法重复的!
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
// PRESENT. 不变的值!
private static final Object PRESENT = new Object();
15、JUC常用的工具类有哪些?
15-01、CountDownLatch
countDownLatch.countDown()
// 数量 -1
countDownLatch.await()
//等待计数器归零,然后再向下执行!
每次有线程调用**countDown()**时候数量-1,假设计数器变为0;**countDownLatch.await()**就会被唤醒,继续向下执行!
15-02CyclicBarrier(加法计数器)
15-03、Semaphore(信号量)
semaphore.acquire()
获得,假设已经满了,等待,等待被释放为止
semaphore.release()
释放,会将当前的信号量释放+1,然后唤醒等待的线程!
使用场景:
1、多个共享资源互斥的时候使用!
2、并发限流,控制最大的线程数!
16、谈谈你对线程池的理解
池化技术: 事先准备好一些资源放在池子里,有人要用,就来我这里拿,用完之后再还给我!
线程池 : 三大方法 + 7 大参数 + 4种拒绝策略
16-01、线程池3大方法
所谓3大方法,就是创建线程池的三种方法:
- Executors.newSingleThreadExecutor() // 通过工具类创建单个线程的线程池
- Executors.newFixedThreadPool(5) // 通过工具类创建一个大小为5的线程池
- Executors.newCachedThreadPool() // 通过工具类创建有弹性的,遇强则强,遇弱则弱的线程池
16-02、线程池的7大参数
// 1、Executors.newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
// 2、Executors.newFixedThreadPool(5)
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
// 3、Executors.newCachedThreadPool()
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, // 21亿 OOM
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
// 本质调用了ThreadPoolExecutor(7大参数)
public ThreadPoolExecutor(int corePoolSize, // 核心线程池大小
int maximumPoolSize, // 最大线程池大小
long keepAliveTime, // 超时了没有人调用就会释放
TimeUnit unit, // 超时的单位
BlockingQueue<Runnable> workQueue, // 阻塞队列
ThreadFactory threadFactory, // 线程工厂,创建线程的;一般不用动
RejectedExecutionHandler handler //拒绝策略 ) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}

16-03、四种拒绝策略
四种拒绝策略总结:
new ThreadPoolExecutor.AbortPolicy() // 银行满了,还有人进来,就不处理这个人的了,抛出异常**(默认)**
new ThreadPoolExecutor.CallerRunsPolicy() // 哪里来的去哪里! 我不受理,让main线程去受理!
new ThreadPoolExecutor.DiscardPolicy() // 队列满了,不会抛出异常。 丢掉任务,不执行这个线程!
new ThreadPoolExecutor.DiscardOldestPolicy() // 队列满了, 抛弃队列中最老的那个,代替他的位置进入到队列中,也不会抛出异常!
17、四大函数式接口
17-01、Function 函数式接口

17-02、Predicate 断定型接口

17-03、Consumer 消费型接口

17-04、Supplier 供给型接口

18、请你谈谈你对Volatile的理解
Volatile 是Java 虚拟机提供的轻量级的同步机制
1、保证可见性
2、不保证原子性
3、禁止指令重排
19、请你谈谈你对单例模式的理解
单例模式之懒汉模式和饿汉模式:
-
懒汉模式只有用到的时候对象才初始化。
-
饿汉模式是无论用到与否,都先初始化,无论用不用我都在这里
package com.kuang.juc.single;
// 饿汉式单例
public class Hungry {
// 可能会浪费空间
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
private Hungry(){
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
package com.kuang.juc.single;
import com.sun.org.apache.regexp.internal.RE;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
// 懒汉式单例模式
public class LazyMan {
// 标志位
private static boolean panghu = false;
// 加锁,防止反编译
private LazyMan(){
synchronized (LazyMan.class){
if (panghu==false){
panghu = true;
}else {
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
private volatile static LazyMan lazyMan; // 避免指令重排
// 双重检测锁模式的 懒汉式单例 DCL懒汉式
private static LazyMan getInstance(){
if (lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan = new LazyMan(); // 不是原子性操作
/**
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*
* 123
* 132 A
* B //此时LazgyMan还没有完成构造
*/
}
}
}
return lazyMan;
}
//反射!
public static void main(String[] args) throws Exception {
// 1、用反射破坏,解决私有构造再加锁
// LazyMan instance1 = LazyMan.getInstance();
// //1、获得反射对象
// Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
// declaredConstructor.setAccessible(true); //无视私有的构造器
// LazyMan instance2 = declaredConstructor.newInstance(); //通过反射创建对象
//
// System.out.println(instance1);
// System.out.println(instance2);
// 2、直接通过反射连续创建两个对象 解决加标志位解决!
// Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
// declaredConstructor.setAccessible(true); //无视私有的构造器
// LazyMan instance1 = declaredConstructor.newInstance(); //通过反射创建对象
// LazyMan instance2 = declaredConstructor.newInstance(); //通过反射创建对象
//
// System.out.println(instance1);
// System.out.println(instance2);
// 3、如果标志位被人反编译出来,知道了 ,再次破解了
Field panghu = LazyMan.class.getDeclaredField("panghu");
panghu.setAccessible(true);
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true); //无视私有的构造器
LazyMan instance1 = declaredConstructor.newInstance(); //通过反射创建对象
panghu.set(instance1,false);
LazyMan instance2 = declaredConstructor.newInstance(); //通过反射创建对象
System.out.println(instance1);
System.out.println(instance2);
}
}
// 双重检查锁模式说明:
// 这个方法首先判断变量是否被初始化,没有被初始化,再去获取锁。
// 获取锁之后,再次判断该变量是否被初始化。
// 第二次判断目的在于有可能其他线程获取过锁,已经初始化过变量。第二次检查通过了,才会真正初始化变量。
// 这个方法检查判定两次,并使用锁,所以形象称为双重检查锁模式。
package com.kuang.juc.single;
//静态内存类
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.holder;
}
public static class InnerClass{
private static final Holder holder = new Holder();
}
}
20、什么是CAS
CAS是compare and swap的缩写,即我们所说的比较交换。cas是一种基于锁的操作,而且是乐观锁。在java中锁分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加version来获取数据,性能较悲观锁有很大的提高。
CAS :比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么执行操作!如果不是就一直循环(底层是自旋锁)
缺点:
1、循环会耗时
2、一次性只能保证一个共享变量的原子性
3、存在ABA问题
21、如何解决ABA问题

如何解决这个问题: 使用原子引用,对应的思想乐观锁! 带版本号的原子操作!
22、如何看待死锁?

如何排查死锁?
1、看日志
2、看堆栈信息!
1、使用jps -l
定位进程号

2、使用jstack 进程号
查看死锁的堆栈信息:

23、请你谈谈JVM的体系结构
24、请你谈谈类加载器
类加载器: 加载class文件
1.启动类(根)加载器(rt.jar):主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。
2.扩展类加载器(jre\lib\ext):主要负责加载jre/lib/ext目录下的一些扩展的jar。
3.应用程序加载器(AppClassLoader):主要负责加载应用程序的主函数类
25、请你谈谈双亲委派机制
分析:
当一个Student.class这样的文件要被加载时。不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。
双亲委派机制的工作原理:
- 如果一个类加载器收到了类加载请求,它并不会自己先加载,而是把这个请求委托给父类的加载器去执行;
- 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的引导类加载器;
- 如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成加载任务,子加载器才会尝试自己去加载,这就是双亲委派机制;
- 父类加载器一层一层往下分配任务,如果子类加载器能加载,则加载此类,如果将加载任务分配至系统类加载器也无法加载此类,则抛出异常;
- 即从应用程序加载器一层一层向上询问,是否已经加载过,若未加载,再从顶层向下加载。即根加载器内找此类,若已加载则结束,若无此类,再在扩展类加载器中找,若存在则结束。若不存在,则在应用程序加载器中找,若存在则结束,若不存在返回null。
26、请你分析一下new 一个对象在内存中的变化
我们知道,java中在类的实例化的过程中,内存中会使用这三个区域,栈区,堆区和方法区。
在程序执行过程中,首先,类中的成员变量和方法体进入到方法区。
然后main()函数方法体进入到栈区,这个过程称为压栈,定义了一个用于指向Student类的实例
接下来在堆内存中创建一个实例 Student s = new Student()的实例。然后将 成员变量和成员方法放在new实例中。将成员方法放在new实例的过程就是取成员方法的地址值!
接下来,s.name = “张三”;s.age = 18;
现在栈区中找到s,然后根据地址值找到堆中存放具体的值进行赋值操作!
然后调用方法体void speak(),在调用方法体的过程中就是先去栈中找到对象s,后根据引用地址找到堆中存放这个对象,再根据这个对象的方法的地址值找到方法区中具体的方法进行执行!
在方法体void speak();被调用完成后,就会立刻马上从栈内弹出(方法出栈)
最后,在main()函数完成后,main函数也出栈
以上便是完整的java类实例化时内存中所发生的变化。
27、请你说说GC垃圾回收常用的算法
JVM在进行GC时,并不是对这三个区域统一回收,大部分时候,回收都是在新生代~
- 新生代
- 幸存from区 幸存to区
- 老年区
GC两种类 : 轻GC (普通的GC), 重GC(全局GC)
1、引用计数法(用的较少)
清除使用较少的!
2、复制算法(Eden)
- 好处:没有内存碎片~
- 坏处:浪费了内存空间~: 多了一半空间永远是空的(to区)。假设对象100%存活(极端情况)
复制算法最佳使用场景: 对象存活率较低的时候,在伊甸园区的时候~
3、标记清除算法
缺点:两次扫描严重浪费时间,会产生内存碎片~
优点:不需要额外的空间~
再优化!!!
标记清除压缩
使用场景:
先标记清除几次,再压缩~
总结:
内存效率:复制算法 > 标记清除算法 > 标记压缩算法(时间复杂度)
内存整齐度:复制算法 = 标记压缩算法 > 标记清除算法
内存利用率:标记清除算法 = 标记压缩算法 > 复制算法
思考一个问题: 难道没有最优算法吗?
答案: 没有,没有最好的算法,只有最合适的算法 —> GC : 分代收集算法
分代收集算法
年轻代 : 存活率低,使用复制算法!
老年代:区域大,存活率高,使用标记清除算法(内存碎片不是很多) + 标记压缩(内存碎片达到一定量) 混合实现
26、谈谈你对Springboot的理解
Springboot三大特性
1、帮助开发者快速整合第三方框架(原理maven依赖封装)
2、内嵌服务器(原理java语言创建服务器)
3、完全注解形式代替XML
27、Synchonized 和 Lock 区别
- Synchoizned 是一个内置的关键字;Lock是一个java类
- Synchonized 无法判断获取锁的状态;Lock可以判断是否获取到了锁
- Synchonized 会自动释放锁;Lock必须要手动释放锁! 如果不释放锁**,死锁!**
- Synchonized 线程一(获得锁、阻塞)、线程二 (等待,傻傻的等);Lock不一定会等待下去!
- Synchonized 可重入锁,不可以中断的,非公平的;Lock,可重入锁,可以判断锁,非公平(可以自己设置)
- Synchonized 适合锁少量的代码同步问题 ;Lock适合锁大量的同步代码!
28、htpps和http的区别
- http不需要证书,而https需要申请ca证书
- http下的传输 是明文传输的,https协议是由ssl + http 协议构建的,可进行加密传输、身份认证的网络协议,可防止传输内容被随意篡改,比http协议更加安全
- http是80端口,http是443端口。
29、String和StringBuilder、StringBuffer的区别
- String : String的值创建后不能修改,任何对String的修改都会引发新的String对象的产生
- StringBuffer:跟String类似,但是值是可以被修改的,使用了synchronized来保证线程安全。
- StringBuilder:StringBuffer是非线程安全版本,没有使用synchronized,它具有更高的性能。
30、mysql查询语句的执行流程
Select 语句执行顺序:
from > where > group by > having > select > order by > limit
31、mysql的事务隔离级别
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | 是 | 是 | 是 |
不可重复读 | 否 | 是 | 是 |
可重复读 | 否 | 否 | 是 |
串行化 | 否 | 否 | 否 |
mysql默认的事务隔离级别为可重复读
用于解决什么问题?
主要用于解决脏读、不可重复读、幻读
- 脏读:一个事务读取到了另一个事务还未提交的数据。
- 不可重复读:在一个事务中多次读取同一个数据时,结果出现不一致。
- 幻读:在一个事务中使用的sql两次读取,第二次读取到了其他事务新插入的行。
不可重复读 注重数据的修改,而 幻读 注重数据的插入。
32、mysql索引
索引的四种基本类型
- 主键索引: 数据列不允许重复,不允许为NULL,一个表只能有一个主键。
- 唯一索引: 数据列不允许重复,允许为NULL值,一个表允许多个列创建唯一索引。
- 普通索引: 基本的索引类型,没有唯一性的限制,允许为NULL值。
- 全文索引: 是目前搜索引擎使用的一种关键技术。
说明 : 后续还会有
集合的面试题Springcloud面试题
mysql面试题
redis面试题
Rabbitmq面试题
常见的linux命令的面试题
明天更新!! 干了,Xdm!!!