石门县建设局网站,制作企业推广网站,做网站的结论与心得,企业所得税最新政策一.MySql篇 1优化相关问题 1.1.MySql中如何定位慢查询#xff1f; 慢查询的概念#xff1a;在MySQL中#xff0c;慢查询是指执行时间超过一定阈值的SQL语句。这个阈值是由long_query_time参数设定的#xff0c;它的默认值是10秒1。也就是说#xff0c;如果一条SQL语句的执… 一.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的左边找到GeekA在G的左边。找到叶子节点Arm该叶子节点存储的数据是Arm的主键id10。 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.索引失效的情况 这是正常查询情况满足最左前缀先查有先度高的索引。 1. 注意这里最后一种情况这里和上面只查询 name 小米科技 的命中情况一样。说明索引部分丢失 2. 这里第二条sql中的status 1 就是范围查询 3. 这里sql中对name索引进行了截取导致索引失效。 4. 发送类型转换导致索引失效。 5. 1.8.sql的优化经验 这里解释一下SQL语句的优化的原理 1.指明字段名称可以尽量使用覆盖索引避免回表查询因此可以提高效率 2.字面意思无需过多赘述。索引就是为了提高查询效率的。 3.图中两条sql直接可以使用union all 或者 union连接。使用union all 的话就会把两条sql所有符合条件的查询结果直接合并。使用union的话就会多一步过滤过滤掉二者的重复项。效率相对较低。 4.避免在where子句使用对字段进行表达式操作这也是因为可能会造成索引失效。如1.7中索引失效的第三种情况 5.如下图这里有两个循环。循环三次的可以看作小表循环1000次的看作大表。 小表放外面MySql只需要连接三次数据库然后在里面执行1000次操作。 而把大表放外面就相当于要连接1000次数据库每次执行三次操作。 而使用内连接就会优先把小表放外面大表放里面。而左右连接并不会改变顺序因为编写sql的时候就已经确定了两个表的位置了。因此优先使用内连接。 当有写操作进来的时候就会走Master数据库进行写操作然后Master数据库再把数据同步到Slave数据库。 当有读操作进来的时候就直接走Slave进行查询。 从而实现读写分离。 二.框架篇 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修饰的 1.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步是创建bean3-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.依赖注入执行了..........);}Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {System.out.println(3.BeanNameAware的实现方法执行了.......);}Overridepublic void setBeanName(String s) {System.out.println(3.BeanNameAware的实现方法执行了.......);}Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {System.out.println(3.ApplicationContextAware的实现方法执行了.......);}PostConstructpublic void init() {System.out.println(5.自定义初始化方法执行了..........);}Overridepublic void afterPropertiesSet() throws Exception {System.out.println(5.InitializingBean的实现方法执行了..........);}PreDestroypublic 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 {Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {if (beanName.equals(user)) {System.out.println(4.postProcessBeforeInitialization方法执行了 - user对象初始化方法之前执行);}return bean;}Overridepublic 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。 1.7.Spring中bean的循环引用 在Spring中bean的生命周期中的构造方法执行时只是把bean对象创建出来了。但还未进行初始化。初始化的第一步就是进行依赖注入对当前bean所需要的bean进行注入。这个时候如果出现A依赖注入BB就会去创建并初始化。但如果此时出现了B初始化时也需要A的实例。两个半成品Bean就会死循环谁都创建不出来 解决循环依赖的方法 注这里的一级缓存中存放的是单例的Bean。对于多例Bean则是在获取Bean的时候才会被创建并不会提前被创建存放在一级缓存中。 三级缓存解决循环依赖图解 注意这里三级缓存相较于二级缓存的有点就是三级缓存中的ObjectFactory既可以生成A(原始对象)也可以生成A的代理对象。而二级缓存是没法把代理对象放入二级缓存中。 而在这个过程中我们ObjectFactory产生的B的半成品Bean和A的代理对象半成品Bean都会放在二级缓存中之后往A中注入B从而产生完整的A就是从二级缓存中拿B的半成品Bean。因为如果多次调用ObjectFactory就可能产生多例的情况 总的来说要解决循环依赖问题1、2、3级缓存需要相互配合都必不可少 三级缓存能解决的是出现在Bean初始化时的循环依赖问题但如果是在构造方法时出现的循环依赖就要我们手动解决了。如 当Bean被注入到另一个Bean中时Spring并不是直接将这个Bean实例化并注入到另一个Bean中而是使用一个代理对象来代替实际的Bean。在这里就是创建A实例的时候Spring先创建一个B的代理对象用于A的构造方法初始化。从而解决了循环依赖问题。 总结 1.循环依赖 循环依赖其实就是循环引用 , 也就是两个或两个以上的 bean 互相持 有对方 , 最终形成闭环。比如 A 依赖于 B,B 依赖于 A循环依赖在spring 中是允许存在 spring 框架依据三级缓存已经解决了大部分的循环依赖 ① 一级缓存单例池缓存已经经历了完整的生命周期已经初始化完成的 bean 对象 ② 二级缓存缓存早期的 bean 对象生命周期还没走完 ③ 三级缓存缓存的是 ObjectFactory 表示对象工厂用来创建某个对象的 2.三级缓存解决循环依赖的具体流程 第一先实例 A 对象同时会创建 ObjectFactory 对象存入三级缓存singletonFactories 第二 A 在初始化的时候需要 B 对象这个走 B 的创建的逻辑 第三 B 实例化完成也会创建 ObjectFactory 对象存入三级缓存singletonFactories 第四 B 需要注入 A 通过三级缓存中获取 ObjectFactory 来生成一个 A 的对象同时存入二级缓存这个是有两种情况一个是可能是A 的普通对象另外一个是A 的代理对象都可以让 ObjectFactory 来生产对应的对象这也是三级缓存的关键 第五 B 通过从通过二级缓存 earlySingletonObjects 获得到 A 的对象后可以正常注入B 创建成功存入一级缓存 singletonObjects 第六回到 A 对象初始化因为 B 对象已经创建完成则可以直接注入 B A创建成功存入一次缓存singletonObjects 第七二级缓存中的临时对象 A 清除 1.8.SpringMVC的执行流程介绍核心组件 SpringMVC核心组件的功能 DispatcherServlet前端控制器 (调度中心)用于接收HTTP请求并将其分派给相应的处理器。DispatcherServlet是整个请求处理的流程控制中心它降低了组件之间的耦合性。HandlerMapping用于确定HTTP请求需要被哪个处理器处理。HandlerMapping根据请求URL查找相应的处理器就是Controller层对应路径的方法。这个save方法就是一个处理器。HandlerAdapter1.用于调用处理器(Handler比如说这里的save方法)并处理HTTP请求。2.处理Handler中的参数和返回值。HandlerAdapter会调用处理器并返回一个ModelAndView对象其中model是一个Map结构存放了返回的所有数据view是逻辑视图名即ViewName。ViewResolver用于确定响应结果的视图。ViewResolver根据ModelAndView对象中的逻辑视图名解析出具体的视图文件再生成对应的View视图对象。 如果是基于接口开发很多返回值是json格式并没有ModelAndView对象。 处理器中某个方法添加了ResponseBody注解这个注解会把当前方法的返回值转化为json格式并响应给前端。