1. 面向对象
什么是面向对象
-
面向对象与面向过程
始终围绕如何解决具体问题进行。面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;
以更形象的方式在计算机中构建现实的事物,更符合人认识世界的习惯。面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
-
面向对象的三大基本特征和五大基本原则
特征:封装,继承,多态
原则:单一职责原则(SRP) 一个类应该有且只有一个去改变它的理由,这意味着一个类应该只有一项工作。
开放封闭原则(OCP) 对象或实体应该对扩展开放,对修改封闭。例如私有方法允许重载,但不允许重写。
里氏替换原则(LSP) 在对象 x 为类型 T 时 q(x) 成立,那么当 S 是 T 的子类时,对象 y 为类型 S 时 q(y) 也应成立。(即对父类的调用同样适用于子类)
依赖倒置原则(DIP) 高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。具体实现应该依赖于抽象,而不是依赖于实现。依赖于实现会使得日后扩展不断。例如A依赖于B、C、D的实现不如依赖于BCD共同实现的接口或抽象类。
接口隔离原则(ISP) 不应强迫客户端实现一个它用不上的接口,或是说客户端不应该被迫依赖它们不使用的方法,使用多个专门的接口比使用单个接口要好的多!
平台无关性
-
Java 如何实现的平台无关
java的运行依赖于java运行环境(JRE),Java虽然平台无关但JRE是平台相关的,虽然它支持大部分主流平台。
-
JVM 还支持哪些语言
(Kotlin、Groovy、JRuby、Jython、Scala)
值传递
-
值传递、引用传递
1.基本类型作为参数传递时,是传递值的拷贝,无论你怎么改变这个拷贝,原值是不会改变的
2.对象作为参数传递时,是把对象在内存中的地址拷贝了一份传给了参数。
3.C++中还有指针传递。
-
为什么说Java中只有值传递?
前述的引用传递,形参拿到的是实参地址的复制,相当于是地址值的传递。
Java中的引用传递相当于c++中的指针传递。至于C++中的引用传递解释如下:引用参数传递过程中,被调函数的形式参数也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参(本体)的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量(根据别名找到主调函数中的本体)。因此,被调函数对形参的任何操作都会影响主调函数中的实参变量。
2. java基础知识
浮点数精度
浮点数在机器中的表示由三部分组成:符号,指数,尾数。所以其最大精度由尾数位数来决定。
包装类型
包装类型用于经基本类型转换为一个对象,包装类于基本类型之间的自动转换称为自动拆装箱。
Integer的缓存机制
在 Java 5 中,为 Integer 的操作引入了一个新的特性,用来节省内存和提高性能。整型对象在内部实现中通过使用相同的对象引用实现了缓存和重用。上面的规则适用于整数区间 -128 到 +127。这种 Integer 缓存策略仅在自动装箱(autoboxing)的时候有用,使用构造器创建的 Integer 对象不能被缓存。Java 编译器把原始类型自动转换为封装类的过程称为自动装箱(autoboxing),这相当于调用 valueOf 方法。这种缓存行为不仅适用于Integer对象。我们针对所有整数类型的类都有类似的缓存机制。
有 ByteCache 用于缓存 Byte 对象
有 ShortCache 用于缓存 Short 对象
有 LongCache 用于缓存 Long 对象
有 CharacterCache 用于缓存 Character 对象
Byte,Short,Long 有固定范围: -128 到 127。对于 Character, 范围是 0 到 127。除了 Integer 可以通过参数改变范围外,其它的都不行。
String
-
JDK 6 和 JDK 7 中 substring 的原理及区别
String是通过字符数组实现的。在jdk 6 中,String类包含三个成员变量:char value[], int offset,int count。他们分别用来存储真正的字符数组,数组的第一个位置索引以及字符串中包含的字符个数。
当调用substring方法的时候,会创建一个新的string对象,但是这个string的值仍然指向堆中的同一个字符数组。这两个对象中只有count和offset 的值是不同的。
这样做乍看起来可以减少内存占用,可是substring的存在会使得整个字符串在无用后得不到回收。所以在JDK7中substring实现方式变为开辟新的内存存储子字符串。
-
字符串拼接的方式
加号“+”
String contact() 方法
StringUtils.join() 方法
StringBuffer append() 方法
StringBuilder append() 方法
-
stringbuild和stringbuffer的区别
在执行速度方面的比较:StringBuilder > StringBuffer
StringBuffer与StringBuilder,他们是字符串变量,是可改变的对象,每当我们用它们对字符串做操作时,实际上是在一个对象上操作的,不像String一样创建一些对象进行操作,所以速度就快了。
StringBuilder:线程非安全的 StringBuffer:线程安全的
-
String.valueOf 和 Integer.toString 的区别
String.valueOf()对不同数据类型实现了重载,对于int类型的参数直接调用Integer.toString()
-
字符串池、常量池(运行时常量池、Class 常量池)、intern
String有两种赋值方式,第一种是通过“字面量”赋值。
String str = "Hello";
第二种是通过new关键字创建新对象。
String str = new String("Hello");class常量池:我们写的每一个Java类被编译后,就会形成一份class文件;class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool tle),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References);
运行时常量池:运行时常量池存在于内存中,也就是class常量池被加载到内存之后的版本,不同之处是:它的字面量可以动态的添加(String.intern()),符号引用可以被解析为直接引用.
字符串池为他们的一部分。字面量创建字符串会先在字符串池中找,看是否有相等的对象,没有的话就在堆中创建,把地址驻留在字符串池;有的话则直接用池中的引用,避免重复创建对象。
new关键字创建时,前面的操作和字面量创建一样,只不过最后在运行时会创建一个新对象,变量所引用的都是这个新对象的地址。
由于不同版本的JDK内存会有些变化,JDK1.6字符串常量池在永久代(即方法区),1.7移到了堆中(与运行时常量池分开),1.8用元空间代替了永久代。但是基本对上面的结论没有影响,思想是一样的。
各种关键字
-
transient、instanceof、final、static、volatile、synchronized、const 原理及用法
transient 表示无需序列化的变量
final可以用来修饰类(继承),方法(重写)和变量(const)(成员变量或局部变量)
finally作为异常处理的一部分,它只能用在try/catch语句中
finalize()是在java.lang.Object里定义的,也就是说每一个对象都有这么个方法。这个方法在gc启动,该对象被回收的时候被调用
集合类
-
ArrayList 和 LinkedList 和 Vector 的区别
ArrayList和Vector都是基于数组实现的,但Vector实现了线程安全所以他效率会低,linkedList是基于双向列表实现的。还有synchronizedList,是实现了线程安全的ArrayList,所以与Vector的区别仅仅是增长速度。
-
HashMap、HashTable、ConcurrentHashMap 区别
Hashtable和HashMap有几个主要的不同:线程安全以及速度。仅在你需要完全的线程安全的时候使用Hashtable,而如果你使用Java 5或以上的话,请使用ConcurrentHashMap吧。
-
HashSet是如何保证元素唯一性的呢?
是通过元素的两个方法,hashCode和equals来完成。
如果元素的HashCode值相同,才会判断equals是否为true。
如果元素的hashcode值不同,不会调用equals。
-
Java 8 中 stream 相关用法
Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。可以并行化处理。
常用操作有 filter map distinct 排序 reduction collect
-
apache 集合处理工具类的使用
并交补、过滤、collect(获取某些属性的集合)
-
不同版本的 JDK 中 HashMap 的实现的区别以及原因
JDK1.8之前处理hash冲突使用链表,但是链表查询满,链表长了之后效率会下降,故JDK1.8之后链表长度达到一定阈值后转换为红黑树,红黑树增删慢,查询快。
-
Collection与Collections
java.util.Collection 是一个集合接口(集合类的一个顶级接口)
Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。
-
Arrays.asList
是不支持add和remove操作的,也就是说Arrays.asList返回的List是个固定大小的List。如果希望转过后的list可以支持add和remove操作,可使用如下方法:
ArrayList<Integer> copyArrays=new ArrayList<>(Arrays.asList(integerArray));
-
Enumeration 和 Iterator 区别
Iterator除了能读取集合的数据之外,也能数据进行删除操作。
Iterator支持fail-fast机制,而Enumeration不支持。
-
fail-fast 和 fail-safe
在使用迭代器遍历集合时,若集合内容发生改变,而我们接着对该集合遍历,此事是否应该抛出异常?快速失败就是即刻抛出Concurrent Modification Exception。而安全失败的遍历是在遍历之前将集合内容复制出来,在其上进行遍历。
-
CopyOnWrite容器
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器
-
ConcurrentSkipListMap
TreeMap使用红黑树按照key的顺序(自然顺序、自定义顺序)来使得键值对有序存储,但是只能在单线程下安全使用;多线程下想要使键值对按照key的顺序来存储,则需要使用ConcurrentSkipListMap。 ConcurrentSkipListMap的底层是通过跳表来实现的。
-
同步、异步、阻塞、非阻塞
同步异步是指进程通信方式,阻塞非阻塞是指同步之下进程能否继续执行其他任务。
linux下的五种I/O模型
1)阻塞I/O(blocking I/O)
2)非阻塞I/O(nonblocking I/O)
3)I/O复用(select 和poll) (I/O multiplexing)
I/O复用模型会用到select、poll、epoll函数,这几个函数也会使进程阻塞,但是和阻塞I/O所不同的的,这两个函数可以同时阻塞多个I/O操作
4)信号驱动I/O (signal driven I/O (SIGIO))
在信号处理函数中调用I/O操作函数处理数据,进程继续运行并不阻塞
5)异步I/O (asynchronous I/O (the POSIX aio_functions))
实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者的输入输出操作
前者与后者的区别在于启用异步I/O意味着通知内核启动某个I/O操作,并让内核在整个操作(包括数据从内核复制到用户缓冲区)完成时通知我们。也就是说,异步I/O是由内核通知我们I/O操作何时完成,即实际的I/O操作也是异步的;而 信号驱动I/O是由内核通知我们何时可以启动一个I/O
-
select、epoll
select的几大缺点及epoll解决方式:
1.每次循环调用select,都需要把fd集合从用户态拷贝到内核态,返回时从内核态拷贝到用户态,这个开销在fd很多时会很大(epoll的解决方案-在epoll_ctl函数中。每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在循环调用epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。)
2.同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大(epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果,和select实现中的第7步是类似的))
3.select支持的文件描述符数量太小了,默认是1024(fd_set只包含一个int数组,数组大小为1024)
-
BIO、NIO、AIO
BIO
NIO的最重要的地方是当一个连接创建后,不需要对应一个线程,这个连接会被注册到多路复用器上面,所以所有的连接只需要一个线程就可以搞定,当这个线程中的多路复用器进行轮询的时候,发现连接上有请求的话,才开启一个线程进行处理,也就是一个请求一个线程模式。也就是说,这个时候,已经不是一个连接就要对应一个处理线程了,而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的。netty基于此
AIO与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。 即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。
反射
-
反射与工厂模式,IOC
将反射与工厂模式结合,可以通过类名字(查找类文件)便能生成对象。可以动态加入类文件。
class Factory{ public static fruit getInstance(String ClassName){ fruit f=null; try{ f=(fruit)Class.forName(ClassName).newInstance(); }catch (Exception e) { e.printStackTrace(); } return f; } }
我们可以把IOC(控制反转)容器的工作模式看做是工厂模式的升华,可以把IOC容器看作是一个工厂,这个工厂里要生产的对象都在配置文件中给出定义,然后利用编程语言提供的反射机制,根据配置文件中给出的类名生成相应的对象。从实现来看,IOC是把以前在工厂方法里写死的对象生成代码,改变为由配置文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性
代理
静态代理
public void execute() { System.out.println("前拦截..."); bussinessImpl.execute(); System.out.println("后拦截..."); }
动态代理,无需手动实现每个接口,只需添加对方法的判断
public Object getProxyInstance(){ return Proxy.newProxyInstance( targetObject.getClass().getClassLoader(), //和目标对象的类加载器保持一致 targetObject.getClass().getInterfaces(), //目标对象实现的接口,因为需要根据接口动态生成对象 new InvocationHandler() { //InvocationHandler:事件处理器,即对目标对象方法的执行 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("前拦截..."); Object result = method.invoke(proxy, args); System.out.println("后拦截..."); return result; } });
动态代理类并不是程序员写的,而是根据传入的参数,由Proxy类在运行时生成的。
有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口?。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势
序列化
-
Java序列化底层原理
-
protobuf
相对于XML,protocol buffers在序列化结构数据时拥有许多先进的特性:
1、更简单
2、序列化后字节占用空间比XML少3-10倍
3、序列化的时间效率比XML快20-100倍
4、具有更少的歧义性
5、自动生成数据访问类方便应用程序的使用
JMS
-
什么是JMS
-
Kafka
泛型
泛型的目的简单地说就是可以让一些运行时才能发现的错误可以在编译期间就可以被编译器所检测出,运行时出问题的代价与编译期出现问题的代价的差别可想而知。换句话说,泛型是编译器的一种及时发现错误的机制,同时也给用户带来了代码的清晰与简洁的附加好处
-
泛型与继承
public class Solution<T> extends HashMap<Integer, T> { void push(Entry<Integer, T> x){ super.put(x.getKey(),x.getValue()); } public static void main(String[] args){ Solution<Integer> b2 = new Solution<>(); Entry<Integer, Integer> x = new SimpleEntry<>(1,2); b2.push(x); } }
-
类型擦除
Java 的泛型在编译器有效,在运行期被删除,也就是说所有泛型参数类型在编译后都会被清除掉
List<String>、List<T>擦除后的类型为 List。
List<String>[]、List<T>[] 擦除后的类型为 List[]。
List<? extends E>、List<? super E> 擦除后的类型为 List<E>。
List<T extends Serialzable & Cloneable> 擦除后类型为List<Serializable>。
-
限定通配符和非限定通配符、上下界限定符 extends 和 super
-
List<Object>、原始类型 List、list<?>
list<Object>表示列表中可以存放任意类型的元素,List<?>表示该列表中的元素类型可以是任一相同类型,即他是List<E>的父类,为了保证类型安全,不允许对List<?>或List<? extends E>这样的通配符类型进行类似add的操作。
相对于List,List<Object>可以帮助编译器在编译阶段发现错误。
测试
-
mock、mockito
API、SPI
API (Application Programming Interface)
大多数情况下,都是实现方来制定接口并完成对接口的不同实现,调用方仅仅依赖却无权选择不同实现。
SPI (Service Provider Interface)
而如果是调用方来制定接口,实现方来针对接口来实现不同的实现。调用方来选择自己需要的实现方。
语法糖
语法糖:switch 支持 String 与枚举、泛型、自动装箱与拆箱、方法变长参数、枚举、内部类、条件编译、 断言、数值字面量、for-each、try-with-resource、Lambda 表达式
并发编程
-
创建线程的几种方式
一般有四种方法,Thread,Runnable,Callable,使用Executor框架来创建线程池.
Runnable和Callable的区别是,
(1)Callable规定的方法是call(),Runnable规定的方法是run().
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
(3)call方法可以抛出异常,run方法不可以
(4)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
一般来说,CachedTheadPool在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,因此它是合理的Executor的首选,只有当这种方式会引发问题时(比如需要大量长时间面向连接的线程时),才需要考虑用FixedThreadPool
-
守护线程
Java的线程分为两种:User Thread(用户线程)、DaemonThread(守护线程)。
只要当前JVM实例中尚存任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束是,守护线程随着JVM一同结束工作,Daemon作用是为其他线程提供便利服务,守护线程最典型的应用就是GC(垃圾回收器),他就是一个很称职的守护者。
User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。
public class DaemonThreadTest
{
public static void main(String[] args)
{
Thread mainThread = new Thread(new Runnable(){
@Override
public void run()
{
Thread childThread = new Thread(new ClildThread());
childThread.setDaemon(true);
childThread.start();
System.out.println("I'm main thread...");
}
});
mainThread.start();
Thread otherThread = new Thread(new Runnable(){
@Override
public void run()
{
while(true)
{
System.out.println("I'm other user thread...");
try
{
TimeUnit.MILLISECONDS.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
});
otherThread.start();
}
}
class ClildThread implements Runnable
{
@Override
public void run()
{
while(true)
{
System.out.println("I'm child thread..");
try
{
TimeUnit.MILLISECONDS.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
还有补充一点,不是说当子线程是守护线程,主线程结束,子线程就跟着结束,这里的前提条件是:当前jvm应用实例中没有用户线程继续执行,如果有其他用户线程继续执行,那么后台线程不会中断,如下:
public class DaemonThreadTest
{
public static void main(String[] args)
{
Thread mainThread = new Thread(new Runnable(){
@Override
public void run()
{
Thread childThread = new Thread(new ClildThread());
childThread.setDaemon(true);
childThread.start();
System.out.println("I'm main thread...");
}
});
mainThread.start();
Thread otherThread = new Thread(new Runnable(){
@Override
public void run()
{
while(true)
{
System.out.println("I'm other user thread...");
try
{
TimeUnit.MILLISECONDS.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
});
otherThread.start();
}
}
class ClildThread implements Runnable
{
@Override
public void run()
{
while(true)
{
System.out.println("I'm child thread..");
try
{
TimeUnit.MILLISECONDS.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
-
线程池不允许使用 Executors 去创建,而是通过ThreadPoolExecutor 的方式
-
线程安全与内存模型
-
死锁出现的条件
- 互斥条件:一个资源每次只能被一个线程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不可剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
-
银行家算法
框架
hibernate
-
懒加载
实现懒加载的前提:
1.实体类不能是final的
2.能实现懒加载的对象都是被CGLIB(反射调用)改写的代理对象,所以不能是final修饰的
3.须要asm,cglib两个jar包
4.相应的lazy属性为true
5.相应的fetch属性为select
下面几种可以实现懒加载功能:
1.通过Session.load()实现懒加载
2.one-to-one,many-to-one,one-to-many。
因为懒加载的存在,在session关闭之后,hibernate又向数据库发出一次请求,结果就抛出异常.解决这个问题的四种方式:
1.Hibernate.initialize(Department.class);
2.修改对象关系文件,将lazy改写lazy=false,即关闭懒加载
3.使用过滤器(web项目)
4.在SSH框架中,使用spring提供的openSessionView
mysql
-
四种事务隔离级别
脏读:事务中读到其他事务修改中间的数据,若其他事务回滚了,那就读到了脏数据
不可重复读:事务多次重复读取数据,数据中途却被其他事务修改
幻读:事务A首先根据条件索引得到N条数据,然后事务B改变了这N条数据之外的M条或者增添了M条符合事务A搜索条件的数据,导致事务A再次搜索发现有N+M条数据了,就产生了幻读
-
引擎
innodb
支持“ACID”事务
锁的粒度小,支持行锁定(只在可以确定主键时),所以适合经常更新的表,适合处理多重并发的更新请求
支持外键约束
不支持fulltext索引
必须导出SQL来备份
myisam
大批量的插入语句时(这里是INSERT语句)执行的比较的快
极度强调快速读取操作。
如果表的读操作远远多于写操作且不需要数据库事务的支持,那么MyIASM也是很好的选择。
允许没有主键和索引的表
MyISAM的数据是以文件的形式存储,所以在跨平台的数据转移中会很方便
一个强调的是性能,一个强调的是大容量数据库的事务安全
-
索引数据结构
B-与B+的区别
B-每个非叶子结点由n-1个key和n个指针组成,其中d<=n<=2d;B+每个结点的指针上限为2d
B+内结点不存储data,只存储key;叶子结点不存储指针。
-
为何使用B-/+来实现索引
可以将一个节点的大小设为一个页面,每个节点一次I/O便可读入内存,便于磁盘I/O
每次查找,最大的节点访问数为h(树高),而h的大小与非叶节点的出度d有关,d增大,便可以减少磁盘I/O
另外,在B+Tree的每个叶子结点增加一个指向相邻叶子结点的指针,就形成了带有顺序访问指针的B+Tree。做这个优化的目的是为了提高区间访问的性能,例如图4中如果要查询key为从18到49的所有数据记录,当找到18后,只需顺着结点和指针顺序遍历就可以一次性访问到所有数据结点,极大提到了区间查询效率。
-
innodb与myisam索引区别
myisam节点data域存放的是数据的地址,innodb主键索引中节点data域存放的直接是数据,辅助索引data域存放主键,然后根据主键再进行一次主键索引找到非主键列。
-
覆盖索引
前面说到innodb使用非主键索引时需要进行两次索引,第二次索引叫回表,当所查询数据为主键时是不需要回表的,如下1
1.select id from user_table where username = 'lzs' 2.select password from user_table where username = 'lzs'
面对2这样的查询为了加快查询速度。可以建立联合索引,也叫覆盖索引
-
最左前缀索引
继续以上面的例子来说明,为了提高语句B的执行速度,我们添加了一个联合索引(username,password),特别注意这个联合索引的顺序,如果我们颠倒下顺序改成(password,username),这样查询能使用这个索引吗?答案是不能的!这是最左前缀的第一层含义:联合索引的多个字段中,只有当查询条件为联合索引的一个字段时,查询才能使用该索引。
最左前缀的第二层含义:索引可以用于查询条件字段为索引字段,根据字段值最左若干个字符进行的模糊查询。1.where username like '张%' 2.where username like '%张%'
-
列中存在重复数据时,索引是什么结构的?
聚合索引要求非空唯一,如果没有满足字段则会自建一列用作聚合索引,非聚合索引(普通索引)叶节点指向聚合索引的键,不存在索引重复数据问题
值重复率高的字段不适合建索引,从性别字段不适合建索引说起
-
count(*)与索引
当利用主键索引(聚集索引)来进行统计效率一般会小于利用二级索引,这是因为count(*)主要的操作是在B+索引树的叶节点上进行扫描,页节点越小所需的磁盘IO便越少,而聚集索引需要扫描整个数据文件。
mysql中的锁
-
行级锁
mysql中的行级锁不是在表上加锁,而是在索引上面加锁,所以只有使用了索引的操作才有可能加行锁,另外行级锁有几率出现死锁。
[mysql行级锁与表所锁]
(https://www.cnblogs.com/guanghe/p/9217421.html)
-
间隙锁
可以用来防止幻读
innodb在RR级别快照读模式下使用MVCC解决幻读,在当前读(insert、update都属于当前读)模式下使用next-key lock。在唯一索引上(如主键索引),只需加record lock(记录锁)即可,在非唯一索引上需要加next-key锁(record lock+gap lock),如班级=12,光加行锁还是会使前后两次select * from student where 班级=12结果出现幻读