常用面试问题以及回答

64 阅读38分钟

1.JAVA中的几种基本数据类型是什么,各自占用多少字节?

byte:1字节8位,表示整数

char:2字节16位,表示整数

short:2字节16位,表示整数

int:4字节32位,表示整数

long:8字节64位,表示整数

float:4字节32位,表示单精度浮点数

double:8字节64位,表示双精度浮点数

boolean:1字节8位,布尔值true或者false

2.String能被继承吗?

不能被继承,因为String被final修饰;被final修饰的类不能被继承 修饰方法不能被覆盖(重写和重载都不行) 变量一旦赋值不能被修改

3.String,Stringbuffer,StringBuilder的区别?

1.  String:
       String类表示字符串对象,是Java中不可变(immutable)的字符串。
       由于它的不可变性,每次对字符串的操作(如拼接、裁剪)都会生成一个新的String对象。
       String类的操作通常比较慢,因为每次操作都会涉及到内存的重新分配。

1.  StringBuffer:
       StringBuffer是可变的字符串序列,它的设计目的是为了解决String频繁创建新对象的问题。
       它是线程安全的,意味着可以在多线程环境中安全使用。
       StringBuffer在进行字符串操作时,通常比String快,因为它内部预先分配了内存,只有在必要时才会扩展内存。

1.  StringBuilder:
       StringBuilder也是可变的字符串序列,它在Java 1.5中被引入。
       它的设计与StringBuffer类似,但是去掉了线程安全的支持,因此在单线程环境下性能更好。
       由于不需要考虑线程同步的问题,StringBuilder通常比StringBuffer更快

4.ArrayList和LinkedList有什么区别?

1.数据结构:
    ArrayList基于动态数组实现,它在内存中是连续的
    LinkedList基于双向链表实现的,它在内存中存储不是连续的,每个元素都有指向下一个和上一个的引用连接起来
2.   性能:
    ArrayList在随机访问元素时性能较好,数组实现直接找下标
    LinkedList在添加或者删除元素的时候性能较好,只需要改变指针即可,如果从头和尾部插入元素,ArrayList也会很快
3.内存占用:
    ArrayList由于内存结构是连续的,可能需要更多的内存用来存储额外的空余空间,以减少因扩容而造成的频繁复制
    LinkedList每个元素都需要额外的存储两个指针,因此对于存储大量的元素而言,可能会比ArrayList占用更多的内存
4.扩容机制:
    ArrayList在底层数组满了之后,会创建一个新的更大的数组,并将旧数组内容复制到新数组中,旧数组可能不会被回收,仍然占用内存,但是没有被引用就会被GC回收
    LinkedList不需要扩容
5.线程安全:
    两者都不是线程安全的

5.类的实例化顺序

首先会加载父类,从父类的静态数据到构造函数(没有显示的调用则执行无参构造)再到非静态字段,再加载子类,子类也是静态数据->构造函数->非静态字段

6.SQL优化有哪些

1.索引优化:
    创建适当的索引来提高查询性能
    避免在where字句中使用函数和计算,这样会阻止索引的使用
2.查询优化:
    尽可能使用JOIN替代子查询
    避免使用select * 选择具体的列
    使用limit此类语句来减少数据量的处理,尤其是在部分返回的情况下
3.统计信息优化:
    保持数据库统计信息更新,这样查询优化器可以更好的决定执行计划
4.执行计划优化:
    查看并分析查询的执行计划,调查查询结构或索引策略,以获取更有效的执行计划
5.存储引擎选择:
    根据应用场景选择合适的存储引擎(InnoDB、MyISAM、Memory等),不同的存储引擎优化方向不同
6.避免不必要的排序
7.使用缓存查询
8.笛卡尔积的避免,确保JOIN操作都是有效的

7.SQL有哪些索引

1.B-Tree索引:最常见的索引类型,适用于全键值、键值范围和键值排序的搜索
2.Hash索引:基于哈希表的实现,只有精确匹配索引所有列的查询才有效,查询快速,不支持排序和部分匹配查找,通过Hash值来比较,所以只能精确匹配
3.Full-text索引:用于文本搜索,能够在字符串数据中进行复杂的搜索,适用于InnoDB和MyISAM存储引擎
4.R-Tree索引:用于空间数据类型,例如GIS数据,支持多维空间数据进行范围查询
5.Clustered索引(聚集索引):在SQL Server中,聚集索引决定了表中数据的物理顺序,每个表只能有一个聚集索引
6.Nonclustered索引(非聚集索引):在SQL Server中,非聚集索引不影响数据在表中的物理顺序,每个表可以有多个非聚集索引
7.Unique索引:保证索引列的唯一性,可以是B-Tree或者Hash索引
8.Covering索引(覆盖索引):包含了查询中所有列的索引,可以避免回表查询
9.Composite索引(复合索引):在索引中包含多个列,可以提高多列查询的效率

8.SQL中哪些操作会导致索引失效

1.表达式计算:在where子句中对索引列使用表达式计算,索引会失效,例如cloumn + 1
2.函数操作:对索引列进行函数操作(如LEFT(),RIGHT(),CONCAT()等)会使得索引失效
3.NULL值:当查询条件包含NULL值到时候,如果索引允许列NULL值,可能会影响索引的效率,但是索引不会失效,这里需要注意
4.Like查询:如果Like查询通配符位于匹配的开头,则索引不会使用(%value和%value%)
5.复合索引的最左匹配原则:在使用复合索引的时候,如果查询条件没有从复合索引的最左边的列开始,则索引不会使用;这里是要求从左到右依次匹配,如果有复合索引<A,B,C>那么查询就要WHERE A = a1 或者 WHERE A = a1 and B = B1 或者 WHERE A = a1 and B = b1 and C = c1
6.使用不同索引需要查看其特性,如果不满足其特性 也会索引失效,比如说上面说到的Hash索引,只有精确匹配索引所有列的查询才有效

9.内连接和外连接的区别

1.自然连接:是一种特殊的等值连接,他要求两个关系表中进行比较的必须是相同属性的列,无须添加连接条件,并且在结果中笑出重复的列。
sql语句: select xxx from 表1 natural join 表2
2.内连接:基本与自然连接相同,不同只出在于内连接不要求两列属性同名,可以用using或者on来指定某两列字段相同的连接条件。
sql语句:select xxx from 表1 inner join 表2 on 表1.A = 表2.E
3.左外连接:两表进行自然连接,返回左表中所有记录,以及与之在右表中匹配的记录。(通常会简写成left join)
sql语句:select xxx from 表1 left outer join 表2 on 表1.A = 表2.E
4.右外连接:两表进行自然连接,返回右表中所有的记录,以及与之在左表中匹配的记录。(通常会简写成right join)
sql语句:select xxx from 表1 right outer join 表2 on 表1.A = 表2.E

10.SQL语句的执行顺序

1.FROM子句,组装来自不同来源的数据
2.WHERE子句,基于指定的条件进行筛选
3.GROUP BY子句,讲数据划分为多个组
4.使用HAVING子句筛选分组
5.如果含有DISTINCT 则在此处去处重复行
6.使用ORDER BY对结果集进行排序
7.最后执行LIMIT这种限制返回行数
即:FROM -> WHERE -> GROUP BY -> HAVING ->DISTINCT -> ORDER BY -> LIMIT -> SELECT

11.什么情况下会发生内存溢出

1.内存泄漏:随着程序运行时间的增长,由于忘记释放不再使用的内存,导致可用内存逐渐减少,最终耗尽所有的可用内存
2.内存膨胀:由于程序员对内存管理的不科学,比如说过度分配内存或者没有充分利用缓存,导致程序占用了远超实际需要的内存。
3.大量数据加载:一次性加载大量的数据到内存中,导致内存快速打到峰值。
4.多线程访问问题:在多线程环境中,如果没有正确管理堆内存的共享访问,可能会导致堆越界或者使用已释放的内存,这两种情况都可能导致内存溢出。
5.资源限制:在有限资源中长时间运行任务可能超出物理内存的限制
6.配置不当:例如,数据库系统的配置不合理,允许查询返回的数据量超过系统可用内存
7.系统级限制:操作系统或者容器环境对进程可用最大内存限制
在编程中,一些常见的可能导致内存溢出的场景:
a.使用大量的全局变量
b.在循环中分配内存而不进行适当的释放
c.创建大量的对象不进行回收
为了预防和处理内存溢出,可以采取以下措施:
a.定期检查和优化代码避免内存泄漏
b.使用内存分析工具来监控和诊断内存的使用情况
c.对于长时间运行的任务,实现内存泄漏检测和清理机制
d.对于多线程程序,确保正确同步内存访问
e.如果可能,增加系统内存或者优化程序以减少内存占用
总的来说 内存溢出就是创建了太多的对象或者对象太大 导致JVM底层模型中的堆空间不足 才会导致内存泄漏

12.什么情况下会发生堆栈溢出

1.无限递归:如果一个函数不断递归调用自身而没有终止条件,或者终止条件达不到,那么每次函数调用都会在栈上创建一个新的栈帧,消耗更多的栈空间,当栈空间被耗尽的时候,就会发生栈溢出,例如:
int a() {
    return a();//无限递归调用
}
int main() {
    a();
    return 0;
}
2.递归深度过深:即使递归有终止条件,但是如果终止条件很难达到,栈空间用完,也会导致栈溢出
3.栈上分配大量内存:在栈上分配大量的内存,例如创建巨大的数组或者结构体,也可能导致栈空间不足
4.函数调用链过深:即使每个函数本身没有问题,如果函数调用链(即一个函数调用另一个函数,另一个又调用其他的函数,以此类推)过深,也可能耗尽栈空间
总的来说,就是一次性使用太多的栈空间,导致栈空间不足,当前操作又没有走完,栈空间得不到释放,则会导致栈溢出。

13.JVM的内存结构,Eden和Survivor的比例

1.堆:JVM内存管理的主要区域,用于存储对象实例。堆内存是所有线程共享的内存区域,也是垃圾回收的主要工作区域。堆内存可以进一步划分为年轻代合老年代,年轻代中又分为Eden和Survivor连个区域
    年轻代:大部分信件的对象都会先在年轻代中分配
    老年代:存储长时间存活的对象
2.程序计数器:现成私有的内存区域,主要存储当前线程执行的字节码指令的地址
3.方法区:用于存储类的结构信息,如类的成员变量、方法、构造函数等,方法区是堆的一个逻辑部分,但JVM规范允许它实现为堆的一个物理部分,也是现成共享的
4.虚拟机栈:线程私有的内存区域,用于存储局部变量表、操作数栈、动态链接、方法出口等信息
5.本地方法栈:线程私有的,为了使用本地方法(C/C++等语言编写的方法)的现成提供栈
6.Code Cache:用于存储JIT编译后的代码,属于JVM实现的一部分。
7.其他内存区域:JVM还需要额外的内存来执行基本任务,如GC、JIT编译等。

JVM内存结构中,年轻代(Young Generation)通常包括Eden区和两个Survivor区(通常称为From Survivor和To Survivor)。在默认情况下,Eden区和Survivor区的比例是8:1,这意味着Eden区占据了年轻代的大部分空间,而每个Survivor区则相对比较小
在JDK1.8中,可以通过以下JVM参数来调整这个比例:
-XX:SurvivorRatio=value
其中,value代表Eden区与一个Survivor区的比例,如果设置成8,表示Eden:Survivor = 8:1

14.JVM中为什么要分成新生代、老年代。新生代中为什么要分为Eden和Survivor

JVM内存这样区分主要是为了优化垃圾回收性能,以及更好的管理不同生命周期的对象
1.新生代和老年代:
    新生代用于存放新建的对象。由于大部分对象生命周期短暂,新生代经常被垃圾回收,这种回收称为minor GC
    老年代用于存放长时间存活的对象。老年代的空间比新生代大,发生垃圾回收的频率也低,但每次垃圾回收的时间更长,这种回收称为Major GC或者Full GC
    这样的分代可以提高垃圾回收的效率,因为不同年代的对象根据其生命周期的特点采用不同的垃圾回收算法。
2.新生代中的Eden和Survivor:
    Enden区是大多数新建对象的首选分配区域。当Eden区满时,会触发Minor GC
    Survivor区分为From Survivor和To Survivor,用于存放从Eden区存活下来的对象。降新生代进一步划分为Enden和Survivor有以下几个目的:
        减少内存碎片:通过复制算法,将存活的对象从一个Survivor区(from区)复制到另一个Survivor区(to区),可以减少内存碎片。
        提高内存利用率:Survivor区用于存放那些经过一次或者多次GC后任然存活的对象,他们可能会在未来的GC中被清理掉,因此不需要与老年代对象相同的存储空间
        简化对象年龄管理:每个对象都有一个年龄计数器,每次GC后存活的对象年龄都会增加,当对象的年龄达到一定阈值(可以通过参数-XX:MaxTenuringThreshold=<N>设置),它们会被晋级到老年代。

15.JVM中一次完整的GC流程是怎样的?对象如何晋升到老年代,说说你知道的集中主要的JVM参数

1.新生代GC(Minor GC):
    当Eden区的空间被占满时,会触发Minor GC
    存活的对象会被复制到Survivor区域(From或者To),非存活对象被清除
    每次Minor GC后,存活对象的年龄计数会增加1
2.对象晋升到老年代:
    对象在Survivor区域经历多次Minor GC后,如果其年龄达到阈值(可以通过参数-XX:MaxTenuringThreshold=<N>设置),则会晋升到老年代
    如果对象的大小超过直接晋升到老年代的阈值(-XX:PetenureSizeThreshold设置),该对象会直接被分配到老年代
主要的JVM参数:
   `-XX:MaxTenuringThreshold=<N>`:设置对象晋升到老年代的年龄阈值。
   `-XX:PetenureSizeThreshold`:设置直接晋升到老年代的对象大小阈值。
   `-XX:SurvivorRatio`:设置Eden区与Survivor区的比例。
   `-XX:+UseParallelGC`:使用Parallel Scavenge收集器进行新生代GC。
   `-XX:+UseParallelOldGC`:使用Parallel Old收集器进行老年代GC。
   `-XX:+UseG1GC`:使用G1垃圾收集器。
完整的GC流程还包括老年代的GC(Major GC或者Full GC),这通常发生在老年代空间不足的情况下,会使用不同的垃圾收集器(如Parallel或者CMS)进行回收。G1收集器作为一种混合收集器,可以再新生代和老年代之间进行灵活的回收   

16.存储过程的了解与使用

存储过程:作为可执行对象存放在数据库中的一个或多个SQL命令,通俗来讲,存储过程其实就是能完成一定操作的一组SQL语句。
优点:
    1.存储过程可封装,并隐藏复杂的商业逻辑
    2.存储过程可以回传值,并可以接收参数
    3.存储过程无法使用select指令来运行,因为它是子程序,与查看表,数据表或用户定义函数不同
    4.存储过程可以用在数据检验,强制实行商业逻辑等。
缺点:
    存储过程往往定制化于特定的数据库上,切换不同的数据库厂商,需要重写存储过程。存储过程的性能调校和撰写,受限于各种数据库系统
使用:
    1.创建存储过程保存在数据库的数据字典中
    2.查询所有存储过程状态
    3.查看对应数据库下所有存储过程状态
    4.mysql存储过程用call和过程名以及一个括号,括号里面根据需要,加入参数,参数包括输入参数、输出参数、输入输出参数调用。

17.消息队列的使用场景

1.异步处理:允许系统将非即时处理的任务放入消息队列中,从而实现异步处理。这样做可以快速返回处理结果,减少用户等待时间,提高系统的响应速度和吞吐量。例如,在订单下单流程中,将订单处理、邮件通知等操作异步化。
2.流量控制:在高并发场景下,如秒杀活动,消息队列可以起到削峰填谷的作用,平滑处理请求的峰值,保护下游系统不被过载请求击垮。
3.服务解耦:消息队列可以将上下游系统解耦,上游系统只负责生产消息,下游系统从消息队列中消费消息进行处理,两者之间不直接进行通信,降低了系统的耦合度。
4.发布/订阅模式:实现微服务间的消息广播,一个消息可以被多个服务订阅和处理。
5.流计算任务与数据连接:消息队列可以连接数据源和流计算任务,使得数据可以实时处理。
6.消息广播:将消息广播给大量的接收者,适用于需要将一条消息分发给多个系统的场景。
7.日志采集:在日志数据量大时,先将日志数据缓存到消息队列中,再由下游系统逐步处理,以避免直接压力过大导致系统崩溃。
8.延迟消息和优先级消息:根据特定的业务需求,使用消息队列的延迟投递或优先级处理功能。
在实际应用中,选择消息队列时需要考虑其性能(如高性能、高吞吐、低延时)以及是否支持上述特定需求。消息队列产品有RabbitMQ、Kafka、ActiveMQ、rocketMQ等等。

18.消息队列中常见问题及其解决方式

1.消息丢失:
    原因:消费位置处理不当、网络问题、系统故障等。
    解决方式:确保消息消费成功后再进行确认(RocketMQ中的ACK机制),使用事务消息,设置合理的重试策略,以及使用死信队来处理无法消费的消息。
2.消息消费积压:
    原因:消费能力不足、系统性能问题、代码逻辑问题等。
    解决方式:首先排查根本原因,比如优化消费逻辑,增加消费者数量(横向扩容),提高单个消费者的处理能力(纵向扩容),或者调整消息的消费模式(如批量消费)。
3.延迟问题:
    原因:消息队列本身的缓冲机制导致。
    解决方式:选择合适的消息队列产品,如RocketMQ支持延时消息,或者优化系统设计,减少不必要的消息传递。
4.系统复杂度增加:
    原因:消息队列引入了额外的组件和流程
    解决方式:通过合理的设计和架构,简化消息队列的使用,如使用消息队列的客户端库来简化开发,以及采用统一的消息队列管理策略。
5.数据不一致:
    原因:消息处理失败或重复处理。
    解决方式:使用幂等性设计来避免重复处理,确保消息处理的原子性,以及采用最终一致性原则。

19.消息队列中的消息丢失或重复发送问题如何解决?

1.消息丢失问题:
    生产端重试:在生产端设置重试机制,确保消息发送失败时可以进行多次尝试。
    消息队列配置集群模式:通过配置消息队列的集群模式,可以增强系统的可用性和容错性,减少消息丢失的可能性。
    消费端处理消费进度:消费端在确认消息处理完毕后再进行消息确认(ACK),以防止因消费端未处理完消息而导致的丢失。
    合理设置超时和重传策略:在消息发送和消费过程中,合理设置超时时间和重传策略,以应对瞬间网络问题。
2.重复消息问题:
    幂等性设计:在处理消息的业务逻辑中加入幂等性设计,确保即使同一条消息被处理多次,也不会影响最终的处理结果。
    去重机制:在消息队列或应用层面实现去重机制,比如使用消息的唯一标识符(Message ID)来过滤重复消息。
3.特殊情况处理:
    硬件故障导致的消息丢失:对于由于硬件故障(如服务器宕机)导致的消息丢失,可以通过备份、冗余系统或者依赖外部存储(如数据库)来记录消息状态,以便在故障恢复后进行补偿。
    重传失效问题:由于某些原因(如IM服务器宕机)导致重传机制失效,可以通过记录消息发送状态到外部存储,并在用户重连时检查这些状态,进行消息补偿。

20.消息的重发、补充策略

1.重试机制:
    重试次数和退避时间:当消息发送失败时,系统会根据配置的重试次数和退避时间进行重试,退避时间可以设置为1秒、10秒、1分钟等,以指数退避或固定时间间隔方式进行重试。
    退避策略:合理的退避策略可以提高充实的成功率。网络都懂通常在毫秒级,但在某些异常情况下可能会持续十几秒。因此,退避时间不宜设置过短(避免在问题未解决前耗尽次数)或过长(以免导致消息发送延迟或积压)。
2.去重机制:
    通过实现幂等性处理,确保消息被多次发送,接收方也能处理正确,不会产生副作用。
3.超时重传:
    如果在一定时间内没有收到消息的确认,发送方会触发超时重传机制,重新发送消息。
4.重试队列:
    当消息在超过重试次数后仍未能成功发送,可以将消息投递到专门的重试队列中,后续可以通过人工干预或补偿机制来处理。
5.补充策略:
    对于因服务器硬件故障等极端情况导致的消息丢失,可以通过以下方式补充:
        客户端状态同步:当用户重新连接时,服务器可以向客户端同步丢失的消息状态,让客户端知道有消息未收到。
        服务期间的消息复制:通过在多个服务器之间复制消息状态,即使一台服务器宕机,其他服务器也能接管消息发送。

21.消息队列中幂等性是如何设计的?

1.生产幂等:
    '通过消息唯一ID实现:'为每条消息分配一个全局唯一的标识符(如UUID),消息队列在存储消息时会检查这个唯一ID,如果已经存在相同的ID,则忽略新消息,保证消息不回被重复存储。
2.核心逻辑:
    满足单一生产者、同步发送、单一分区的条件,以确保消息在生产端的有序性和消费端的正确处理。
3.技术实现:
    在消息生产时,李勇消息队列提供的机制(如某些消息队列的幂等性生产者功能)来确保消息的幂等性。
    在消息消费时,确保消费者逻辑处理消息时也是幂等的,这通常需要消费者逻辑自身来保证,例如通过数据库的唯一约束来实现。
以下是生产者端使用唯一ID来设计幂等性的代码:
// 生成唯一消息ID
messageId = generateUniqueMessageId()

// 发送消息
while(!sendMessage(messageId, message)) {
    // 如果发送失败,可以充实,但通常会限制重试次数
    // 重试逻辑
}

// 发送消息的函数
private Boolean sendMessage(messageId, message) {
    // 检查是否已经存在
    if (messageQueue.exists(messageId)) {
        // 如果消息已存在,则不再发送,直接返回成功
        return true;
    }
   // 发送消息
   return messageQueue.send(messageId, message);
}

22.如何保证消息的有序性

1.队列层面保证:只有在队列上才能保证消息的严格顺序。如果业务需要全局严格顺序,可以将消息队列数配置为1,同时生产者和消费者也配置为一个实例。
2.局部有序:通常情况下,全局严格顺序不是必须的,可以通过以下方式实现局部有序:
    在发送端使用业务相关的Key(如账户ID),通过一致性哈希算法或者简单的队列数量取模方式,计算出队列的编号,确保相同Key的消息总是发送到同一队列上。
    使用支持单调自增序号的资源生产或分布式时间相关的ID生成服务来为每条消息确定时序基准。
3.服务端处理:即使有了时序基准,由于IM服务器差异和多线程处理,不能保证消息严格按照序号到达接收方。可以通过以下机制来保证:
    “服务端包内整流”机制,确保需要严格有序的批量消息正确执行。
    接收方根据消息序号进行本地整流,确保多接收方的最终一致性。
4.序号生成器的选择:在即时消息收发场景中,序号生成器不必须是全局递增的,因为对于点对点或群组聊天,只要保证每个接收者受到的消息顺序一致即可,不一定要全局单调递增。

23.用过哪些MQ,和其他mq比较有什么优缺点,MQ的连接是线程安全的吗

1.RocketMQ:适用于处理在线业务,如交易系统中的订单传递,具有低延时和金融级稳定性。
优点是易于上手和维护,缺点是在高并发和高性能要求的场景下可能不如其他MQ
在设计上是线程安全的,可以安全的被多个生产者和消费者线程同时使用
2.RabbitMQ:适合对消息队列功能和性能要求不高的场景,它是一个开箱即用且易于维护的产品。
优点是低延迟和高稳定性,缺点是相比RabbitMQ可能更复杂,需要更多的配置
在顺序消费模式的场景下通过加锁来保证线程安全,但整体线程安全性可能取决于具体的使用场景
3.Kafka:适合处理海量消息,如日志收集、监控信息,以及与大数据、流相关的场景。
优点是极高的吞吐量,适合海量数据处理,缺点是可能在消息顺序和事务方面不如其他的MQ
4.ZMQ(ZeroMQ):不需要考虑底层TCP/IP通信细节,异步、安全、完整地传递消息,适合处理大流量数据。
    ZMQ的线程数:可以通过增大线程数来提高并发能力,通常设置为4~6,具体需要通过性能测试确定。
    ZMQ的Hight Water Mark(HWM):设置接收和发送消息时的本地缓存两,防止ZMQ在数据过多时阻塞或丢失消息。
优点是简化网络通信,适合嵌入式使用,缺点是配置复杂,需要深入了解其工作模式和参数
ZMQ提供的代码实例显示,可以通过设置套接字选项来处理线程安全问题。ZMQ本身提供了线程安全的消息传递机制,但是使用时仍然需要注意合理配置HWM和其他参数避免线程安全问题
在多线程环境下,使用MQ时通常需要注意:
    确保消息的生产者和消费者是线程安全的
    避免多个线程同时访问一个连接或会话实例
    如果MQ客户端支持,使用线程专用的连接或会话实例

24.数据库表的设计注意事项有哪些?三大范式了解多少?

数据库设计的注意事项:
    1.字段的原子性:保证每列的原子性,不可分解,能用一个字段表达清楚的绝不使用第二个字段。
    2.主键设计:主键不要与业务逻辑有所关联,最好是毫无意义的一串独立不重复的数字(如UUID)。
    3.字段使用次数:对于频繁修改的字段(一般指状态类字段)最好用独立的数字或单个字母表示,不能用汉字或长字符的英文。
    4.字段的长度:建表的时候,字段长度尽量要比实际业务的字段大3-5个字段左右,最好是2的n次方幂值。当然,这个不同公司也有不同规范,在哪个公司就按照哪个公司的规范即可。
    5.关于外键:尽量不要建立外键,保证每个表的独立性。
    6.动静分离:最好做到动态表和静态表的分离。
    7.关于code的值:使用数字码或者字母代替实际的名字,也就是尽量把name转化为code
    8.关于null值:尽量不要有null值,有null值的话,数据库在进行索引的时候,查询的时间更久,从而浪费更多的时间!可以在建表的时候设置一个默认值!
    9.关于引擎选择:myisam的实际查询速度要比Innodb快,因为它不扫全表,但myisam不支持事务,没办法保证数据的一致性
三大范式:
    1.第一范式(1NF):确保每一列的原子性 数据表中的每一列都是最小的不可分割的单元
    2.第二范式(2NF):表中的记录是唯一的 表中的数据可以通过主键来区分
    3.第三范式(3NF):表中数据不要有冗余 在一个表中不要出现其他表中除了关键字段(主键)的其他字段,用外键关联

25.百万级的数据分页查询如何优化?

1.禁用跨页查询:为了避免查询时跳跃读取多个不连续的数据页,可以优化查询逻辑,减少跨页操作,这样可以提高查询效率。
2.平均分页法:通过数据进行预处理,使得数据在物理存储上平均分布,从而提高分页查询的效率。
3.二次查询法:
    第一次查询用于获取满足条件的主键集合(例如使用索引快速定位)。
    第二次查询根据第一次查询的结果获取完整的记录数据。
    例如:
    -- 第一次查询获取主键列表
    SELECT id FROM your_table WHERE conditions LIMIT page_size OFFSET page_offset;
    -- 第二次查询根据主键列表获取完整记录 其中 `list_of_ids` 是第一次查询得到的 ID 列表。
    SELECT 列1,列2,列3... FROM your_table WHERE id IN (list_of_ids);
4.中间表发:
    创建中间表,将复杂查询的结果集存储在中间表中,然后对中间表进行分页查询
    这种方法适用于复杂的联合查询,可以减少重复的计算和避免遍历大量数据
5.索引优化:
    创建适当索引,包含覆盖索引,以减少对数据表的访问
    定期维护索引,避免索引碎片化
6.查询优化:
    使用EXPLAIN或其他数据库提供的分析工具来分析查询计划,找出可能的性能瓶颈
    避免使用select *,只选择需要的列
7.硬件优化:
    根据需要增加内存,提高数据库缓存。
    使用更快的存储系统,如SSD。
8.分库分表:
    如果数据量实在太大,可以考虑分库分表策略,将数据分散到不同的表和数据库中。     

26.如何实现分库分表?

1.需求分析
    确定拆分的动机,比如单库单表的数据量过大,导致查询效率低下。
    分析业务场景,确定是垂直分表、垂直分库、水平分库还是水平分表
2.垂直分库:
    根据业务不同将不同的业务表拆分到不同的数据库中。
    每个数据库只包含特定业务的相关表。
3.垂直分表:
    根据字段的使用频率将一个大表拆分成两个或者多个表,例如将经常访问的字段(如条码、名称、价格)放在一个表中,不经常访问的字段(如规格、单位)放在另外一个表中
4.水平分库:
    将一个表中的数据按照某种规则分散到多个数据库中(比如按照年份甚至月份或者地区、用户类型等分库)
5.水平分表:
    将表中的数据按照某个规则分散到多个表中。常见的包括范围分表(按照时间范围)、哈希分表(如按照ID的哈希值)。
6.数据迁移:
    设计数据迁移方案,确保数据的一致性和完整性。
    可以采用双写(同时写入旧库和新库)或者停机迁移等方式。
7.应用调整:
    修改应用代码,确保应用能够根据分表的规则访问正确的数据库和表
8.测试验证:
    在迁移完成后,进行全面的测试,确保业务逻辑的正确性和性能满足要求。

-- 假设我们有一个用户表 user,我们根据用户ID的范围进行水平分表

-- 创建分表
CREATE TABLE user_0 (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    -- 其他字段
) ENGINE=InnoDB;

CREATE TABLE user_1 (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    -- 其他字段
) ENGINE=InnoDB;

-- 分表插入数据
INSERT INTO user_0 (id, name) VALUES (1, 'Alice');
INSERT INTO user_1 (id, name) VALUES (1001, 'Bob');

以上是一个简单的水平分表示例,实际操作中,分表更为复杂,需要考虑事务的一致性、数据迁移、数据访问层的抽象等多方面因素。此外还需要借助一些分库分表中间件,如MyCat、ShardingSphere等,来简化开发和维护工作     

27.数据库中的乐观锁和悲观锁的使用

1.悲观锁:顾名思义,就是很悲观,每次去拿数据的时候,都认为别人会修改,所以每次拿数据都会上锁,这样别人想拿这个锁就会block直到它拿到锁;行锁、表锁等,读锁、写锁等,都是在操作之前先上锁,用到了这种锁机制;

2.乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,在更新的时候回去判断一下此期间有没有人去更新这个数据,一般使用版本号或者时间戳来判断;

3.乐观锁和悲观锁的区别:乐观锁适用于多读少写的场景,这样可以提高吞吐量;悲观锁则和乐观锁相反适用于多写少读,经常产生冲突的场景;

28.#{}和${}的区别是什么

#{}是预编译处理,${}是字符串替换
mybatis在处理#{}时,会将SQL中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
mybatis在处理${}时,就是把${}替换成变量的值。
使用#{}可以有效的防止SQL注入,提高系统安全性。

29.数据库中字符串和日期的相互转换

Oracle:
    时间转字符串:to_char(date, format) select to_char(sysdata, 'YYYY-MM-DD')
    字符串转时间:to_date(str, format) select to_date('2024-09-05', 'YYYY-MM-DD')
MySQL:
    MySQL内置函数,在MySQL里面利用str_to_date()把字符串转为日期 select str_to_date('2024-09-05', '%Y-%m-%d')
    日期转字符串:select DATE_FORMAT(SYSDATE(),'%Y 年%m 月%d日')

30.MySQL有哪些存储引擎

InnoDB MyISAM Memory Merge Archive Federate CSV BLACKHOLE

31.事务隔离级别有哪些?MySQL和Oracle默认的隔离级别是什么?

1.读未提交(Read Uncommitted): 这是最低级别的隔离,解决了写写冲突(脏写),但可能出现脏读、不可重复读和幻读。
2.读已提交(Read committed):解决了写写冲突和写读冲突,避免了脏读,但仍然可能出现不可重复读和幻读
3.可重复读(Repeatable Read): 解决了写写、写读和读写冲突,避免了脏读和不可重复读,但任然可能出现幻读。在InnoD引擎中,默认使用这个隔离级别
4.串行化(Serializable):这是最高级别的隔离,解决了所有事物见的冲突,确保了事务的串行化执行,但可能会导致性能下降。
5.快照隔离(Snapshot Isolation):位于读已提交和串行化之间,其核心思想是在事务开始前给所有的数据打一个快照,事物之间不会互相干扰。通常使用多版本并发控制(MVCC)来实现快照隔离。
Oracle数据库只支持串行化级别和读已提交两种级别,默认级别是读已提交
MySQL数据库支持以上的隔离级别,默认隔离级别是可重复读,也就是上面写到的InnoDB引擎默认使用可重复读
补充:
    脏读:某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的
    幻读:在一个事务的两次查询中数据笔数不一致,例如一个事务查询了几列数据,而另外一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前没有的
    不可重复读:在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新原有的数据

32.如何查看SQL的执行计划

set statistics profile on
explain

33.为什么说 Mybatis 是半自动 ORM 映射工具?它与全自动 的区别在哪里?

Hibernate属于全自动ORM映射工具,使用hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。
MyBatis在查询关联对象或关联集合对象时,需要手动编写SQL来完成,所以称之为半自动ORM映射工具

34.常见的高并发场景有哪些,对应的架构设计方案是什么

1.  互联网平台:如社交网络、电商网站、在线游戏等,这些平台用户量大,交互频繁,容易产生高并发请求。
2.  金融服务:股票交易、支付系统等,需要处理大量的交易请求,对系统的实时性和并发处理能力要求极高。
3.  大规模数据处理:如搜索引擎的索引构建、日志处理等,需要处理海量的数据,并要求快速处理完成。

对应的架构设计方案通常包括以下几种:

-   Scale-out(横向扩展) :

    -   分布式服务:通过将系统拆分为多个微服务,部署在不同的服务器或容器上,提高系统的并发处理能力。
    -   数据库分片:包括一主多从、分库分表等,分散数据库的读写压力,提高数据库的并发处理能力。

-   缓存:

    -   内存缓存:使用Redis、Memcached等内存缓存技术,减少对后端数据库的访问,提高系统响应速度。
    -   CDN缓存:对于静态资源,使用内容分发网络(CDN)进行缓存,降低源服务器的负载。

-   异步处理:

    -   消息队列:通过异步消息队列如RabbitMQ、Kafka等,将请求异步处理,提高系统的吞吐量和响应速度。
    -   任务调度:使用异步任务和定时任务处理耗时的操作,避免阻塞主业务流程。

-   负载均衡:

    -   硬件负载均衡:如F5,或者软件负载均衡如Nginx,将请求分发到多个后端服务器,提高系统的整体处理能力。

-   数据库优化:

    -   读写分离:将数据库的读操作和写操作分开,分别由不同的服务器处理。
    -   索引优化:优化数据库索引,提高查询效率。

35.介绍完整的分布式中间件有哪些,各自的应用场景和作用。

分布式中间件是构建分布式系统的重要组成部分,它们帮助开发者处理系统间的通信、数据一致性、负载均衡、容错等问题。根据前面提供的内容,以下是几种常见的分布式中间件、它们的应用场景和作用:

1.  消息中间件:

    -   应用场景:处理高并发场景下的异步消息传递,如订单处理、日志收集、应用解耦等。
    -   作用:提高系统的吞吐量和响应速度,保证消息的可靠传递。
    -   常见产品:RocketMQ、Kafka。
    -   稳定性保障:通过消息确认、持久化、分布式架构等方式确保消息不丢失。

2.  定时调度中间件:

    -   应用场景:需要定时执行任务的业务场景,如数据统计、定时推送等。
    -   作用:提供任务调度和执行的能力,确保任务按时准确执行。
    -   常见产品:ElasticJob、XXL-Job。

3.  数据库中间件:

    -   应用场景:处理大规模数据的存储和查询,特别是在分布式数据库环境中。
    -   作用:提供数据分片、读写分离、负载均衡等功能,增强数据库的扩展性和可用性。
    -   常见产品:MyCat、ShardingSphere。

4.  缓存中间件:

    -   应用场景:减少数据库访问压力,提高系统读取速度。
    -   作用:临时存储频繁访问的数据,加快数据检索速度。
    -   常见产品:Redis、Memcached。

5.  搜索中间件:

    -   应用场景:全文检索、数据分析等需要快速数据检索的场景。
    -   作用:提供快速的数据搜索能力。
    -   常见产品:Elasticsearch。

1.  日志中间件:

    -   应用场景:记录系统运行状态,进行故障排查和性能分析。
    -   作用:收集、存储和分析日志数据。
    -   常见产品:ELK(Elasticsearch, Logstash, Kibana)栈。

以上是对分布式中间件的简要介绍。在实际应用中,根据业务需求和场景的不同,选择合适的中间件并合理配置是确保系统高性能、高可用和可扩展的关键。由于中间件的选择和配置涉及较多细节,需要结合具体场景进行深入分析和设计。