Java面试题总结(持续更新中······)
一. Java相关
1.1 多线程相关
1、谈谈volatile、synchronized关键字
-
synchronized 可保证原子性、有序性、可见性
-
volatile
只保证可见性(多线程下对变量的修改是可见的)、有序性(禁止进行指令重排序) -
volatile 的底层实现原理是内存屏障(内存栅栏),Memory Barrier(Memory Fence),内存屏障会提供3个功能:
- 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成
- 它会强制将对缓存的修改操作立即写入主存
- 如果是写操作,它会导致其他CPU中对应的缓存行无效
-
volatile
修饰之后的变量会加入读写屏障-
写屏障(sfence):保证在该屏障之前的,对共享变量的改动,都同步到主存当中
-
读屏障(lfence):保证在该屏障之后的, 对共享变量的读取,加载的是主存中的最新数据
-
对 volatile 变量的写指令后会加入写屏障
-
对 volatile 变量的读指令前会加入读屏障
-
1.2 JUC相关
1.3JVM相关
1. 说说你对垃圾回收原理和你的理解?
垃圾回收阶段
- 垃圾回收大致主要分为两个阶段:第一阶段为判断哪些对象符合回收的条件;第二阶段是对这些符合为垃圾的对象进行回收。
1.1、如何判断对象可以回收
引用计数法
- 记录每个对象的被引用次数,若大于0则不能回收
- 弊端:循环引用时,两个对象的计数都为1,导致两个对象都无法被释放
可达性分析算法
- JVM中的垃圾回收器通过可达性分析来探索所有存活的对象
- 扫描堆中的对象,看能否沿着GC Root对象为起点的引用链找到该对象,如果找不到,则表示可以回收
- 可以作为GC Root的对象
- 虚拟机栈(栈帧中的本地变量表)中引用的对象。
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(即一般说的Native方法)引用的对象
五种引用
强引用
只有GC Root都不引用该对象时,才会回收强引用对象
- 如上图B、C对象都不引用A1对象时,A1对象才会被回收
软引用
当GC Root指向软引用对象时,在内存不足时,会回收软引用所引用的对象
- 如上图如果B对象不再引用A2对象且内存不足时,软引用所引用的A2对象就会被回收
强引用,软引用,弱引用,幻象引用有什么区别?
https://cloud.tencent.com/developer/article/1632634
1.2、垃圾回收算法
-
标记-清除
定义:标记清除算法顾名思义,是指在虚拟机执行垃圾回收的过程中,先采用标记算法确定可回收对象,然后垃圾收集器根据标识清除相应的内容,给堆内存腾出相应的空间
- 这里的腾出内存空间并不是将内存空间的字节清0,而是记录下这段内存的起始结束地址,下次分配内存的时候,会直接覆盖这段内存
缺点:容易产生大量的内存碎片,可能无法满足大对象的内存分配,一旦导致无法分配对象,那就会导致jvm启动gc,一旦启动gc,我们的应用程序就会暂停,这就导致应用的响应速度变慢
-
标记-整理
标记-整理 会将不被GC Root引用的对象回收,清楚其占用的内存空间。然后整理剩余的对象,可以有效避免因内存碎片而导致的问题,但是因为整体需要消耗一定的时间,所以效率较低
-
复制
-
定义:将内存分为等大小的两个区域,FROM和TO(TO中为空)。先将被GC Root引用的对象从FROM放入TO中,再回收不被GC Root引用的对象。然后交换FROM和TO。
-
优缺点
- 可以避免内存碎片的问题,但是会占用双倍的内存空间
-
1.3、分代回收
现在的商业虚拟机采用分代收集算法,它根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。
一般将堆分为新生代和老年代。
- 新生代使用: 复制算法
- 老年代使用: 标记 - 清除 或者 标记 - 整理 算法
1.4、垃圾回收器
cms、g1
2、你了解的常用JVM参数有哪些?
jvm配置参数分为3大类:
1、堆分配参数:分配堆内存
堆参数 | 作用 |
---|---|
-Xms | 最小堆 |
-Xmx | 最大堆 |
-Xmn | 新生代内存 |
-XX:NewRatio | 新生代和老年代比值 |
-XX:SurvivorRatio | 新生代中Survivor区与Eden区的比值 |
2、栈分配参数:分配栈内存
栈参数 | 作用 |
---|---|
-Xss | 栈大小 |
3、跟踪参数:跟踪、监控JVM状态,用于程序员JVM调优及故障排查
跟踪监控参数 | 作用 |
---|---|
-XX:+PrintGC | 打印gc简要信息 |
-verbose:gc | 打印gc简要信息 |
-XX:+PrintGCDetails | 打印gc详细信息及堆使用详细信息 |
-Xloggc:log/gc.log | 将gc日志记录到外部文件中去 |
实例:java -jar配置参数示例
# 堆最小200M,堆最大200M,栈内存512K,打印GC详细信息
java -Xms200m -Xmx200m -Xss512K -XX:+PrintGCDetails -jar demo-0.0.1-SNAPSHOT.jar
3、什么是OOM异常?如何解决?
什么是OOM?
- JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error注:非exception,因为这个问题已经严重到不足以被应用处理)。
OOM 原因?
- 分配的太少了
- 内存泄漏
- 一般指的是代码中引用问题,申请使用完或无利用的内存没有及时释放、无法释放已申请的内存空间,JVM就不能继续使用这段内存而导致。
- 比如定义了全局变量或者成员变量,但是方法执行完了。
- 常见的有数据库、网络连接、IO等连接未关闭;
- 作用域不合理,成员变量只在该类中一个方法中使用,但若是调用其他方法,该变量内存就为泄漏。
- 内存溢出
- 直接原因:物理内存过小导致
- 根本原因:内存泄漏的次数过多,就会导致gc不能回收泄露的空间内存,最终积累导致内存溢出。
如何解决?
- 首先通过内存映像分析工具(如Eclipse Memory Analyzer)对dump 出来的堆转储快照进行分析,重点是确认内存中的对象是否是必要的,也就是要先分清楚到底是出现了内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。
- 如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots 的引用链。于是就能找到泄漏对象是通过怎样的路径与GC Roots 相关联并导致垃圾收集器无法自动回收它们的。掌握了泄漏对象的类型信息,以及GC Roots 引用链的信息,就可以比较准确地定位出泄漏代码的位置。
- 如果不存在内存泄漏,换句话说就是内存中的对象确实都还必须存活着,那就应当检查虚拟机的堆参数(-Xmx 与-Xms),与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗。
1.4 Java基础知识
1、final、private、static final 修饰符?
-
可以加强线程安全,符合面向对象编程开闭原则中的close,例如子类不可继承、非法访问等
-
final 变量可以保证其他线程获取的该变量的值是唯一的。
-
例如:(变量指成员变量或者静态变量)
final int a =10; int b = 10; // b 变量赋值的底层字节码指令被分为两步:第一步先定义 int b;第二步再赋值为 10 // final 在其指令后自动加入了写屏障,可以保证其变量的可见性
-
b 不能保证其他线程获取到的值一定是 10,有可能为 0。
-
-
读取 final 变量解析
- 不加 final 读取变量时去堆内存寻找,final 常量是在栈空间,读取速度快
- 读取 final 变量时,直接将其在栈中的值复制一份,不用去 getstatic ,性能得到提升
- 注意:不是所有被 final 修饰的变量都在栈中。当数值超过变量类型的 MAX_VALUE 时,将其值存入常量池中
- 读取变量的速度:栈 > 常量池 > 堆内存
-
-
final
和static
和final static
区别解释?- static是用来修饰静态资源的(包括类、方法、变量等),final 是用来保证当前变量为常量,final static即保证为静态常量(意思就是不依赖于对象去调用,也不可以被改变)
- final 可以用在方法参数声明中,保证引用变量不会被改变(只读)。
-
这其中也需要看所修饰的为对象还是变量或者常量
static 修饰符详解
首先解释一下static所修饰的变量、方法、类它们都是可以单独存在,静态类一定是静态内部类
-
静态内部类可以声明普通成员变量和方法,而普通内部类不能声明static变量和方法(final static为除外,他本质也是常量)。
-
静态内部类使用场景一般是当外部类需要使用内部类,而内部类无需依赖于外部类资源,并且内部类可以单独创建的时候
可修饰的位置介绍:
- 静态内部类和静态变量可无限套娃:static 可修饰成员变量和方法和内部类。
不可修饰的位置注意点:
- 内部类(非静态)不能有静态声明(成员变量、方法)原因:非静态内部类需要实例化外部类去调用该类,
- 非静态变量、方法、类不可以被静态方法实例化引用
总结:无论是变量、方法、类。它们都可称为资源,所谓资源的静态和非静态。即静态资源就是可单独存在,非静态资源需要依赖于实例对象去调用,他不可单独存在。
2、Integer、string、stringbuffer、hashtable等线程一定安全吗
-
Integer 是线程安全的
-
private final int value; // final变量定义后不可改变
-
-
-
对于string来说是线程安全的
-
string 进行修改操作时,将返回一个拼接后新的字符串对象,并不会变动原来的对象
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen); this.value = Arrays.copyOfRange(value, offset, offset+count);
-
-
对于stringbuffer 是线程安全的
- 因为它的底层append方法声明了synchronized关键字,加入了同步锁。
- stringbuilder并没有,因而线程不安全。
-
对于hashtable来说不一定
-
对于单个方法操作 是具有原子性的
public synchronized V put(K key, V value)
-
对于组合方法使用时,多线程会造成不安全,还需要加相应的锁机制来约束
if (hashtable.get(key) != null){ hashtable.put(key,value); }
-
二.框架相关
2.1 Spring
1、Interceptor 与 Filter 的区别?
-
相同点
- Spring 的Interceptor 与 Servlet 的 Filter 有相似之处,比如二者都是 AOP 编程思想的体现,
都能实现权限检查、日志记录等。
- Spring 的Interceptor 与 Servlet 的 Filter 有相似之处,比如二者都是 AOP 编程思想的体现,
-
不同点:
-
使用范围不同: Filter 是 Servlet 规范规定的,只能用于 Web 程序中。而Interceptor 既
可以用于 Web 程序,也可以用于 Application 、 Swing 程序中。 -
实现规范不同: Filter 是在 Servlet 规范中定义的,是 Servlet 容器支持的。而Interceptor 是
在 Spring 容器内的,是 Spring 框架支持的。 -
使用的资源不同:同其他的代码块一样,Interceptor 也是一个 Spring 的组件,归 Spring 管理,配置在 Spring 文件中,因此能使用 Spring 里的任何资源、对象,例如 Service 对象、数据源、事务管理等,通过 loC 注入到Interceptor 即可;而 Filter 则不能。
-
作用域深度不同: Filter 在只在 Servlet 前后、处理器前后起作用。而Interceptor 能够深入到方法前后、异常
抛出前后等,因此Interceptor 的使用具有更大的弹性。所以在 Spring 构架的程序中,要优先使用Interceptor 。
-
2、探探你对IOC和AOP的理解与认识?
IOC
- IOC说白了就是一个容器,在我们的spring框架中,它的表面意思就是控制反转,控制反转说的就是我们的控制主动权交给IOC容器。IOC容器来管理我们对象的生命周期和各种对象的依赖问题。
- 比如说我需要在一个组件中调用其他的component组件或者bean实例,前提是我们需要将这些组件和bean都注入该容器中。这个时候我们使用的时候只需要关注被调用者是谁,而不用去关注他在哪,我们从哪去找到、去获取、去实现、销毁等细节。他就像一个大杂烩一样,我们把整个spring应用中的所有组件都注册进去,他就可以帮我们去处理依赖的问题,简化我们的管理逻辑。
- DI依赖注入
AOP
- AOP顾名思义就是面向切面编程。我们日常java开发根据面向对象编程一般写的代码都是流水线或者说是纵向逻辑的,不能代码进行复用。比如说需要给每个业务添加一个日志分析处理,如果按照面向对象编程,就需要对每个业务方法上添加同样逻辑代码,这会造成代码冗余且影响整体项目代码结构。
- 但是在实际开发中,我们需要对其中的某一个点进行修改或者添加部分业务功能逻辑等。这时候面向切面编程AOP派上用场了,可以单独写一个逻辑业务横切到我们整个项目的任意位置中;并且还可以控制其中某一个bean的前后环绕等逻辑。
- 总结:AOP,面向切面编程,作为面向对象编程的补充,可以将一些和业务代码无关的、公用的代码抽取并封装出来作为一个切面,进而横切到我们的业务中使用。像一些权限认证(拦截器的实现等)、日志、事务等。
- AOP原理:AOP可以基于静态代理和动态代理两种来实现。AOP思想已经出现很多年了,比Spring更早,Spring只是将AOP思想很好的应用到其中,就像SpringAOP的实现。
- SpringAOP主要基于动态代理模式实现的,而SpringAOP动态代理模式有两种:JDK动态代理和CGLIB动态代理。
JDK动态代理
- 动态代理首先是基于反射实现的
3、BeanFactory和ApplicationContext有什么区别?
-
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。
-
BeanFactory是Spring里面最底层的接口,是IoC的核心,定义了IoC的基本功能,包含了各种Bean的定义、加载、实例化,依赖注入和生命周期管理。ApplicationContext接口作为BeanFactory的子类,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:
-
继承MessageSource,因此支持国际化。
资源文件访问,如URL和文件(ResourceLoader)。 -
载入多个(有继承关系)上下文(即同时加载多个配置文件) ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
-
提供在监听器中注册bean的事件。
-
-
BeanFactroy采用的是延迟加载形式来注入Bean的,只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样的话,我们就不能提前发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
-
ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。
-
ApplicationContext启动后预载入所有的单实例Bean,所以在运行的时候速度比较快,因为它们已经创建好了。相对于BeanFactory,ApplicationContext 唯一的不足是占用内存空间,当应用程序配置Bean较多时,程序启动较慢。
-
-
BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。
-
BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。
4、Spring Bean的生命周期?
5、Spring启动加载流程?
2.2、Mybatis
1、了解mybatis多级缓存吗?
-
首先mybatis可以说是实现了一个二级缓存。它的一级缓存作用域是SQLSession,比如说相同的两次查询,第一次会去查数据库,第二次就走缓存了,相同的要求必须是它的SQL语句相同,和使用的是同一个SQLSession。
-
一级缓存存入时机:第一次查询数据库后就缓存。
-
一级缓存是基于 PerpetualCache 的 HashMap 本地缓存,默认打开一级缓存。
-
-
二级缓存作用域是namebace,是Mapper映射级别的缓存,如果说我们使用多个SQLSession去操作的是同一个Mapper映射的查询语句,那么多个SQLSession可以共享二级缓存。
-
缓存的过期时间是cache的过期时间,是flushInterval,意味着整个清空缓存cache。
-
二级缓存存入时机:一级缓存SQLSession关闭后,会将一级缓存到二级中。
-
其次每次mybatis查询映射,第一步先去查询二级缓存中有无,然后再去一级缓存中找,没有就去查库。
-
-
缓存的清除:增删改操作之后(SQLSession执行commit)
2、知道SQL注入吗?
SQL注入问题
-
概述:
- 首先SQL注入是一个非常危险的操作,很可能被一些不怀好意的人钻空导致我们系统出现异常等状况,比如数据库遭到破坏或被入侵。
-
直接原因:
- 在页面中有数据交互的地方,攻击者构造sql语句,使web服务器执行恶意命令访问数据库。
-
根本原因:服务端没有严格检验用户数据导致SQL注入漏洞,像使用JDBC的Statement语句添加SQL语句,如下:
-
由于我们的JDBC在对数据库进行操作时,需要客户端传入一些参数。我们在日常中的处理是将字符串参数作为SQL语句进行拼接,但是加入客户端传入SQL语句关键字恶意篡改SQL语句就会改变服务端SQL语义发生系统异常。严重时就会导致系统和数据库破坏,这时的攻击方式就叫SQL注入了。
-
实例:模拟登录请求传入用户id和密码参数,使用字符串拼接导致的SQL注入。
-
拼接SQL语句,就会出现SQL注入的安全问题,拼接代码如下:
String sql = "select * from user where username='" + uid + "' and password='" + passwd + "'";
-
若此时传入参数如下:永真式 或 封号结束注释后面条件验证(只能说人的脑洞真大哈哈)
params.put("uid", "malongfei"); params.put("passwd", "111' or '1' = '1"); // 或者 params.put("uid", "malongfei'; -- ") // 或者 params.put("uid", "malongfei'; # ")
-
此时JDBC还没意识到安全问题,依旧将以上参数拼接到我们的SQL原语中,如下:
select * from user where uid = 'malongfei' and passwd = '111' or '1' = '1'; select * from user where uid = 'malongfei'; -- ' and passwd = '111' or '1' = '1'; select * from user where uid = 'malongfei'; # ' and passwd = '111' or '1' = '1';
-
-
-
预防SQL注入:使用PreparedStatement代替Statement可以有效防止SQL注入。
- PreparedStatement利用预编译的机制将sql语句的主干和参数分别传输给数据库服务器,这样即使参数中携带数据库关键字,也不能作为SQL中真正的关键字而起作用。
// 后端登录验证密码接口的SQL语句 select * from user where uid = ? and passwd = ?;
- 设置黑名单也可提前预防,单纯针对于用户输入中含有SQL关键字的拦截方法,比如在注册账号时,用户名和密码中不能含有SQL语句关键字;
- 或者说在进行SQL拼接时加入逻辑处理,对传入参数含有SQL关键字的进行报输入异常。
-
PreparedStatement 与 Statment 区别:
- 语法不同:PreparedStatement 使用预编译的sql,而 Statment 使用静态的sql
- 效率不同: PreparedStatement 具有 sql缓存区,效率比 Statment 高
- 安全性不同:PreparedStatement 可以有效防止sql注入,而 Statment 不能
Mybatis对SQL注入的预防处理
-
出现SQL注入问题的原因和上面一样,都是由于拼接SQL导致的,只不过方式不同。
- Mybatis接收参数处理有两种语法:
#{}
和${}
,#
使用预编译,$
使用拼接SQL方式。 - 这里需要注意的是:使用#运算符,Mybatis会将传入的参数当成一个字符串,在进行变量替换时会加上引号!
- Mybatis接收参数处理有两种语法:
-
mybatis 出现SQL注入实例:
-
模糊查询时,如下实例:
-
采用
#{}
的话程序会报异常。最后替换成like "'name'"
select * from users where name like '%#{name}%'
-
常人看了既然#{}报错那么我用${},正中SQL注入的下怀,这个时候倘若我们的服务端 Java 代码没有对传入参数进行拦截处理,SQL注入条件满足!
select * from users where name like '%${name}%'
-
正确SQL写法,需要使用 concat函数 来进行连接参数(concat为mysql函数,连接参数产生字符串)
select * from users where name like concat('%',#{name}, '%')
-
-
-
补充:
-
in
之后的多个参数在mybatis中也不能采用#{}
或者${}
,需要使用动态SQL语法中foreach
循环遍历select * from users where id in <foreach collection="ids" item="item" open="("separatosr="," close=")"> #{item} </foreach>
-
order by
之后也不能使用#{}
,他也会将字段改为字符串形式,加上引号后就不能正常排序,所以我们需要考虑${}
的方式,但是在后台代码中一定要进行数据参数的校验等手段,防止SQL注入.
-
2.3、SpringMVC
1、MVC执行流程?
- 首先客户端像服务器发起请求(携带url地址),请求进入web应用,请求会被Spring MVC的前端控制器 DispatcherServlet拦截;
- DispatcherServlet带着URL路径去找处理器映射器HandlerMapping匹配相应的处理器对象Handler和url对应的拦截器对象;
- HandlerMapping然后返回给DispatcherServlet相应的Handler;
- DispatcherServlet根据返回结果选择合适的HandlerAdapter去适配相应的处理器;
- HanandlerAdapter会调用执行Handler(处理器) 也就是Controller
- Handler处理请求,执行业务代码,封装数据返回给HanandlerAdapter模型和视图ModelAndView对象;
- HanandlerAdapter执行完之后将ModelAndView返回给DispatchServlet;
- DispatchServlet根据ModelAndView中的View去找视图解析器ViewResolve;
- ViewResolve解析完视图之后将相应视图返回给DispatchServlet。
- DispatchServlet去找View视图进行渲染(单体软件前后端不分离),最终将结果返回给客户端。
- 不过我们开发中一般都是用的JSON数据格式进行前后端数据交互,加入控制器为RestController的话,那么MVC就会自动将这个字符串返回,这期间也少了解析视图和渲染数据等流程。说起渲染数据的其他方式,就得谈谈现在主流的开发方式,前后端分离开发模式,我们服务器端也就是后端开发更加注重起了性能优化、业务逻辑处理等等。将数据渲染和视图之间调用都交给了前端。不想之前的JSP页面技术,需要我们服务器端全权处理,渲染数据等。不过后来后端为了优化JSP这种古板方式,SpringBoot也出现了像themleaf这样的后端渲染引擎,很适合与小模块的单体服务开发。类似的框架前端现在主流的是Vue框架来渲染数据,并且采用组件化开发模式,极大地改善了前端代码冗余、渲染数据困难等操作。
2.4、SpringBoot
三. 中间件相关
3.1 Redis
四. MySQL相关
1、事务
1.1、事务的特性?
事务包括四大特性:ACID
-
A: 原子性 Atomicity(要么都成功!要么都失败!)
- 一个事务是一个单独的个体,不可拆分,有一个指令执行失败,事务进行回滚到执行整个指令执行之前的状态
-
C: 一致性:事务必须是保证多条DML语句同时成功或者同时失败 Consistenty
- 事务执行前后需要保证数据完整性(准确性和可靠性),像主键约束、not null 等等
-
I: 隔离性 Isolation
- 相对于并发访问时满足隔离性,事务A与事务B之间具有隔离
-
D: 持久性 Durability
- 事务执行完毕之后对数据的改变是永久的,即存储在磁盘中
1.2、关于事务之间的隔离性
链接:Immersive Reader (cnblogs.com)
MySQL数据库自动提交事务 :使用 start transaction 关闭自动提交机制。
MySQL默认级别:第三级别:可重复读
事务隔离性存在隔离级别,理论上隔离级别包括四个:
-
第一级别:读未提交
- 对方事务还没有提交,我们当前事务可以读取到对方未提交的数据,读未提交 存在脏读(Dirty Read)现象,表示读到了脏的数据
-
第二级别:读已提交
- 解决脏读--->读已提交导致不可重复读
- 不可重复读:一次事务相同查询返回不同的数据
-
第三级别:可重复读
- 解决不可重复读--->可重复读(导致幻读)
- 开始读取数据(事务开启)时,不再允许update操作,但是不能杜绝insert操作
- 使用了行级锁不可重复读:一次事务相同查询返回不同的数据
-
第四级别:序列化读/串行化读
- 使用表级锁,避免了所有问题,事务A与事务B串行,而不并发。
- 解决了所有问题。效率低。需要事务排队。
-
总结
- 幻读和不可重复读:幻读和不可重复读都是指的一个事务范围内的操作受到其他事务的影响了。
- 只不过幻读是重点在插入和删除,不可重复读重点在修改。
2、索引
2.1、索引的分类?
- 单一索引:给单个字段添加索引
- 复合索引:给多个字段联合起来添加1个索引
- 主键索引:主键上会自动添加索引
- 唯一索引:有unique约束的字段上会自动添加索引
- ····
2.2、索引什么时候失效?
select username from user where usename like '%zs%';
模糊查询的时候 使用了通配符 % 这个时候是失效的
3、数据库设计三范式
3.1、什么是设计范式?
设计表的依据。按照这个三范式设计的表不会出现数据冗余
3.2、三范式都是哪些?
-
第一范式:任何一张表都应该有主键,并且每一个字段原子性不可再分
-
第二范式:建立在第一范式的基础上,所有的非主键字段完全依赖主键,不产生部分依赖。
多对多?三张表,关系表两个外键
-
第三范式:建立在第一范式的基础之上,所有的非主键字段直接依赖主键,不能产生传递依赖。
一对多?两张表,多的表加外键