• 极客专栏正式上线!欢迎访问 https://www.jikewenku.com/topic.html
  • 极客专栏正式上线!欢迎访问 https://www.jikewenku.com/topic.html

Java后端开发面试问题总结(下3)

技术杂谈 勤劳的小蚂蚁 3个月前 (01-08) 113次浏览 已收录 0个评论 扫描二维码



本文主要涉及下面11个【JVM】问题以及22个【数据库】问题

  • JVM
    • 1、Java内存区域(注意不是Java内存模型JMM)的划分?
    • 2、新生代和老年代。对象如何进入老年代,新生代怎么变成老年代?
    • 3、新生代的GC和老年代的GC?
    • 4、对象在什么时候可以被回收,调用finalize方法后一定会被回收吗?
    • 5、哪些对象可以作为GC Roots?
    • 6、讲一讲垃圾回收算法?
    • 7、介绍下类加载器和类加载过程?
    • 8、什么是双亲委派模型,有什么好处?如何打破双亲委派模型?
    • 9、说一说CMS和G1垃圾收集器?各有什么特点。
    • 10、CMS和G1的区别?
    • 11、GC一定会导致停顿吗,为什么一定要停顿?任意时候都可以GC吗还是在特定的时候?
  • 数据库
    • 1、数据库设计的三大范式?
    • 2、MySql的事务隔离级别?推荐使用哪种?
    • 3、MySql数据库在什么情况下出现死锁?产生死锁的四个必要条件?如何解决死锁?
    • 4、现在发现sql查询很慢,如何分析哪里出了问题,应该如何优化?
    • 5、索引的好处?
    • 6、哪些情况需要建立索引?
    • 7、索引的最左匹配原则了解吗?
    • 8、如何建立复合索引,可以使sql语句能尽可能匹配到索引?
    • 9、建立了索引,索引就一定会被命中吗?或者说索引什么时候失效
    • 10、为什么要使用联合索引?
    • 11、既然索引可以加快查询速度,索引越多越好是吗?
    • 12、主键和唯一索引的区别?
    • 13、B+树和B-树的区别?
    • 14、聚集索引与非聚集索引的区别?
    • 15、InnoDB和MyISAM引擎的区别?
    • 16、 COUNT(*)和 COUNT(1)的区别? COUNT(列名)和 COUNT(*)的区别?
    • 17、数据库中悲观锁和乐观锁讲一讲?
    • 18、MySQL的可重复读是如何实现的?
    • 19、覆盖索引是什么?
    • 20、MySQL中JOIN和UNION什么区别?
    • 21、WHERE和HAVING的区别?
    • 22、SQL注入是什么,如何防止?

JVM

1、Java内存区域(注意不是Java内存模型JMM)的划分?

  • 程序计数器。
  • 虚拟机栈。
  • 本地方法栈。
  • Java堆。
  • 方法区。
前三个是线程私有的,后两个是线程共享的。
字节码解释器通过改变程序计数器的值来决定下一条要执行的指令,为了在线程切换后每条线程都能正确回到上次执行的位置,因为每条线程都有自己的程序计数器。
虚拟机栈是存放Java方法内存模型,每个方法在执行时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法返回地址等信息。方法的开始调用对应着栈帧的进栈,方法执行完成对应这栈帧的出栈。位于栈顶被称为“当前方法”。
本地方法栈和虚拟机栈类似,不过虚拟机栈针对Java方法,而本地方法栈针对Native方法。
Java堆。对象实例被分配内存的地方,也是垃圾回收的主要区域。
方法区。存放被虚拟机加载的类信息、常量(final)、静态变量(static)、即时编译期编译后的代码。方法区是用永久代实现的,这个区域的内存回收目标主要是针对常量池的回收和类型的卸载。运行时常量池是方法区的一部分,运行时常量池是Class文件中的一项信息,存放编译期生成的各种字面量和符号引用。

2、新生代和老年代。对象如何进入老年代,新生代怎么变成老年代?

Java堆分为新生代和老年代。在新生代又被划分为Eden区,From Sruvivor和To Survivor区,比例是8:1:1,所以新生代可用空间其实只有其容量的90%。对象优先被分配在Eden区。
  • 不过大对象比如长字符串、数组由于需要大量连续的内存空间,所以直接进入老年代。这是对象进入老年代的一种方式,
  • 还有就是长期存活的对象会进入老年代。在Eden区出生的对象经过一次Minor GC会若存活,且Survivor区容纳得下,就会进入Survivor区且对象年龄加1,当对象年龄达到一定的值,就会进入老年代。
  • 在上述情况中,若Survivor区不能容纳存活的对象,则会通过分配担保机制转移到老年代。
  • 同年龄的对象达到suivivor空间的一半,大于等于该年龄的对象会直接进入老年代。

3、新生代的GC和老年代的GC?

发生在新生代的GC称为Minor GC,当Eden区被占满了而又需要分配内存时,会发生一次Minor GC,一般使用复制算法,将Eden和From Survivor区中还存活的对象一起复制到To Survivor区中,然后一次性清理掉Eden和From Survivor中的内存,使用复制算法不会产生碎片。
老年代的GC称为Full GC或者Major GC:
  • 当老年代的内存占满而又需要分配内存时,会发起Full GC
  • 调用System.gc()时,可能会发生Full GC,并不保证一定会执行。
  • 在Minor GC后survivor区放不下,通过担保机制进入老年代的对象比老年代的内存空间还大,会发生Full GC;
  • 在发生Minor GC之前,会先比较历次晋升到老年代的对象平均年龄,如果大于老年代的内存,也会触发Full GC。如果不允许担保失败,直接Full GC。

4、对象在什么时候可以被回收,调用finalize方法后一定会被回收吗?

在经过可达性分析后,到GC Roots不可达的对象可以被回收(但并不是一定会被回收,至少要经过两次标记),此时对象被第一次标记,并进行一次判断:
  • 如果该对象没有调用过或者没有重写finalize()方法,那么在第二次标记后可以被回收了;
  • 否则,该对象会进入一个FQueue中,稍后由JVM建立的一个Finalizer线程中去执行回收,此时若对象中finalize中“自救”,即和引用链上的任意一个对象建立引用关系,到GC Roots又可达了,在第二次标记时它会被移除“即将回收”的集合;如果finalize中没有逃脱,那就面临被回收。
因此finalize方法被调用后,对象不一定会被回收。

5、哪些对象可以作为GC Roots?

  • 虚拟机栈中引用的对象
  • 方法区中类静态属性引用的对象(static)
  • 方法区中常量引用的对象(final)
  • 本地方法栈中引用的对象

6、讲一讲垃圾回收算法?

  • 复制算法,一般用于新生代的垃圾回收
  • 标记清除, 一般用于老年代的垃圾回收
  • 标记整理,一般用于老年代的垃圾回收
  • 分代收集:根据对象存活周期的不同把Java堆分为新生代和老年代。新生代中又分为Eden区、from survivor区和to survivor区,默认8:1:1,对象默认创建在Eden区,每次垃圾收集时新生代都会有大量对象死亡。此时利用复制算法将Eden区和from survivor区还存活的对象一并复制到tosurvivor区。老年代的对象存活率高,没有额外空间进行分配担保,因此采用标记-清除或者标记-整理的算法进行回收。前者会产生空间碎片,而后者不会。

7、介绍下类加载器和类加载过程?

先说类加载器
在Java中,系统提供了三种类加载器。
  • 启动类加载器(Bootstrap ClassLoader),启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要委派给启动类加载器,直接使用null。
  • 扩展类加载器(Extension ClassLoader)
  • 应用程序类加载器(Application ClassLoader),负责加载用户类路径(ClassPath)上锁指定的类库。是程序中默认的类加载器。
当然用户也可以自定义类加载器。
再说类加载的过程
主要是以下几个过程:
  1. 加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载
加载
  • 通过一个类的全限定名获取定义该类的二进制字节流
  • 将字节流表示的静态存储结构转化为方法区的运行时数据结构
  • 在内存中生成这个类的Class对象,作为方法区这个类的各种数据的访问入口
验证
  • 文件格式验证:比如检查是否以魔数0xCAFEBABE开头
  • 元数据验证:对类的元数据信息进行语义校验,保证不存在不符合Java语言规范的元数据信息。比如检查该类是否继承了被final修饰的类。
  • 字节码验证,通过数据流和控制流的分析,验证程序语义是合法的、符合逻辑的。
准备。 为类变量(static)分配内存并设置默认值。比如static int a = 123在准备阶段的默认值是0,但是如果有final修饰,在准备阶段就会被赋值为123了。
解析
将常量池中的符号引用替换成直接引用的过程。包括类或接口、字段、类方法、接口方法的解析。
初始化
按照程序员的计划初始化类变量。如static int a = 123,在准备阶段a的值被设置为默认的0,而到了初始化阶段其值被设置为123。

8、什么是双亲委派模型,有什么好处?如何打破双亲委派模型?

类加载器之间满足双亲委派模型,即:除了顶层的启动类加载器外,其他所有类加载器都必须要自己的父类加载器。当一个类加载器收到类加载请求时,自己首先不会去加载这个类,而是不断把这个请求委派给父类加载器完成,因此所有的加载请求最终都传递给了顶层的启动类加载器。只有当父类无法完成这个加载请求时,子类加载器才会尝试自己去加载。
双亲委派模型的好处?使得Java的类随着它的类加载器一起具备了一种带有优先级的层次关系。Java的Object类是所有类的父类,因此无论哪个类加载器都会加载这个类,因为双亲委派模型,所有的加载请求都委派给了顶层的启动类加载器进行加载。所以Object类在任何类加载器环境中都是同一个类。
如何打破双亲委派模型?使用OSGi可以打破。OSGI(Open Services Gateway Initiative),或者通俗点说JAVA动态模块系统。可以实现代码热替换、模块热部署。在OSGi环境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为更加复杂的网状结构。

9、说一说CMS和G1垃圾收集器?各有什么特点。

CMS(Concurrent Mark Sweep) 从名字可以看出是可以进行并发标记-清除的垃圾收集器。针对老年代的垃圾收集器,目的是尽可能地减少用户线程的停顿时间。
收集过程有如下几个步骤:
  • 初始标记:标记从GC Roots能直接关联到的对象,会暂停用户线程
  • 并发标记:即在堆中堆对象进行可达性分析,从GC Roots开始找出存活的对象,可以和用户线程一起进行
  • 重新标记:修正并发标记期间因用户程序继续运作导致标记产生变动的对象的标记记录
  • 并发清除:并发清除标记阶段中确定为不可达的对象
CMS的缺点:
  • 由于是基于标记-清除算法,所以会产生空间碎片
  • 无法处理浮动垃圾,即在清理期间由于用户线程还在运行,还会持续产生垃圾,而这部分垃圾还没有被标记,在本次无法进行回收。
  • 对CPU资源敏感
CMS比较类似适合用户交互的场景,可以获得较小的响应时间。
G1(Garbage First),有如下特点:
  • 并行与并发
  • 分代收集
  • 空间整合 :整体上看是“标记-整理”算法,局部(两个Region之间 )看是复制算法。确保其不会产生空间碎片。(这是和CMS的区别之一)
  • 可预测的停顿:G1除了追求低停顿外,还能建立可预测的时间模型,主要原因是它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。
在使用G1收集器时,Java堆的内存划分为多个大小相等的独立区域,新生代和老年代不再是物理隔离。G1跟踪各个区域的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的区域。
G1的收集过程和CMS有些类似:
  • 初始标记:标记与GC Roots直接关联的对象,会暂停用户线程(Stop the World)
  • 并发标记:并发从GC Roots开始找出存活的对象,可以和用户线程一起进行
  • 最终标记:修正并发标记期间因用户程序继续运作导致标记产生变动的对象的标记记录
  • 筛选回收:清除标记阶段中确定为不可达的对象,具体来说对各个区域的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。
G1的优势:可预测的停顿;实时性较强,大幅减少了长时间的gc;一定程度的高吞吐量。

10、CMS和G1的区别?

由上一个问题可总结出CMS和G1的区别:
  • G1堆的内存布局和其他垃圾收集器不同,它将整个Java堆划分成多个大小相等的独立区域(Region)。G1依然保留了分代收集,但是新生代和老年代不再是物理隔离的,它们都属于一部分Region的集合,因此仅使用G1就可以管理整个堆。
  • CMS基于标记-清除,会产生空间碎片;G1从整体看是标记-整理,从局部(两个Region之间)看是复制算法,不会产生空间碎片。
  • G1能实现可预测的停顿。

11、GC一定会导致停顿吗,为什么一定要停顿?任意时候都可以GC吗还是在特定的时候?

GC进行时必须暂停所有Java执行线程,这被称为Stop The World。为什么要停顿呢?因为可达性分析过程中不允许对象的引用关系还在变化,否则可达性分析的准确性就无法得到保证。所以需要STW以保证可达性分析的正确性。
程序执行时并非在所有地方都能停顿下来开始GC,只有在“安全点”才能暂停。安全点指的是:HotSpot没有为每一条指令都生成OopMap(Ordinary Object Pointer),而是在一些特定的位置记录了这些信息。这些位置就叫安全点。

数据库

1、数据库设计的三大范式?

  • 第一范式1NF: 数据表中的每一列(字段),必须是不可拆分的最小单元,也就是确保每一列的原子性。如订单信息列为orderInfo = “DD1024 2018.5.18″,必须拆分为orderId和orderTime。
  • 第二范式2NF: 在满足第一范式的基础上,表中的所有列都必需依赖于主键(和主键有关系),其他和主键没有关系的列可以拆分出去。通俗点说就是:一个表只描述一件事情。比如order表中有orderId、orderTime、userId和userName,只有前两列依赖于订单表,后两列需要拆分到user表中。
  • 第三范式3NF: 在满足第二范式的基础上,要求数据不能有传递关系。表中的每一列都要与主键直接相关,而不是间接相关(表中的每一列只能依赖于主键)。比如order表中有orderId、orderTime、userId和userName,根据orderId可以查出userId,根据userId又可以查出userName,这就是数据的传递性,完全可以只留下userId这一列。

2、MySql的事务隔离级别?推荐使用哪种?

  • 读未提交
  • 读已提交
  • 可重复读
  • 串行化
在具体解释上面的四个隔离级别前。有必要了解事务的四大特性(ACID)
推荐阅读这篇博客:
  • 原子性(Atomicity):事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
  • 一致性(Consistency):事务开始前和结束后,数据的完整性约束没有被破环。比如A向B转了钱,转账前后钱的总数不变。
  • 隔离性(Isolation):多个用户并发访问数据数据库时,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间的数据相互隔离。比如事务A和事务B都修改同一条记录,这条记录就会被重复修改或者后者会覆盖前者的修改记录。
  • 持久性(Durability):事务完成后,事务对数据库的更新被保存到数据库,其结果是永久的。
事务并发可能产生的问题: 脏数据:事务对缓冲池中的行记录进行修改,但是还没有被提交。
  • 脏读:事务A读取到了事务B修改但未提交的数据。如果此时B回滚到修改之前的状态,A就读到了脏数据。
  • 不可重复读:事务A多次读取同一个数据,此时事务B在A读取过程中对数据修改并提交了,导致事务A在同一个事务中多次读取同一数据而结果不同。
  • 幻读:事务A对表进行修改,这个修改涉及到表中所有的行,但此时事务B新插入了一条数据,事务A就会发现居然还有数据没有被修改,就好像发生幻觉一样。
脏读是读取到事务未提交的数据,不可重复度读读取到的是提交提交后的数据,只不过在一次事务中读取结果不一样。
不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。
一般来说,数据库隔离级别不一样,可能出现的并发问题也不同。级别最高的是串行化,所有问题都不会出现。但是在并发下性能极低,可重复读会只会导致幻读。
所以一般使用MySQL默认的可重复读即可。MVCC(多版本并发控制)使用undo_log使得事务可以读取到数据的快照(某个历史版本),从而实现了可重复读。MySQL采用Next-Key Lock算法,对于索引的扫描不仅是锁住扫描到的索引,还锁住了这些索引覆盖的范围,避免了不可重复读和幻读的产生。

3、MySql数据库在什么情况下出现死锁?产生死锁的四个必要条件?如何解决死锁?

死锁是指两个或两个以上的事务在执行过程中,因争夺锁资源而造成的一种互相等待的现象,若无外力作用两个事务都无法推进,这样就产生了死锁。下去 死锁的四个必要条件:
  • 互斥条件:即任何时刻,一个资源只能被一个进程使用。其他进程必须等待。
  • 请求和保持条件:即当资源请求者在请求其他的资源的同时保持对原有资源的占有且不释放。
  • 不剥夺条件:资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
  • 环路等待条件:比如A占有B在等待的资源(B等待A释放),B占有A在等待的资源(A等待B释放)。多个进程循环等待着相邻进程占用着的资源。
避免死锁可以通过破环四个必要条件之一。
解决死锁的方法:
  • 加锁顺序保持一致。不同的加锁顺序很可能导致死锁,比如哲学家问题:A先申请筷子1在申请筷子2,而B先申请筷子2在申请筷子1,最后谁也得不到一双筷子(同时拥有筷子1和筷子2)
  • 超时,为其中一个事务设置等待时间,若超过这个阈值事务就回滚,另一个等待的事务就能得以继续执行。
  • 及时检测出死锁,回滚undo量最小的事务。一般是采用等待图(wait-for gragh)。采用深度优先搜索的算法实现,如果图中有环路就说明存在死锁。

4、现在发现sql查询很慢,如何分析哪里出了问题,应该如何优化?

开启慢查询,查找哪些sql语句执行得慢。使用explain查看语句的执行计划,比如有没有使用到索引,是否启用了全表扫描等。查询慢,很大可能是因为没有使用索引或者索引没有被命中。还有其他的原因,比如发生了死锁,硬件、网速等原因。
优化手段:为相关列添加索引,并且确保索引可以被命中。优化sql语句的编写。

5、索引的好处?

索引是对数据库表中一个或多个列的值进行排序的结构。MySql中索引是B+树,在查找时可以利用二分查找等高效率的查找方式,以O(lg n)的时间找到。因此索引可以加快查询速度。

6、哪些情况需要建立索引?

  • 在经常要搜索的列上
  • 经常出现在where后面的列上
  • 在作为主键的列上
  • 作为外键的列上
  • 经常需要排序、分组和联合操作的字段建立索引
哪些情况不适合建立索引?
  • 查询中很少使用的字段
  • 数值太少的字段
  • 唯一性不太差的字段
  • 更新频繁的字段
  • 不会出现在where后的字段
  • 索引适合建立在小字段上,text和blob等大字段不适合建立索引

7、索引的最左匹配原则了解吗?

建了一个(a,b,c)的联合索引,那么实际等于建了(a),(a,b),(a,b,c)三个索引,但是有时在条件查询时只会匹配到a或者(a, b)而不会匹配到(a, b, c)。下面的例子
  1. SELECT * FROM table WHERE a =1 AND c =3;// 使用了索引a,c不走索引
  2. SELECT * FROM table WHERE a =1 AND b <2 AND c =3;// 使用到了索引(a,b),c不走索引
建立联合索引(a, b ,c),所以索引是按照a -> b -> c的顺序进行排序的。a-b-c这样的索引是先找a,然后在范围里面找b,再在范围内找c。 所以上面的语句里的c 会分散在很多个b里面且不是排序的,所以没办法走索引。
举个例子比如(a, b)联合索引,先按a排序再按b排序,得到
  1. (1,1)->(1,2)->(2,1)  (2,4)->(3,1)->(3,2)
如果执行 selectafromtablewhereb=2,就没有使用到(a, b)这个联合索引,因为b的值1,2,1,4,1,2显然不是排序的。
具体来说:MySQL会从左开始一直向右匹配直到遇到范围查询(>,<,BETWEEN,LIKE)就停止匹配,比如: a = 1 AND b = 2 AND c > 3 AND d = 4,如果建立 (a,b,c,d)顺序的索引,使用了索引(a, b, c),但是d是没有走索引的,如果建立(a,b,d,c)的索引,则可以命中索引(a, b, c, d),其中a,b,d的顺序可以任意调整。
等于(=)和in 可以乱序。比如,a = 1 AND b = 2 AND c = 3 建立(a,b,c)索引可以任意顺序。

8、如何建立复合索引,可以使sql语句能尽可能匹配到索引?

  • 等于条件的索引放在前面(最左),范围查询放在后面。 a=1AND b=2AND c>3AND d=4,建立(a, b, d, c)就是不错的选择;
  • 先过滤后排序(ORDER BY)如 SELECT*FROM t WHERE c=100andd='xyz'ORDER BY b建立(c, d, b)联合索引就是不错的选择
  • 对于索引列的查询,一般不建议使用LIKE操作,像 LIKE'%abc'这样的不能命中索引;不过 LIKE'abc%'可以命中索引。

9、建立了索引,索引就一定会被命中吗?或者说索引什么时候失效

  • 使用了 notin,<>,!=则不会命中索引。注: <>是不等号
  • innoDB引擎下,若使用OR,只有前后两个列都有索引才能命中(执行查询计划,type是index_merge),否则不会使用索引。
  • 模糊查询中,通配符在最前面时,即 LIKE'%abc'这样不能命中索引
  • 对列进行函数运算的情况(如 where md5(password) = “xxxx”)
  • 联合索引中,遇到范围查询时,其后的索引不会被命中
  • 存了数字的char或varchar类型,常见的如用字符串表示的手机号,在查询时不加引号,则不会命中(如where phone=‘13340456789’能命中,where phone=13340456789不能命中)
  • 当数据量小时,MySQL发现全表扫描反而比使用索引查询更快时不会使用索引。

10、为什么要使用联合索引?

MySQL5.0之前,一个表一次只能使用一个索引,无法同时使用多个索引分别进行条件扫描。但是从5.1开始,引入了 index merge 优化技术,对同一个表可以使用多个索引分别进行条件扫描。
推荐阅读这篇博客
  • 减少开销。建了一个(a,b,c)的联合索引,相当于建了(a),(a,b),(a,b,c)三个索引
  • 覆盖索引。减少了随机IO操作。同样的有复合索引(a,b,c),如果有如下的sql: select a,b,c from table where a=1 and b = 1。那么MySQL可以直接通过遍历索引取得数据,而无需回表,这减少了很多的随机io操作
  • 效率高。索引列越多,通过索引筛选出的数据越少。比如有1000W条数据的表,有如下sql:select * from table where a = 1 and b =2 and c = 3,假设假设每个条件可以筛选出10%的数据,如果只有单值索引,那么通过该索引能筛选出1000W*10%=100w 条数据,然后再回表从100w条数据中找到符合b=2 and c= 3的数据,然后再排序,再分页;如果是复合索引,通过索引筛选出1000w *10% *10% *10%=1w,然后再排序、分页。

11、既然索引可以加快查询速度,索引越多越好是吗?

推荐阅读这篇优博客:
大多数情况下索引能大幅度提高查询效率,但数据的变更(增删改)都需要维护索引,因此更多的索引意味着更多的维护成本和更多的空间 (一本100页的书,却有50页目录?)而且过小的表,建立索引可能会更慢(读个2页的宣传手册,你还先去找目录?)

12、主键和唯一索引的区别?

  • 主键是一种约束,唯一索引是索引,一种数据结构。
  • 主键一定是唯一索引,唯一索引不一定是主键。
  • 一个表中可以有多个唯一索引,但只能有一个主键。
  • 主键不允许空值,唯一索引允许。
  • 主键可以做为外键,唯一索引不行;

13、B+树和B-树的区别?

B-树是一种平衡的多路查找树。2-3树和2-3-4树都是B-树的特例。一棵M阶的B-树,除了根结点外的其他非叶子结点,最多含有M-1对键和链接,最少含有M/2对键和链接。根结点可以少于M/2,但是也不能少于2对。
  • 关键字集合分布在整颗树中
  • 每个元素在该树中只出现一次,可能在叶子结点上,也可能在非叶子结点上。
  • 搜索有可能在非叶子结点结束。
  • 所有叶子结点位于同一层
B+树是B-树的变体,也是一种多路查找树。
  • 非叶子结点值可以看作索引,仅含有其子树中的最大(或)最小关键字。
  • 叶子结点保存了所有关键字,且叶子结点按照从小到大的顺序排列,是一个双向链表结构。
  • 只能在叶子节点命中搜索
B+ 树更适合用于数据库和操作系统的文件系统中。
假设一个结点就是一个页面,B树遍历所有记录,通过中序遍历的方式,要多次返回到父结点,同一个结点多次访问了,增加了磁盘I/O操作的次数。B+因为在叶子结点存放了所有的记录,而且是双向链表的结构,只需在叶子节点这一层就能遍历所有记录,大大减少了磁盘I/O操作,所以数据库索引用B+树结构更好。

14、聚集索引与非聚集索引的区别?

  • 对于聚集索引,表记录的排列顺序和与索引的排列顺序是一致的;非聚集索引不是
  • 聚集索引就是按每张表的主键构造一棵B+树,每张表只能拥有一个聚集索引;一张表可以有多个非聚集索引
  • 聚集索引的叶子结点存放的是整张表的行记录数据;非聚集索引的叶子结点并不包含行记录的全部数据,除了包含键值还包含一个书签——即相应行数据的聚集索引键。因此通过非聚集索引查找时,先根据叶子结点的指针获得指向主键索引的主键,然后再通过主键索引来找到一个完整的行记录。

15、InnoDB和MyISAM引擎的区别?

  • InnoDB支持事务,MyISAM不支持
  • InnoDB是行锁设计,MyISAM是表锁设计
  • InnoDB支持外键,MyISAM不支持
  • InnoDB采用聚集的方式,每张表按照主键的顺序进行存放。如果没有主键,InnoDB会为每一行生成一个6字节的ROWID并以此为主键;MyISAM可以不指定主键和索引
  • InnoDB没有保存表的总行数,因此查询行数时会遍历整表;而MyISAM有一个变量存储可表的总行数,查询时可以直接取出该值
  • InnoDB适合联机事务处理(OLTP),MyISAM适合联机分析处理(OLAP)

16、 COUNT(*)和 COUNT(1)的区别? COUNT(列名)和 COUNT(*)的区别?

COUNT(*)COUNT(1)没区别。 COUNT(列名)COUNT(*)区别在于前者不会统计列为NULL的数据,后者会统计。

17、数据库中悲观锁和乐观锁讲一讲?

悲观锁:总是假设在并发下会出现问题,即假设多个事务对同一个数据的访问会产生冲突。当其他事务想要访问数据时,会在临界区提前加锁,需要将其阻塞挂起。比如MySQL中的排他锁(X锁)、和共享锁(S锁)
乐观锁: 总是假设任务在并发下是安全的,即假设多个事务对同一个数据的访问不会发生冲突,因此不会加锁,就对数据进行修改。当遇到冲突时,采用CAS或者版本号、时间戳的方式来解决冲突。数据库中使用的乐观锁是版本号或时间戳。乐观并发控制(OCC)是一种用来解决写-写冲突的无锁并发控制,不用加锁就尝试对数据进行修改,在修改之前先检查一下版本号,真正提交事务时,再检查版本号有,如果不相同说明已经被其他事务修改了,可以选择回滚当前事务或者重试;如果版本号相同,则可以修改。
提一下乐观锁和MVCC的区别,其实MVCC也利用了版本号,和乐观锁还是能扯上些关系。
MVCC主要解决了读-写的阻塞,因为读只能读到数据的历史版本(快照);OCC主要解决了写-写的阻塞,多个事务对数据进行修改而不加锁,更新失败的事务可以选择回滚或者重试。
当多个用户/进程/线程同时对数据库进行操作时,会出现3种冲突情形:读-读,不存在任何问题;读-写,有隔离性问题,可能遇到脏读、不可重复读 、幻读等。写-写,可能丢失更新。多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,读操作只读该事务开始前的数据库的快照,实现了一致性非锁定读。 这样在读操作不用阻塞写操作,写操作不用阻塞读操作的同时,避免了脏读和不可重复读。乐观并发控制(OCC)是一种用来解决写-写冲突的无锁并发控制,不用加锁就尝试对数据进行修改,在修改之前先检查一下版本号,真正提交事务时,再检查版本号有,如果不相同说明已经被其他事务修改了,可以选择回滚当前事务或者重试;如果版本号相同,则可以修改。

18、MySQL的可重复读是如何实现的?

MVCC(多版本并发控制)使用undo_log使得事务可以读取到数据的快照(某个历史版本),从而实现了可重复读。MySQL采用Next-Key Lock算法,对于索引的扫描不仅是锁住扫描到的索引,还锁住了这些索引覆盖的范围,避免了不可重复读和幻读的产生。
具体来说:
在可重复读下: select….from会采用MVCC实现的一致性非锁定读,读取的是事务开始的快照,避免了不可重复读。select …..from …. for update会采用 Next-Key Locking来保证可重复读和幻读。
在读已提交下: select….from 会采用快照,读取的是最新一份的快照数据,不能够保证不可重复读和幻读;select …..from …. for update会采用Record Lock,不能够保证不可重复读/幻读。

19、覆盖索引是什么?

如果一个索引包含(或覆盖)所有需要查询的字段的值,即只需扫描索引而无须回表,这称为“覆盖索引”。InnoDB的辅助索引在叶子节点中保存了部分键值信息以及指向聚集索引键的指针,如果辅助索引叶子结点中的键值信息已经覆盖了要查询的字段,就没有必要利用指向主键索引的主键,然后再通过主键索引来找到一个完整的行记录了。

20、MySQL中JOIN和UNION什么区别?

UNION 操作符用于合并两个或多个 SELECT 语句的结果集。UNION 内部的 SELECT 语句必须拥有相同数量的列。列也必须拥有相同的数据类型。同时,每条 SELECT 语句中的列的顺序必须相同。默认情况下,UNION会过滤掉重复的值。使用 UNION ALL则会包含重复的值。
JOIN用于连接两个有关联的表,筛选两个表中满足条件(ON后的条件)的行记录得到一个结果集。从结果集中SELECT的字段可以是表A或者表B中的任意列。
JOIN常用的有LEFT JOIN、RIGHT JOIN、INNER JOIN。
  • LEFT JOIN会以左表为基础,包含左表的所有记录,以及右表中匹配ON条件的记录,对于未匹配的列,会以NULL表示。
  • LEFT JOIN会以右表为基础,包含左表的所有记录,以及右表匹配ON条件的记录,对于未匹配的列,会以NULL表示。
  • INNER JOIN,产生两个表的交集(只包含满足ON条件的记录)
INNER JOIN
FULL OUTER JOIN
LEFT JOIN
RIGHT JOIN和LEFT JOIN类似。

21、WHERE和HAVING的区别?

  • WHERE过滤的是行,HAVING过滤分组。
  • WHERE能完成的,都可以用HAVING(只是有时候没必要)
  • WHERE在分组前对数据进行过滤,HAVING在分组后对数据进行过滤
  • WHERE后不能接聚合函数,HAVING后面通常都有聚合函数

22、SQL注入是什么,如何防止?

所谓SQL注入式攻击,就是攻击者把SQL命令插入到Web表单的输入域或页面请求的查询字符串,欺骗服务器执行恶意的SQL命令。
比如在登录界面,如果用户名填入 'xxx'OR1=1--就能构造下面的SQL语句,因为OR 1=1,password被注释掉,因此无论name和password填入什么都能登录成功。
  1. SELECT * FROM USER WHERE NAME='xxx' OR 1=1--and password='xxx';
使用PrepareStatement,可以防止sql注入攻击,sql的执行需要编译,注入问题之所以出现,是因为用户填写 sql语句参与了编译。使用PrepareStatement对象在执行sql语句时,会分为两步,第一步将sql语句 “运送” 到mysql上预编译,再回到java端拿到参数运送到mysql端。预先编译好,也就是SQL引擎会预先进行语法分析,产生语法树,生成执行计划,也就是说,后面你输入的参数,无论你输入的是什么,都不会影响该sql语句的语法结构了。用户填写的 sql语句,就不会参与编译,只会当做参数来看。从而避免了sql注入问题。

丨极客文库, 版权所有丨如未注明 , 均为原创丨
本网站采用知识共享署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议进行授权
转载请注明原文链接:Java后端开发面试问题总结(下3)
喜欢 (0)
[247507792@qq.com]
分享 (0)
勤劳的小蚂蚁
关于作者:
温馨提示:本文来源于网络,转载文章皆标明了出处,如果您发现侵权文章,请及时向站长反馈删除。

您必须 登录 才能发表评论!

  • 精品技术教程
  • 编程资源分享
  • 问答交流社区
  • 极客文库知识库

客服QQ


QQ:2248886839


工作时间:09:00-23:00