2020年面试官常问点总结

2 篇文章 0 订阅
0 篇文章 0 订阅

2020年面试笔记

             最近冒着下雨去试试水......

 

  • 基础
  1. 有哪些集合?

常用的三大类集合:Set、List、Map。

 

1.单列各个集合底层数据结构和基本性质

一.List集合:(有序,元素可以重复)

1.ArrayList集合:

1).底层数据结构是数组,查找快,增删慢。

2). 线程不安全,效率高

2.Vector集合:

1) 底层数据结构是数组,查询快,增删慢

2)线程安全,效率低

3.LinkedList集合:

1) 底层数据结构是链表,查询慢,增删快

2)线程不安全,效率高

二、Set集合(元素不可重复,元素唯一)

1.Hashset集合:

1) 底层数据结构是哈希表,底层实际是用HashMap实现的(HashSet本质就是一个简化版的HashMap),哈希表依赖两个方法hascode ()和equals()方法

2)两个方法的执行顺序:

首先判断hascode()值是否相同

是:继续执行equals()方法,看其返回值

是true:说明元素重复,不添加

是false:就直接添加元素

否:就直接添加到集合

2.Treeset集合:

底层数据结构是二叉树

2.双列各个集合底层数据结构和基本性质

1).java中的Map集合是双列的,Map集合是Map集合家族的根接口,它有HashMap和TreeMap集合两个子类。

2).Map集合中只对键有效,而对值是无效的。

3).子类的数据结构:

Hashmap:底层数据结构:数组+链表,Treemap底层数据结构是二叉树,其性质和Set的底层数据结构一样。

3、java中的集合的选择

根据实际需求来选择合适的集合。

1.单列还是双列:

单列就选Collection类型的,双列选Map类型的

2.使用场景

选择单列后看元素是否唯一

是:选择Set集合

看元素是否排序

是:TreeSet

否:HashSet

否: 选择List集合

安全性高低:

高:Vector

低:ArrayList 或LinkedList ’

操作类型:

增删多:LinkedList

查询多:ArrayList

  1. HashMap的存储原理?HashSet的存储原理?

当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。

注:jdk1.8 后同一个位置的链表超过8个会自动转为红黑二叉树算法。

HashSet是采用哈希算法实现,底层实际是用HashMap实现的(HashSet本质就是一个简化版的HashMap),因此,查询效率和增删效率都比较高。

  1. 数组和集合的扩容?

Map扩容的原理  

创建HashMap对象默认情况下,数组大小为16。

开始扩容的大小=原来的数组大小*loadFactor。

扩容后大小是原来的2倍,其中加载因子loadFactor的默认值为0.75,这个参数可以再创建对象时在构造方法中指定。

例如:

16*0.75=12,默认创建一个map对象数组大小是16,当map添加12个元素到的时候就发生扩容,创建新的数组的大小2*16=32,然后重新计算每个元素在新数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

 

数组扩容的原理

1)Java数组对象的大小是固定不变的,数组对象是不可扩容的。

2)利用数组复制方法(Arrays.copyOf())可以变通的实现数组扩容。

3)Arrays.copyOf() 的底层是System.arraycopy()可以复制数组。

4)Arrays.copyOf()创建数组副本的同时将数组长度增加就变通的实现了数组的扩容。

扩容后新的数组容量newCapacity = oldCapacity + (oldCapacity >> 1); 这里使用了位运算,向右移1位,相当于原来容量的一半,再加上原容量,那就是原容量的1.5倍。

  1. hashmap和Hashtable的区别?实现原理?

HashMap继承自AbstractMap类。但二者都实现了Map接口。
Hashtable继承自Dictionary类,Dictionary类是一个已经被废弃的类(见其源码中的注释)。父类都被废弃,自然而然也没人用它的子类Hashtable了。

HashMap线程不安全,HashTable线程安全

底层都是数组+链表实现
Hashtable:
1.无论是key还是value都不能为null,线程安全,实现线程安全的方式是在修改数据时锁住整个Hashtable,效率低
2.初始size为11,扩容:newsize=oldsize*2+1
Hashmap:
1.可以存储null键和null值,线程不安全
2.初始size为16,扩容:newsize =oldsize*2,size一定为2的n次幂

  1. String,StringBuffer与StringBuilder的区别

String 字符串常量

StringBuffer 字符串变量(线程安全)

StringBuilder 字符串变量(非线程安全)

StringBuffer与StringBuilder两者共同之处:可以通过append、indert进行字符串的操作。

 简要的说, String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。
 而如果是使用 StringBuffer 类则结果就不一样了,每次结果都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,再改变对象引用。所以在一般情况下我们推荐使用 StringBuffer ;

StringBuffer
Java.lang.StringBuffer线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。
可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。
StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。

java.lang.StringBuilde
java.lang.StringBuilder一个可变的字符序列是5.0新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。两者的方法基本相同。

  1. hash碰撞?

hash碰撞---对象Hash的前提是实现equals()和hashCode()两个方法,那么HashCode()的作用就是保证对象返回唯一hash值,但当两个对象计算值一样时,这就发生了碰撞冲突。理解hash结构才能很好的去使用。如:伪造了一个和密文一样的原文,也就是加密不安全了。

碰撞处理

   通常有两类方法处理碰撞:开放寻址(Open Addressing,)法和链接(Chaining)法。前者是将所有结点均存放在散列表T[0..m-1]中;后者通常是把散列到同一槽中的所有元素放在一个链表中,而将此链表的头指针放在散列表T[0..m-1]中。

(1)开放寻址法(扰动函数算法,促使元素位置分布均匀,减少碰撞几率)

  所有的元素都在散列表中,每一个表项或包含动态集合的一个元素,或包含NIL。这种方法中散列表可能被填满,以致于不能插入任何新的元素。在开放寻址法中,当要插入一个元素时,可以连续地检查或探测散列表的各项,直到有一个空槽来放置待插入的关键字为止。

(2)链接法

  将所有关键字为同义词的结点链接在同一个链表中。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数组T[0..m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。在拉链法中,装填因子α可以大于1,但一般均取α≤1。

  1. 类加载机制?

  Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数,属性和方法等,Java允许用户借由这个Class相关的元信息对象间接调用Class对象的功能。虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

类加载工作流程:总结出如下四点:

1、类的加载过程采用委托模式实现

2、每个 ClassLoader 都有一个父加载器。

3、类加载器在加载类之前会先递归的去尝试使用父加载器加载。

4、虚拟机有一个内建的启动类加载器(bootstrap ClassLoader),该加载器没有父加载器,但是可以作为其他加载器的父加载器。

  1. 在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational)的方法注解会生效吗?

注解是不会生效的。

 

  1. Jdk1.8有哪些特性?

Lambda表达式

函数式接口

*方法引用和构造器调用

Stream API

接口中的默认方法和静态方法

新时间日期API

1. HashMap

主要还是HashMap中链长度大于8时采取红黑树的结构存储。(1.7的时候是链表结构)
红黑树,除了添加,效率高于链表结构。

2. Lambda表达式

Lambda表达式的基础语法:Java8引入了一个新的操作符“->”,该操作符成为箭头操作符或者Lambda操作符,箭头操作符将Lambda表达式拆分成两部分

语法格式一:无参数,无返回值

Runnable r2 = () -> System.out.println("hello lambda");

        r2.run();

语法格式二:有一个参数,并且无返回值

(x) -> System.out.print(x);

语法格式三:若只有一个参数,小括号可以省略不写

x -> System.out.print(x);

语法格式四:有两个以上的参数,有返回值,并且Lambda体中有多条语句

        Comparator<Integer> c1 = (x, y) -> {

            System.out.print(Integer.compare(x, y)+"函数式接口");

            return Integer.compare(x, y);

        }  ;

        c1.compare(1, 2);

  1. 接口的默认方法

Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法

  1. 新的日期时间API

LocalDate该类的实例是一个不可变对象,它只提供了简单的日期,并不含当天的时间信息。另外,它也不附带任何与时区相关的信息。

LocalTime,该类用于一天中的时间,比如13:45:20

LocalDateTime,是LocalDate和LocalTime的合体。它同时表示了日期和时间,但不带有时区信息,你可以直接创建,也可以通过合并日期和时间对象构造

  1. 线程的实现方式?
  1. 继承Thread类,重写run方法(其实Thread类本身也实现了Runnable接口)

  每次创建一个新的线程,都要新建一个Thread子类的对象

  启动线程,new Thread子类().start()

创建线程实际调用的是父类Thread空参的构造器

public class MyThread {

    public static void main(String ards[]){

        for(int i=0;i<10;i++){

            new ExtendsThread().start();

        }

        System.out.println(Thread.currentThread().getName());

    }

}

class ExtendsThread extends Thread{

    @Override

    public void run() {

        System.out.println(Thread.currentThread().getName());

    }

}

  1. 实现Runnable接口,重写run方法

  不论创建多少个线程,只需要创建一个Runnable接口实现类的对象

  启动线程,new Thread(Runnable接口实现类的对象).start()

   创建线程调用的是Thread类Runable类型参数的构造器

public class MyThread {

    public static void main(String ards[]){

        Runnable implRunnable = new ImplRunnable();

        for(int i=0;i<10;i++){

            new Thread(implRunnable).start();

        }

        System.out.println(Thread.currentThread().getName());

    }  

}

class ImplRunnable implements Runnable{

    private volatile  int i = 0;

    @Override

    public void run() {

        System.out.println(Thread.currentThread().getName()+"--"+ i++);

    }

}

  1. 实现Callable接口,重写call方法(有返回值)

  自定义类实现Callable接口时,必须指定泛型,该泛型即返回值的类型

  每次创建一个新的线程,都要创建一个新的Callable接口的实现类、

  如何启动线程?

    (1)创建一个Callable接口的实现类的对象

    (2)创建一个FutureTask [ˈfjuːtʃər] 对象,传入Callable类型的参数

public FutureTask(Callable<V> callable){……}

    (3)调用Thread类重载的参数为Runnable的构造器创建Thread对象

        将FutureTask作为参数传递

public class FutureTask<V> implements RunnableFuture<V>

public interface RunnableFuture<V> extends Runnable, Future<V>

  1. 使用线程池(有返回值)
  • 使用ThreadPoolExecutor  [ɪgˈzɛkjətər] 工具类中的方法创建线程池(阿里开发手册不推荐Executors)。
  • 设置核心池的数量为 CPU 数的两倍,一般是 4、8,好点的 16 个线程
  • 最大线程数设置为 64
  • 空闲线程的存活时间设置为 1 秒

 

   2. 线程池的参数有哪些?回滚策略那些?

1.五大参数(后三个可选)

a核心线程数

b 最大线程数

c 线程空闲时间

d 阻塞队列大小:queueCapacity

e 任务拒绝处理器 :rejectedExceptionHandler 

f 空闲存货时间

g 空闲时间单位

l 线程工厂,创建线程的,一般不用动

2.四种拒绝策略:

  当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

1.ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

2.ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。

3.ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

4.ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务  

  1. 线程池的关闭

ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:

shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务

shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

  1. 有几种线程池?

Java通过Executors[ɪgˈzɛkjʊtəz]提供四种线程池,分别为:

·  newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程

·  newFixedThreadPool:创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。

·  newSingleThreadExecutor:创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。

·  newScheduleThreadPool
创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。

  1. 多线程:最大线程数在什么情况下这个参数会不起作用

OOM(Out of Memory)异常(Java虚拟机内部的原理。知道就好):堆内存溢出

,如:死循环。

内存 memory [ˈmeməri]

 

5.线程池的原理:  

线程池,究竟是怎么一回事?其实线程池的原理很简单,类似于操作系统中的缓冲区的概念,它的流程如下:

先启动若干数量的线程,并让这些线程都处于睡眠状态,当客户端有一个新请求时,就会唤醒线程池中的某一个睡眠线程,让它来处理客户端的这个请求,当处理完这个请求后,线程又处于睡眠状态。

可能你也许会问:为什么要搞得这么麻烦,如果每当客户端有新的请求时,我就创建一个新的线程不就完了?这也许是个不错的方法,因为它能使得你编写代码相对容易一些,但你却忽略了一个重要的问题——性能!

就拿我所在的单位来说,我的单位是一个省级数据大集中的银行网络中心,高峰期每秒的客户端请求并发数超过100,如果为每个客户端请求创建一个新线程的话,那耗费的CPU时间和内存将是惊人的,如果采用一个拥有200个线程的线程池,那将会节约大量的的系统资源,使得更多的CPU时间和内存用来处理实际的商业应用,而不是频繁的线程创建与销毁。

 

  1. 秒杀设计思路?

1:秒杀活动开始之前,将商品的SKU和库存存入redis中

redisTemplate.expire(商品的SKU,25,TimeUnit.HOURS);//以商品的唯一标识做为key,缓存24小时

2:商品下单方法中增加一个计数器,以商品SKU为KEY,计数器为原子性,不存在并发问题long count=redisTemplate.opsForValue0.increment(商品SUK,1);

3:提交订单时,从redis中获取商品SKU的库存量,和计数器中的count比较,如果大于等于count,说明秒杀完了

4:同步库存量到数据库

  1. 高并发下怎么保证两个数据库的数据一致性(如修改)?

解决方案:对多个更新操作的业务加事物注解。在数据库表中加一个vesion版本控制字段(初始值为0)在更新操作前查询并记录该字段,更新操作完成vesion+1,再次查询vesion与更新操作前记录的值相差1说明前后数据一致,否则回滚更新操作;

  1. 高并发下怎么保证数据库和缓存的数据一致性?

大致网上会有几种简单的方案:

1.先更新缓存,后更新数据库

2.先更新数据库,后更新缓存

3.先删除缓存,后更新数据库

4.先更新数据库,后删除缓存

5.先删除缓存,后更新数据库,再删除缓存

 

缓存和数据库一致性解决方案

1.第一种方案:采用延时双删策略

在写库前后都进行redis.del(key)操作,并且设定合理的超时时间。

伪代码如下

public void write(String key,Object data){

redis.delKey(key);

db.updateData(data);

Thread.sleep(500);

redis.delKey(key);

}

 

2.具体的步骤就是:

1)先删除缓存

2)再写数据库

3)休眠500毫秒

4)再次删除缓存

那么,这个500毫秒怎么确定的,具体该休眠多久呢?

需要评估自己的项目的读数据业务逻辑的耗时。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。

当然这种策略还要考虑redis和数据库主从同步的耗时。最后的的写数据的休眠时间:则在读数据业务逻辑的耗时基础上,加几百ms即可。比如:休眠1秒。

3.设置缓存过期时间

从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。所有的写操作以数据库为准,只要到达缓存过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。

4.该方案的弊端

结合双删策略+缓存超时设置,这样最差的情况就是在超时时间内数据存在不一致,而且又增加了写请求的耗时。

2、第二种方案:异步更新缓存(基于订阅binlog的同步机制)

1.技术整体思路:

MySQL binlog增量订阅消费+消息队列+增量数据更新到redis

1)读Redis:热数据基本都在Redis

2)写MySQL:增删改都是操作MySQL

3)更新Redis数据:MySQ的数据操作binlog,来更新到Redis

2.Redis更新

1)数据操作主要分为两大块:

一个是全量(将全部数据一次写入到redis)

一个是增量(实时更新)

这里说的是增量,指的是mysql的update、insert、delate变更数据。

2)读取binlog后分析 ,利用消息队列,推送更新各台的redis缓存数据。

这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。

其实这种机制,很类似MySQL的主从备份机制,因为MySQL的主备也是通过binlog来实现的数据一致性。

这里可以结合使用canal(阿里的一款开源框架),通过该框架可以对MySQL的binlog进行订阅,而canal正是模仿了mysql的slave数据库的备份请求,使得Redis的数据更新达到了相同的效果。

当然,这里的消息推送工具你也可以采用别的第三方:kafka、rabbitMQ等来实现推送更新Redis。

 

 

 

  • Kafka
  1. Kakfa怎么确保发送成功?

为了确定消息是发送成功,我们要判断消息发送的结果。但是要注意的是 Kafka 生产者(Producer) 使用 send 方法发送消息实际上是异步的操作,我们可以通过 get()方法获取调用结果,但是这样也让它变为了同步操作,示例代码如下:

SendResult<String, Object> sendResult = kafkaTemplate.send(topic, o).get();

if (sendResult.getRecordMetadata() != null) {

   logger.info("生产者成功发送消息到" + sendResult.getProducerRecord().topic() + "-> " + sendRe

            sult.getProducerRecord().value().toString());

}

但是一般不推荐这么做!可以采用为其添加回调函数的形式,示例代码如下:

ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(topic, o);

        future.addCallback(result -> logger.info("生产者成功发送消息到topic:{} partition:{}的消息", result.getRecordMetadata().topic(), result.getRecordMetadata().partition()),

                ex -> logger.error("生产者发送消失败,原因:{}", ex.getMessage()));

 

如果消息发送失败的话,我们检查失败的原因之后重新发送即可!

另外这里推荐为 Producer 的retries(重试次数)设置一个比较合理的值,一般是 3 ,但是为了保证消息不丢失的话一般会设置比较大一点。设置完成之后,当出现网络问题之后能够自动重试消息发送,避免消息丢失。另外,建议还要设置重试间隔,因为间隔太小的话重试的效果就不明显了,网络波动一次你3次一下子就重试完了。

  1. kakfa怎么避免消息不丢失?

消息丢失解决方案:

首先对kafka进行限速, 其次启用重试机制,重试间隔时间设置长一些,最后Kafka设置acks=all,即需要相应的所有处于ISR的分区都确认收到该消息后,才算发送成功

消息重复解决方案:

消息可以使用唯一id标识 

生产者(ack=all 代表至少成功发送一次) 

消费者 (offset手动提交,业务逻辑成功处理后,提交offset) 

落表(主键或者唯一索引的方式,避免重复数据) 

业务逻辑处理(选择唯一主键存储到Redis或者mongdb中,先查询是否存在,若存在则不处理;若不存在,先插入Redis或Mongdb,再进行业务逻辑处理)

  1. Kakfa怎么保证不重复消费?

幂等producer:保证发送单个分区的消息只会发送一次,不会出现重复消息(指定group-id);

事务(transaction):保证原子性地写入到多个分区,即写入到多个分区的消息要么全部成功,要么全部回滚流处理EOS:流处理本质上可看成是“读取-处理-写入”的管道。此EOS保证整个过程的操作是原子性。注意,这只适用于Kafka Streams

  1. 事务的四大特性

原子性、一致性、隔离性、持久性

原子性:事务是一个不可分割的操作单位,事务中的所有操作要么全部成功,要么全部失败。

一致性:一致性和原子性密切相关。

隔离性:并发执行多个不同的事务之间是互不干扰的。

持久性:事务一旦提交,对数据库操作的影响是具有持久性的。

  1. 什么情况下用到事务?

对数据库的数据进行批量或连表操作时,为了保证数据的一致性和正确性,我们需要添加事务管理机制进行管理。当对数据库的数据进行操作失败时,事务管理可以很好保证所有的数据回滚到原来的数据,如果操作成功,则保证所有需要更新的数据持久化。

  1. 并发下事务有什么问题(脏读、丢失修改、幻读、不可重复读)?

脏读(Dirty read)

当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。

丢失修改(Lost to modify)

指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。

不可重复读(Unrepeatableread)

指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。

幻读(Phantom read)

幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

不可重复度和幻读区别:

不可重复读的重点是修改,幻读的重点在于新增或者删除。

例1

(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导 致A再读自己的工资时工资变为 2000;这就是不可重复读。

例2

(同样的条件, 第1次和第2次读出来的记录数不一样 ):某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读。

  1. 分布式锁?

基于数据库实现分布式锁;

基于缓存(Redis等)实现分布式锁;

基于Zookeeper实现分布式锁;

  1. 基于数据库

基于数据库的实现方式的核心思想是:在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。

(1)创建一个表:

DROP TABLE IF EXISTS `method_lock`;CREATE TABLE `method_lock` (

  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',

  `method_name` varchar(64) NOT NULL COMMENT '锁定的方法名',

  `desc` varchar(255) NOT NULL COMMENT '备注信息',

  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

  PRIMARY KEY (`id`),

  UNIQUE KEY `uidx_method_name` (`method_name`) USING BTREE

) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';

(2)想要执行某个方法,就使用这个方法名向表中插入数据:

INSERT INTO method_lock (method_name, desc) VALUES ('methodName', '测试的methodName');

因为我们对method_name做了唯一性约束,这里如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,可以执行方法体内容。

(3)成功插入则获取锁,执行完成后删除对应的行数据释放锁:

delete from method_lock where method_name ='methodName';

  1. Redis实现分布式锁

原因:
(1)Redis有很高的性能;
(2)Redis命令对此支持较好,实现起来比较方便
使用命令介绍:
(1)SETNX
SETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。
(2)expire
expire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
(3)delete
delete key:删除key
在使用Redis实现分布式锁的时候,主要就会使用到以上这三个命令。
实现思想:
(1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
(3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。

  1. ZooKeeper实现分布式锁

ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。基于ZooKeeper实现分布式锁的步骤如下:
(1)创建一个目录mylock;
(2)线程A想获取锁就在mylock目录下创建临时顺序节点;
(3)获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;
(4)线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;
(5)线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。
这里推荐一个Apache的开源库Curator,它是一个ZooKeeper客户端,Curator提供的InterProcessMutex是分布式锁的实现,acquire方法用于获取锁,release方法用于释放锁。
优点:具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。
缺点:因为需要频繁的创建和删除节点,性能上不如Redis方式。

  1. 分布式系统数据一致性

经典方案 - eBay 模式解决方法,将主要修改操作以及更新用户表的消息放在一个本地事务来完成。同时为了避免重复消费用户表消息带来的问题,达到多次重试的幂等性,增加一个更新记录表 updates_applied 来记录已经处理过的消息。

其他解决方案如下

1 假设调用支付系统支付订单的时候先不扣钱,订单状态更新完成之后,在通知支付系统你扣钱

如果采用这种设计方案,那么在同一时刻,这个用户,又支付了另外一笔订单,订单价格200,顺利完成了整个订单支付流程,由于当前订单的状态已经变成了支付成功,但是实际用户已经没有钱支付了,这笔订单的状态就不一致了。即使用户在同一个时刻没有进行另外的订单支付行为,通知支付系统扣钱这个动作也有可能完不成,因为也有可能失败,反而增加了系统的复杂性。

2 订单系统自动发起重试,多重试几次,例如三次,直到扣款成功为止。

这个看起来也是不错的考虑,但是和解决方案一样,解决不了问题,还会带来新的问题,假设订单系统第一次调用支付系统成功,但是没有办法收到应答,订单系统又发起调用,完了,重复支付,一次订单支付了200。

假设支付系统正在发布,你重试多少次都一样,都会失败。这个时候用户在等待,你怎么处理?

3 在第二种方案的基础上,我们先解决订单的重复支付行为,我们需要在支付系统上对订单号进行控制,一笔订单如果已经支付成功,不能在进行支付。返回重复支付标识。那么订单系统根据返回的标识,更新订单状态。

接下来解决重试问题,我们假设应用上重试三次,如果三次都失败,先返回给用户提示支付结果未知。假设这个时候用户重新发起支付,订单系统调用支付系统,发现订单已经支付,那么继续下面的流程。如果会员没有发起支付,系统定时(一分钟一次)去核对订单状态,如果发现已经被支付,则继续后续的流程。

这种方案,用户体验非常差,告诉用户支付结果未知,用户一定会骂你,你丫咋回事情,我明明支付了,你告诉我未知。假设告诉用户支付失败,万一实际是成功的咋办。你告诉用户支付成功,万一支付失败咋办。

4 第三种方案能够解决订单和支付数据的一致性问题,但是用户体验非常差。当然这种情况比较可能是少数,可以牺牲这一部分的用户体验,我们还有没有更好的解决方案,既能照顾用户体验,又能够保证资金的安全性。

我们再回来看看第一种方案,我们先不扣钱,但是有木有办法让这一部分钱不让用户使用,对了,我们先把这一部分钱冻结起来,订单系统先调用支付系统成功的时候,支付系统先不扣钱,而是先把钱冻结起来,不让用户给其他订单支付,然后等订单系统把订单状态更新为支付成功的时候,再通知支付系统,你扣钱吧,这个时候支付系统扣钱,完成后续的操作。

看起来这个方案不错,我们仔细在分析一下流程,这个方案还存在什么问题,假设订单系统在调用支付系统冻结的时候,支付系统冻结成功,但是订单系统超时,这个时候返回给用户,告知用户支付失败,如果用户再次支付这笔订单,那么由于支付系统进行控制,告诉订单系统冻结成功,订单系统更新状态,然后通知支付系统,扣钱吧。如果这个时候通知失败,木有问题,反正钱都已经是冻结的了,用户不能用,我只要定时扫描订单和支付状态,进行扣钱而已。

那么如果变态的用户重新拍下来一笔订单,100块钱,对新的订单进行支付,这个时候由于先前那一笔订单的钱被冻结了,这个时候用户余额剩余100,冻结100,发现可用的余额足够,那就直接在对用户扣钱。这个时候余额剩余0,冻结100。先前那一笔怎么办,一个办法就是定时扫描,发现订单状态是初始的话,就对用户的支付余额进行解冻处理。这个时候用户的余额变成100,订单数据和支付数据又一致了。假设原先用户余额只有100,被冻结了,用户重新下单,支付的时候就失败了啊,的确会发生这一种情况,所以要尽可能的保证在第一次订单结果不明确的情况,尽早解冻用户余额,比如10秒之内。但是不管如何快速,总有数据不一致的时刻,这个是没有办法避免的。

 

  1. Spring两大特性:IoC和AOP?

IOC(Inverse of Contro)控制反转,有时候也被称为DI依赖注入,它是一种降低对象耦合关系的一种设计思想。具有依赖注入功能的容器,可以创建对象的容器。IoC容器负责实例化、定位、配置应用程序中的对象并建立这些对象之间的依赖。

AOP面向切面编程就是纵向的编程。主要一般应用于签名验签、参数校验、日志记录、事务控制、权限控制、性能统计、异常处理等。

 

  1. Spring:bean的生命周期什么是bean的循环依赖?怎么注入bean?

Spring中的Bean生命周期

1. 实例化一个Bean,也就是我们通常说的new

2. 按照Spring上下文对实例化的Bean进行配置,也就是IOC注入

3. 如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的是Spring配置文件中Bean的ID

4. 如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBeanFactory(),传递的是Spring工厂本身(可以用这个方法获取到其他Bean)

5. 如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文,该方式同样可以实现步骤4,但比4更好,以为ApplicationContext是BeanFactory的子接口,有更多的实现方法

6. 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在Bean初始化结束时调用After方法,也可用于内存或缓存技术

7. 如果这个Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法

8. 如果这个Bean关联了BeanPostProcessor接口,将会调用postAfterInitialization(Object obj, String s)方法

注意:以上工作完成以后就可以用这个Bean了,那这个Bean是一个single的,所以一般情况下我们调用同一个ID的Bean会是在内容地址相同的实例

9. 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean接口,会调用其实现的destroy方法

10. 最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法

bean循环依赖

其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。如下图:

 

注意,这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件。

Spring中循环依赖场景有: 
(1)构造器的循环依赖 
(2)field属性的循环依赖。

Spring中依赖注入有三种注入方式:

一、构造器注入;

二、设值注入(setter方式注入);设值注入就是通过setXxxx方法将bean注入到组件中

三、Feild方式注入(注解方式注入)。

 

  1. Bean实例化的三种方式?

1. 构造器实例化

spring容器通过bean对应的默认的构造函数来实例化bean。

2. 静态工厂方式实例化

首先创建一个静态工厂类,在类中定义一个静态方法创建实例。

 

静态工厂类及静态方法:

 

public class MyUserDaoFactory{

//静态方法,返回UserDaoImpl的实例对象

public static UserDaoImpl createUserDao{

return new UserDaoImpl();

}

}

xml配置文件

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

       "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

       <!-- 将指定对象配置给spring,让spring创建其实例 -->

       <bean id="userDao" class="com.ioc.MyUserDaoFactory" factory-method="createUserDao"/>

</beans>

3. 实例工厂方式实例化

该种方式的工厂类中,不再使用静态方法创建Bean实例,而是采用直接创建Bean实例的方式。同时在配置文件中,需要实例化的Bean也不是通过class属性直接指向其实例化的类,而是通过factory-bean属性配置一个实例工厂,然后使用factory-method属性确定使用工厂中哪个方法。

工厂类方法:

public class MyBeanFactory{

    public MyBeanFactory(){

        System.out.println("this is a bean factory");

    }

    public UserDaoImpl createUserDao(){

        return new UserDaoImpl();

    }

}

xml配置文件

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

       "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

   <!-- 配置工厂 -->

   <bean id="myBeanFactory" class="com.ioc.MyBeanFactory"/>

   <!-- 使用factory-bean属性配置一个实例工厂,使用factory-method属性确定工厂中的哪个方法 -->

   <bean id="userDao" factory-bean="myBeanFactory" factory-method="createUserDao"/>

</beans>

主函数

public class Client {

    public static void main(String[] args) {

        // TODO Auto-generated method stub

        //此处定义xml文件放置的位置为src目录下的com/xml目录下

        String path = "com/xml/bean.xml";

        ApplicationContext application = new ClassPathXmlApplicationContext(path);

        UserDaoImpl userDao = (UserDaoImpl) application.getBean("userDao");

        userDao.sayHello();   //调用UserDaoImpl类的sayHello方法

    }

}

  1. spring动态代理?

Spring动态代理的开发步骤:

  1. 开发原始类 :书写核心方法
  2. 开发额外功能 :implements MethodInterceptor
  3. 配置切入点 : 额外功能需要加那个方法中

<aop:config>

<aop:pointcut id=pc expression=切入点函数(切入点表达式)/>

</aop:config>

  1. 组装 : 切面 =切入点+额外功能

 

  1. Springmvc核心处理流程?

a、DispatcherServlet前端控制器接收发过来的请求,交给HandlerMapping处理器映射器

b、HandlerMapping处理器映射器,根据请求路径找到相应的HandlerAdapter处理器适配器(处理器 适配器就是那些拦截器或Controller)

c、HandlerAdapter处理器适配器,处理一些功能请求,返回一个ModelAndView对象(包括模型数据、 逻辑视图名)

d、ViewResolver视图解析器,先根据ModelAndView中设置的View解析具体视图

e、然后再将Model模型中的数据渲染到View上这些过程都是以DispatcherServlet为中轴线进行的。

  1. 虚拟机有几部分组成?     

                                                                                                                                            

 

1,JVM内存结构

分析JVM内存结构,主要就是分析JVM运行时数据存储区域

JVM的运行时数据区主要包括:堆、栈、方法区、程序计数器

JVM的优化问题主要在线程共享的数据区中:堆、方法区

2,各区简介:

2.1 方法区(共享)

线程共享的内存区域

用于存储被虚拟加载的类信息、常量、静态变量等

该区域也被称为:永久代(或者叫非堆内存区域)

2.2 堆(共享)

Java堆是jvm所管理的内存中最大的一部分,主要包含了java运行中所存放的对象

Java堆也是GC管理的主要区域,主流的算法都基于分代收集算法方式进行:

新生代

Eden Space

Survivor Space 存活区

老年代(Tenured Gen)

堆内存是共享的,所有的java线程共享的内存区域

堆中存放的是类实例化出来的对象

2.3. Java栈(线程私有)

每一个线程都有自己的栈,线程启动起来就自动给它创建一个栈,这个虚拟机栈中描述的是java方法执行的内存模型

每个方法被执行时,都会给它创建一个栈帧,用于存储线程自己的局部变量

Java栈内存为线程私有,存放线程自己的局部变量等信息

2.3.4 PC寄存器(Program Counter Register)(线程私有)

程序计数器,线程独占的内存空间

一般是非常小的内存空间

2.3.5 本地方法栈(线程私有)

Native Method Stacks

它不是通过虚拟机栈为虚拟机执行java方法,而是为虚拟机所使用到的本地方法提供服务

这里的本地方法对于Windows和Linux是不同的,它表明的是它真正能够在那个主机上所能执行的特有方法

所以具体的实施方案是依赖于平台的

2.3.6 GC 垃圾回收器

GC垃圾回收面向于堆内存空间

主要目的就是回收那些不用了的对象

 

  1. mybatis plus 的特点?

MyBatis:

优势:

1.SQL语句可以自由控制,更灵活,性能较高

2.SQL与代码分离,易于阅读和维护

3.提供XML标签,支持编写动态SQL语句

劣势:

1.简单CRUD操作还得写SQL语句

2.XML中有大量的SQL要维护

3.MyBatis自身功能很有限,但支持Plugin

 

MyBatis-Plus介绍:

是MyBatis的增强工具,原方法不改变

 

MyBatis-Plus特性:

1.无侵入、损耗小、强大的CRUD操作

2.支持Lambda形式调用、支持多种数据库

3.支持主键自动生成、支持ActiveRecord [ˈæktɪv ˈrekɔːd] 模式

4.支持自定义全局通用操作、支持关键词自动转义

5.内置代码生成器、内置分页插件、内置性能分析插件

6.内置全局拦截插件、内置Sql注入剥离器

注:Active Record是一种领域模型模式,特点是一个模型类对应关系型数据库中的一个表,而模型类的一个实例对应表中的一行记录。

  1. mybatis中一级缓存和二级缓存?

 

  • Redis
  1. redis的数据类型?
  2. 工作中Redis有哪些应用场景?
  3. Redis分布式锁?
  4. Redis 是单线程还是多线程?

Redis是单线程。

 

  1. mongdb数据量大时怎么优化?

数据定时清理;

  1.  spring cloud的常用组件?说说作用?

常用组件介绍

Eureka:注册中心

Feign:服务调用

Ribbon:负载均衡

Hystrix或Resilience4j:熔断器

Zuul、Gateway:网关

Spring Cloud Config:分布式配置

简单理解:

1、Eureka 服务发现
接入后,每个节点都是一个 Eureka Client,会将本节点对应的地址端口信息汇总注册到 Eureka Server,server 端缓存了所有的服务信息,供客户端调用。
调用服务族中的任何服务都是通过 Eureka Server 返回的信息确定。

2、Feign 远程调用
需要调用的其他服务接口时,通过 @FeignClient 注解动态代理生成对应的实现类,最终减少了调用代码的编写。

3、Ribbon 服务路由
步骤二中我们使用 @FeignClient 注解有一个name 属性,表示服务的唯一标识名,那么假如某一服务有多个节点,他是不知道该访问哪一个节点的。这个时候就需要一种路由机制,来确定最终为我们提供服务的节点。
路由策略默认是 轮询策略。可以定义responseTime weight这种方式。

4、Hystrix 服务熔断/降级
服务之间相互调用,由于增加了中间网络的存在,肯定会出现服务不可用或相应缓慢的情况。
假如一个服务有问题,调用方的所有线程都阻塞在该服务上,导致调用方的服务也挂了,这是不应该的。
所以,Hystrix 是处理这种情况的。它会划分出一个个的线程池,每一个服务一个线程池,加入问题线程池满了,不会影响到其他服务的线程池的情况。这就是隔离机制。
更进一步,服务一致有问题,也就没有必要去调用了,所以可以设定一个机制,比如出问题后,5分钟内的调用都会直接返回。这就是熔断的机制。
那么直接返回也不是很优雅,我应该要做点什么来应对服务恢复的情况,比方说记录下请求的参数,以及目的。等待服务恢复之后,可以把这一段的业务恢复到原来的情况。这种机制就是服务降级。

5、Zuul 微服务网关
一般微服务架构中都必然会设计一个网关在里面,像android、ios、pc前端、微信小程序、H5等等,不用去关心后端有几百个服务,就知道有一个网关,所有请求都往网关走,网关会根据请求中的一些特征,将请求转发给后端的各个服务。

而且有一个网关之后,还有很多好处,比如可以做统一的降级、限流、认证授权、安全,等等。

6、Spring Cloud Config:分布式配置

作用:配置管理

简介:SpringCloud Config提供服务器端和客户端。服务器存储后端的默认实现使用git,因此它轻松支持标签版本的配置环境,以及可以访问用于管理内容的各种工具。

这个还是静态的,得配合Spring Cloud Bus实现动态的配置更新。

总结:

最后再来总结一下,上述几个Spring Cloud核心组件,在微服务架构中,分别扮演的角色:

Eureka:各个服务启动时,Eureka Client都会将服务注册到Eureka Server,并且Eureka Client还可以反过来从Eureka Server拉取注册表,从而知道其他服务在哪里

Ribbon:服务间发起请求的时候,基于Ribbon做负载均衡,从一个服务的多台机器中选择一台

Feign:基于Feign的动态代理机制,根据注解和选择的机器,拼接请求URL地址,发起请求

Hystrix:发起请求是通过Hystrix的线程池来走的,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题

Zuul:如果前端、移动端要调用后端系统,统一从Zuul网关进入,由Zuul网关转发请求给对应的服务

Spring Cloud Config:配置管理

 

  • 数据库

1.sql优化?

1.执行From 字句是从右往左进行执行。因此必须选择记录条数最少的表放在右边。

    a表10条   b表100    a在右边 通过10条数据过滤掉不符合的数据

2.如果在where 子句中有OR 操作符或单独引用Job 列(索引列的后面列) 则将不会走索引,将会进行全表扫描。索引遵守最左原则。

3.对于Where字句其执行顺序是从后向前执行、因此可以过滤最大数量记录的条件必须写在Where子句的末尾,而对于多表之间的连接,则写在之前。

因为这样进行连接时,可以去掉大多不重复的项。

4.大量(或多用户)操作(增删改)同一数据时,可能发生行级死锁(锁住本行记录,直到commit或者连接超时后释放锁)。

    避免这种问题,设置事物,在事物内 不要处理异常(处理了事物就找不到异常会往下走,就可能造成行级死锁) ,如果非要处理异常,可放在事物外(提取出入库逻辑为一个方法,加事物)。

5. 分析表analyze table,如当有大量删除后执行。

    --分析删除表    

    analyze table t compute statistics;

    delete删除表,数据删了但是表资源还存在,依旧耗资源,分析后就会初始化表。

6. 删除表

    ①、drop table X 将表移到回收站(recyclebin)中 ;若删错后可以恢复

    ②、回收站中的数据可以通过 purge删除,  若drop table X purge 删除某个表,该表不会出现在回收站中;若删除不可恢复

7.删除数据

     ①、delete:dml语句,用于删除数据,可以回滚(反悔),可以精确删除。该语句非常耗费资源,需要写undo回滚段,占用大量内存。delete语句是所有dml语句中最消耗资源的语句。

    ②、truncat:DDL语句,清除数据。该操作不可回滚,不可精确删除。一旦操作,那么不可找回。节省资源,但是该命令每次清除的基本单位为“段”,比如:table,partition,subpartition等,不能选择清除某些数据。

TRUNCATE  TABLE t_user;

 

2.数据库连接池

通过使用连接池,将大大提高程序运行效率,同时,我们可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。

1)  最小连接数是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费; 
2)  最大连接数是连接池能申请的最大连接数,如果数据库连接请求超过此数,后面的数据库连接请求将被加入到等待队列中,这会影响之后的数据库操作。

 

  1. Mybatis是怎么防止sql注入

#方式能够很大程度防止sql注入。

注:一般传参用$,涉及表结构的用#。

  1. 什么情况下sql索引会失效?

a.如果条件中有or,即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因,or 会引起全表扫描)
 注意:要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引

b.对于多列索引,不是使用的第一部分,则不会使用索引(最左原则)。

c.like查询是以%开头。

注:like查询已%结尾可以用索引。

e.如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引。

f.如果mysql估计使用全表扫描要比使用索引快,则不使用索引。

j.索引不要和函数一起使用(mysql不支持函数索引,Oracle支持)。

 

此外,查看索引的使用情况
show status like ‘Handler_read%’;
大家可以注意:
handler_read_key:这个值越高越好,越高表示使用索引查询到的次数
handler_read_rnd_next:这个值越高,说明查询低效

 

  1. 主键和唯一索引的区别

主键唯一索引都要求值唯一,但是它们还是有区别的:
①.主键是一种约束,唯一索引是一种索引;
②.一张表只能有一个主键,但可以创建多个唯一索引
③.主键创建后一定包含一个唯一索引,唯一索引并一定是主键;
④.主键不能为null,唯一索引可以为null;
⑤.主键可以做为外键,唯一索引不行;

 

  1. 事务隔离的概念?
  • ISOLATION  [aɪsə'leɪʃ(ə)n]
      a. ISOLATION_DEFAULT(默认事务): 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.
          另外四个与JDBC的隔离级别相对应
     b. Read uncommitted(读未提交): 这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。该隔离级别可以通过“排他写锁”,但是不排斥读线程实现。避免了更新丢失,但会产生脏读,不可重复读和幻像读。
     c. Read committed(读提交): 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。避免了脏读,但依旧有不可重复读和幻读
      d.Repeatable read(可重复读取): 确保事务可以多次从一个字段中读取相同的值,在此事务持续期间,禁止其他事务对此字段的更新,可以避免脏读和不可重复读,仍会出现幻读问题。
      e. Serializable(序列化): 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,可避免所有并发问题(防止脏读,不可重复读外,还避免了幻像读),但性能非常低。

 

  1. canal实现mysql实时数据binlog同步

原理相对比较简单(Alibaba开源中间件Canal):

 

canal模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议

mysql master收到dump请求,开始推送binary log给slave(也就是canal)

canal解析binary log对象(原始为byte流)

  1. 了解索引数据结构、存储引擎?

引数据结构:

Oracle存储索引的数据结构是B*树,位图索引也是如此,只不过是叶子节点不同B*数索引;索引由根节点、分支节点和叶子节点组成,上级索引块包含下级索引块的索引数据,叶节点包含索引数据和确定行实际位置的rowid。

mysql索引数据结构B-Tree和 B+Tree
B-Tree不是“B减树”,而是“B树”。

存储引擎:

MYSQL常用 两种 MyISAM与InnoDB
前者 都可以进行一般的数据存储 但各有优势
简单不全面的说 前者 适合 少改写 少插入 的 读取频繁的表
后者 使用于 频繁维护的 update insert等 数据表
这俩 锁定方式也不同 锁表方式

  1. 数据库的乐观锁和悲观锁

乐观所,不会锁表,会根据记录的version进行是否更新,效率高。

悲观所,直接锁表,效率低。

  1. 悲观锁

正如其名,它指的是对数据被外界(包括本系统(System)当前的其他事务,以及来自外部系统(System)的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也仅有数据库层提供的锁机制才可以真正保证数据访问的排他性,否则,即使在本系统(System)中实现了加锁机制,也没方法保证外部系统不会修改数据)。

b、乐观锁( Optimistic Locking )

相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情形下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往没方法承受。

而乐观锁机制在一定程度上处理了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加1个版本标识,在基于数据库表的版本处理方案中,一般是通过为数据库表增加1个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,假如提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

 

 

持续学习中......

如有错误,欢迎指正!

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值