一.MySql篇
1优化相关问题
1.1.MySql中如何定位慢查询?
慢查询的概念:在MySQL中,慢查询是指执行时间超过一定阈值的SQL语句。这个阈值是由
long_query_time
参数设定的,它的默认值是10秒1。也就是说,如果一条SQL语句的执行时间超过了long_query_time
所设定的时间,那么这条SQL语句就被认为是慢查询。现在的问题就是:出现页面加载过慢,响应时间过长。我怎么确定就是我sql的问题呢?就算是sql的问题,我应该怎么找出执行慢的那条sql语句呢?
方案一:使用开发工具
以Skywalking为例。这个工具,可以直观看到每个接口的执行时间。还可以追踪进接口,详细的看耗时情况,其中也包含了sql的耗时。
方案二:使用MySql自带的慢日志
简单来说,这个日志记录了执行时间较长的sql。
总结:
1.2.索引相关概念
基础概念:
在MySQL中,索引是一种数据结构,用于加快数据库查询的速度和性能。索引可以帮助MySQL快速定位和访问表中的特定数据,就像书籍的索引一样,通过存储指向数据行的指针,可以快速查找到需要的页面。
底层数据结构 (B+树):
1.磁盘读写代价更低:非叶子节点不存放数据,只存储指针,相对来说存储压力低。数据只存储在叶子节点,在查询比对的时候,就不会把非叶子节点上的数据也加载出来了。
2.查询效率稳定:因为数据都存储在非叶子节点,在查询时都要从根节点开始对比。最终到叶子节点获取数据。
3.便于扫库和区间查询:叶子节点之间采用双向指针。在范围查询时更加方便。比如说:我们现在要查找索引为6~34区间的数据。先从根节点出发找到比38小的16,再从16往左找到叶子节点6,由于叶子节点之间有双向指针,因此6~34区间的数据都能获取!(这个区间内的数据不需要再从根节点再次查找)
总结:
1.什么是索引:
索引在项目中还是比较常见的,它是帮助MySQL 高效获取数据的数据结构,主要是用来提高数据检索的效率,降低数据库的IO 成本,同时通过索引列对数据进行排序,降低数据排序的成本,也能降低了CPU 的消耗。 2.索引底层的数据结构: MySQL的默认的存储引擎 InnoDB 采用的 B+ 树的数据结构来存储索引,选择B+ 树的主要的原因是:第一阶数更多,路径更短,第二个磁盘读写代价B+ 树更低,非叶子节点只存储指针,叶子阶段存储数据,第三是 B+ 树便于扫库和区间查询,叶子节点是一个双向链表。 3.B树和B+树的区别: 第一:在B 树中,非叶子节点和叶子节点都会存放数据,而 B+ 树的所有的数据都会出现在叶子节点,在查询的时候,B+ 树查找效率更加稳定。 第二:在进行范围查询的时候, B+ 树效率更高,因为 B+ 树都在叶子节点存储,并且叶子节点是一个双向链表。
1.3.聚簇索引和非聚簇索引
这里的主键id就是聚集索引,每个叶子节点存放的数据,就是当前叶子节点主键id所对应的一行数据!
假如我们这里把name字段也添加了索引,它就是非聚集索引 (二级索引)。它的叶子节点所存储的数据,就是当前name对应的主键id。比如说叶子节点Arm,存储的数据就是:10。
1.4回表查询
现在我们有一条sql语句:select * from user where name = 'Arm'
由于我们给name字段添加了索引。这里的执行顺序就是:
1.先去二级索引里面找Arm,先跟根节点Lee比对,A在L的左边,找到Geek,A在G的左边。找到叶子节点Arm,该叶子节点存储的数据是Arm的主键id:10。
2.因为这里我要获取Arm的所有信息 * 。因此,二级索引无法提供完整信息,我还要根据找到的主键值10,去聚集索引里面查询。
3.重复类似过程一,从聚集索引里面找到主键id = 10的叶子节点。该叶子节点存储的数据,就是id = 10 的所有信息!
这整个过程就是回表查询
总结:
1.聚集索引和非聚集索引
聚簇索引主要是指数据与索引放到一块,B+ 树的叶子节点保存了整 行数据,有且只有一个,一般情况下主键在作为聚簇索引的。 非聚簇索引值的是数据与索引分开存储,B+ 树的叶子节点保存对应的主键,可以有多个,一般我们自己定义的索引都是非聚簇索引 2.回表查询 回表查询跟聚簇索引和非聚簇索引是有关系的,回表的意思就是通过二级索引找到对应的主键值,然后再通过主键值找到聚集索引中所对应的整行数据,这个过程就是回表。
1.5.覆盖索引
MySql超大分页处理问题:
解决方案:
先根据id排序并从9000000条数据里面获取10条。这里根据id排序并返回id走的是覆盖索引。
再跟之前的表做关联,做一个等价查询。这样性能就有所提升了。
第一条sql是把9000010条数据全部提交给server层,然后再根据limit的第一个参数丢弃前9000000条数据,只要10条数据。
而第二条sql,是把9000010条id给server操作,相比来说就优化了。
总结:
1.覆盖索引:
覆盖索引是指select 查询语句使用了索引,在返回的列,必须在索引中全部能够找到,如果我们使用id 查询,它会直接走聚集索引查询,一次索引扫描,直接返回数据,性能高。 如果按照二级索引查询数据的时候,返回的列中没有创建索引,有可能会触发回表查询,尽量避免使用select * ,尽量在返回的列中都包含添加索引的字段。 2.MySql超大分页处理 超大分页一般都是在数据量比较大时,我们使用了limit 分页查询,并且需要对数据进行排序,这个时候效率就很低,我们可以采用覆盖索引和子查询来解决先分页查询数据的 id 字段,确定了 id 之后,再用子查询来过滤,只查询这个id列表中的数据就可以了因为查询id 的时候,走的覆盖索引,所以效率可以提升很多。
1.6.索引创建的原则
总结:
一般表中的数据要超过10万以上,我们才会创建索引,并且添加索引的字段是查询比较频繁的字段,一般也是像作为查询条件,排序字段或分组的字段这些。 还有就是,我们通常创建索引的时候都是使用复合索引来创建,一条sql 的返回值,尽量使用覆盖索引,如果字段的区分度不高的话,我们也会把它放在组合索引后面的字段。如果某一个字段的内容较长,我们会考虑使用前缀索引来使用,当然并不是所有的字段都要添加索引,这个索引的数量也要控制,因为添加索引也会导致新增改的速度变慢。
1.7.索引失效的情况
这是正常查询情况,满足最左前缀,先查有先度高的索引。
注意这里最后一种情况,这里和上面只查询 name = '小米科技' 的命中情况一样。说明索引部分丢失!
二.框架篇
1.Spring框架
1.1.Spring框架中的bean默认情况下是单例的吗?
Spring框架中的bean默认情况下是单例的。Spring中有一个注解@Scope,这是用来设置bean的情况。默认情况下设置为singleton(单例的)
1.2.Spring框架中的单例bean是线程安全的吗?
简单来说就是:判断当前成员变量能不能修改。如果是int这种可修改的类型,线程就不是安全的。
如果是,Service、DAO这些无状态类 (没有什么可变状态)就算是线程安全的。
总结:
不是线程安全的,是这样的: 当多用户同时请求一个服务时,容器会给每一个请求分配一个线程,这是多个线程会并发执行该请求对应的业务逻辑(成员方法),如果该处理逻辑中有对该单列状态的修改(体现为该单例的成员属性),则必须考虑线程同步问题。 Spring框架并没有对单例bean 进行任何多线程的封装处理。关于单例 bean的线程安全和并发问题需要开发者自行去搞定。 比如:我们通常在项目中使用的Spring bean都是不可可变的状态 (比如Service类和DAO 类 ) ,所以在某种程度上说 Spring 的单例 bean是线程安全的。如果你的bean有多种状态的话(比如 View Model对象),就需要自行保证线程安全。最浅显的解决办法就是将多态bean的作用由 “ singleton ”变更为“prototype”。
1.3.什么是AOP?项目中有没有使用AOP?
AOP的概念及常见使用场景: 总结: aop是面向切面编程,在 spring 中用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取公共模块复用,降低耦合,一般比如可以做为公共日志保存,事务处理等。 实际项目中使用AOP的例子: 在后台管理系统中,就是使用 aop 来记录了系统的操作日志 主要思路是这样的,使用 aop 中的环绕通知 + 切点表达式,这个表达式就是要找到要记录日志的方法,然后通过环绕通知的参数获取请求方法的参数,比如类信息、方法信息、注解、请求方式等,获取到这些参数以后,保存到数据库。
1.4.Spring中事务是如何实现的?
这里的"保存用户"方法,就是我们要加事务的方法。在该方法上添加@Transactional注解。
总结:
声明式事务管理实际上是基于Spring的AOP(面向切面编程)机制实现的。当你在方法上使用@Transactional注解时,Spring会在运行时创建一个代理对象,这个代理对象会在方法调用前后插入事务管理的代码,从而实现事务的自动管理。
具体来说,当Spring容器在运行时遇到带有@Transactional注解的方法时,它会创建一个代理对象来拦截这个方法。在代理对象拦截方法调用时,Spring会在方法调用前后分别插入开始事务和结束事务的代码。如果方法执行过程中抛出异常,Spring会根据异常类型决定是否回滚事务。
因此,虽然在使用声明式事务管理时,开发者不需要自己编写AOP代码,但是Spring仍然使用了AOP技术来实现事务管理。这也是为什么有时候我们会说声明式事务管理是基于AOP的。
1.5.Spring中事务失效的场景
这种情况下,自己把异常处理了,导致@Transactional没有发现异常,事务没有回滚。
Spring默认只会回滚非检查异常也就是runtime异常。而这里的FileNotFound是一个检查异常,并不能被捕获。
Spring为方法创建代理,添加事务通知,前提条件都是该方法是public修饰的!
标签:Java,索引,Spring,查询,面试,bean,相关,方法,节点 From: https://blog.csdn.net/qq_64064246/article/details/1367758261.6.Bean的生命周期
1.BeanDefinition
2.构造函数
在此期间,调用Bean的构造函数,实例化对象 (但是还未赋值!)
3.依赖注入
Spring容器会将Bean的属性设置为Bean定义中指定的值。这个过程也被称为依赖注入,因为Spring容器会自动寻找并注入Bean所依赖的其他Bean。
4.Aware接口
用于增强Bean的功能
如果Bean实现了以Aware结尾的接口,就要重写里面的方法。图中三个接口,就是分别用于在Bean的初始化过程中,用于获取:Bean的名字、Bean的工厂、容器的上下文对象(Spring容器本身)。
5.BeanPostProcessor#before
bean的后置处理器,在初始化方法调用之前执行。
6.初始化方法
1.InitalizingBean接口,实现了该接口,就要实现里面的方法。而在这一步就会执行重写方法。
2.自定义init:在bean中某个方法上标注了@PostConstruct注解。就会在这一步执行这个方法。
7.BeanPostProcessor#before
bean的后置处理器,在初始化方法调用之后执行。
当一个类的功能被增强了使用到了AOP,大概率就是使用后置处理器被增强的。
8.销毁Bean
如果在哪个方法上标注了@PreDestroy方法,Spring容器在关闭前就会调用该方法。
注:
1.Bean的创建和初始化是分开的,第2步是创建bean,3-7是初始化赋值
2.第5步和第7步的两个后置处理器。都是某个类实现了BeanPostProcessor接口,所重写的两个方法。分别在初始化方法前后执行。
测试代码:
第一个类User类,作为我们测试的Bean
import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @Component public class User implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean { public User() { System.out.println("1.User的构造方法执行了........."); } private String name; @Value("张三") public void setName(String name) { System.out.println("2.依赖注入执行了.........."); } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { System.out.println("3.BeanNameAware的实现方法执行了......."); } @Override public void setBeanName(String s) { System.out.println("3.BeanNameAware的实现方法执行了......."); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { System.out.println("3.ApplicationContextAware的实现方法执行了......."); } @PostConstruct public void init() { System.out.println("5.自定义初始化方法执行了.........."); } @Override public void afterPropertiesSet() throws Exception { System.out.println("5.InitializingBean的实现方法执行了.........."); } @PreDestroy public void destroy(){ System.out.println("7.destroy方法执行了........."); } }
第二个类:实现了BeanPostProcessor重写了其中方法的类。里面是两个后置处理器。
import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.stereotype.Component; @Component public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (beanName.equals("user")) { System.out.println("4.postProcessBeforeInitialization方法执行了 -> user对象初始化方法之前执行"); } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (beanName.equals("user")) { System.out.println("6.postProcessAfterInitialization方法执行了 -> user对象初始化方法之后执行"); } return bean; } }
执行结果
destroy方法要在容器关闭时执行
总结:
1.首先会通过一个非常重要的类,叫做BeanDefinition获取bean的定义信息, 这里面就封装了bean的所有信息,比如,类的全路径,是否是延迟加载,是否是单例等等这些信息。
2.在创建bean的时候,第一步是调用构造函数实例化bean
3.第二步是bean的依赖注入,比如一些set方法注入,像平时开发用的@Autowire都是这一步完成。
4.第三步是处理Aware接口,如果某一个bean实现了Aware接口就会重写方法执行。
5.第四步是bean的后置处理器BeanPostProcessor,这个是前置处理器。
6.第五步是初始化方法,比如实现了接口InitializingBean或者自定义了方init-method标签或@PostContruct。
7.第六步是执行了bean的后置处理器BeanPostProcessor,主要是对bean进行增强,有可能在这里产生代理对象。
8.最后一步是销毁bean。