整理了80道高频面试八股文(上篇)
哈喽大家好啊,我是Hydra。
上篇讲面试经验的文章最后,说过要整理一下最近的高频面试题,最终挑选了一些有针对性的问题后已经给大家整理好啦,有需要的小伙伴们可以先收藏一波,或者下载32页的PDF版本慢慢看,公众号后台回复【八股】直接获取PDF。
话不多说,看看这些问题你能答上多少吧~
synchronized底层实现原理,怎么做的锁升级
锁升级过程:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁。JDK15后,默认关闭了偏向锁。
对象头中MarkWord
标记字使用了三位表示锁的状态 :
001 无锁
101 偏向锁 -> 写入ThreadID成功持有偏向锁
10 轻量级锁 -> 交替获取的情况,不存在并发,CAS方式
11 重量级锁 -> 存在并发竞争,Monitor锁监视器对象
主要靠monitor
监视器锁实现,每个对象都有一个monitor
与之关联,当一个线程执行到monitorenter
时,就会获得对象所对应的 monitor
的所有权,并且计数器加1,也就获得到了对象的锁,执行到 monitorexit
时计数器减1,归0后释放锁被其他线程获取。
Synchronized用在静态方法和非静态方法的区别
静态方法锁的对象是class对象,非静态方法锁的对象是对象实例本身。
ReentrantLock可重入锁,怎么做的可重入?
可重入锁内部维护了一个int类型的state来计算重入次数,当一个线程尝试获取锁时,会先判断state是否为0:
- 如果是0表示锁没有被持有,可以获得锁
- 如果不为0,会判断当前持有锁的线程是不是自己,如果是的话将status+1,重入成功
Java中线程状态
不同于操作系统层面的五状态或七状态模型,Java定义了自己的线程状态。
五状态:新建、就绪、运行、等待、终止
七状态:新建、挂起就绪、就绪、运行、挂起等待、等待、终止
Java中线程的6种状态:
- new:创建态
- runnable:运行态+就绪态
- blocked:阻塞态
- waiting:等待(无限时)
- timed_waiting:限时等待
- terminated:终止态
线程池的参数有几个,分别是干嘛的
- corePoolSize:核心线程数,线程池维护的最少线程数量。刚创建线程池时为0,有任务来时就创建线程,直到到达corePoolSize这个值
- maximumPoolSize:最大线程数量。一个任务被提交到线程池后会先进入工作队列中,如果队列满了,会创建一个新线程,然后在队列中取出一个任务交给新线程处理,再将新提交的任务放入到工作队列中。线程池的最大线程数量由maximumPoolSize决定
- keepAliveTime:空闲线程最大存活时间,当前线程数量超过corePoolSize时超过这个时间才会被销毁
- unit:空闲线程存活的时间单位
- workQueue:工作队列,新任务被提交后会先进入到此工作队列中,任务调度时再从队列中取出
- threadFactory:线程工厂,创建新线程的时候使用的工厂,可以用来指定线程名,是否为daemon等
- RejectedExecutionHandler:拒绝策略,有以下四种
AbortPolicy
:丢弃任务并抛出RejectedExecutionException异常。DiscardPolicy
:丢弃任务,但是不抛出异常。可能导致无法发现系统的异常状态。DiscardOldestPolicy
:丢弃队列最前面的任务,然后重新提交被拒绝的任务。CallerRunsPolicy
:由调用线程处理该任务。
计算密集型:CPU核数 + 1。目标是提高CPU利用率,过多反而会增加切换线程成本。设置+1是因为偶尔在内存页失效或其他原因导致阻塞时,这个额外的线程可以顶上,保证CPU的利用率。
IO密集型:CPU核数 * 2。Netty源码中就是这么设计的。
阿里规约禁止使用Executors创建线程池,因为SingleThreadExecutor、FixedThreadPool内部都使用无界的LinkedBlockingQueue,会把任务一直添加到任务队列中,且线程数量不再增加,会产生OOM。
进程间通信方式
- 管道(pipe)及命名管道(named pipe):管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
- 信号(signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致的
- 消息队列(message queue):生产者与消费者之间的通信
- 共享内存(shared memory):可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。
- 信号量(semaphore):主要作为进程之间及同一种进程的不同线程之间得同步和互斥手段。(线程级别也可以!)
- 套接字(socket):这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。
Spring Bean的生命周期
- 扫描包,通过注解扫描找到所有的Bean,生成
BeanDefinition
- 在
doGetBean
方法中实例化Bean,通过构造方法反射实例化 -
populateBean
方法进行属性填充,遍历所有需要注入的属性,通过inject
方法反射注入 - 执行回调方法,例如
BeanNameAware
、BeanClassLoaderAware
、BeanFactoryAware
的方法 - 执行所有后置处理器的
postProcessBeforeInitialization
方法 - 执行bean生命周期回调中的
init-method
,若bean实现了InitializingBean
接口,执行afterPropertiesSet
方法 - 执行所有后置处理器的
postProcessAfterInitialization
方法 - 后续销毁时,如果实现了
DisposableBean
,执行destroy
方法 - 如果定义了
destroy-method
,调用destroy
方法
Mybatis 分页实现原理
常见的4种分页方式:
- sql语句分页(物理分页)
- 拦截器分页,通过拦截器给sql语句结尾添加limit(物理分页)
- RowBounds分页,查询出结果集放在List中,然后使用subList通过偏移量和限制数量截取(逻辑分页,吃内存,性能差)
- MyBatisPlus的
PageHelper
,走自定义的拦截器。先通过startPage将分页参数放进ThreadLocal中,然后count总条数并保存,然后添加limit
后执行分页查询,最终封装结果。
Mybatis有哪几种拦截器,插件机制怎么实现的
4种拦截器:
- Excutor:mybatis的内部执行器,作为调度核心负责调用StatementHandler操作数据库,并把结果集通过ResultSetHandler进行自动映射
- StatementHandler:封装了jdbc statement操作,是sql语法的构建器,负责和数据库进行交互执行sql语句
- ParameterHandler:处理sql参数,主要实现读取参数和对PreparedStatement的参数进行赋值
- ResultSetHandler:处理Statement执行完成后返回的结果集,通过它把ResultSet集合映射成对象
执行流程:
-
Executor
执行query()
方法,创建一个StatementHandler
对象 -
StatementHandler
调用ParameterHandler
对象的setParameters()
方法 -
StatementHandler
调用 Statement
对象的execute()
方法 -
StatementHandler
调用ResultSetHandler
对象的handleResultSets()
方法,返回最终结果
自定义的拦截器需要说明拦截哪个方法:
@Intercepts({
@Signature(type = Executor.class,method = "query", args = { MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class })
})
当编写完成我们自己的插件后,需要向mybatis中注册插件,有两种方式可以使用,第一种直接在SqlSessionFactory
中配置,另一种使用xml配置。
Mybatis是怎么通过spring,怎么注入到容器里面
扫描到Mapper接口后,借助spring的FactoryBean,为每一个Mapper生成一个FactoryBean对象,使用的是叫MapperFactoryBean的类。具体通过动态代理实现,通过Mapper接口的动态代理对象执行SQL语句。
Mybatis的xml,是怎么解析生成里面动态sql的
java中解析DOM基于XPath,MyBatis进行了进一步的封装,封装了一个XPathParser。
SpringBoot自动加载原理
@SpringBootApplication注解由三个注解组成:
- SpringBootConfiguration
- ComponentScan
- EnableAutoConfiguration
基于spring的spi扩展机制,@EnableAutoConfiguration
会读取META-INF/spring.factories
文件,决定加载哪些类。
在引入的Springboot Starter中,在这个文件中引入了@Configuration配置类:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.test.MyAutoConfiguration
配置类中的@Bean会被自动加载。
Springboot引入的jar包,如果不想启动怎么配置
扫描包时添加excludeFilters
:
@ComponentScan(value = "aa.bb", excludeFilters = {
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {MyService.class})})
注入值时,@Value("${ }")和@Value("#{ }")的区别
@Value("${}")
是从配置文件获取值,例如application.properties等。
@Value("#{}")
是Spring的SpEl表达式,可以有逻辑操作符,可以调用方法。
分布式锁和redis 红锁算法了解吗?
Redis
Redis实现分布式锁,主要是基于两个命令:
- setnx key value:key不存在才能写成功,key存在则失败。保证同一时刻只有一个线程能获取锁
- setex key timeout value:设置超时时间,保证锁会超期释放,避免长时间占用导致死锁
加锁过程需要保证原子操作,所以需要借助lua脚本,释放同理。这种写法问题是不能保证业务逻辑一定能执行完成,因为有超时时间存在。
zookeeper
zk实现分布式锁,主要是基于它的节点类型:
- 持久节点:客户端与zk断开后,节点不会自动删除,需要手动删除
- 持久顺序节点:在持久节点的基础上,对节点进行了顺序编号
- 临时节点:客户端与zk断开后,节点会自动删除
- 临时顺序节点:在临时节点的基础上,对节点进行了顺序编号
同时,zk有监听机制,可以监听节点创建、删除、数据发生变更、客户端连接状态变化等。
实现思路:使用zk顺序节点,只有兄弟节点中序号最小值能获取分布式锁。
释放锁:通过删除节点,使用临时节点,保证意外宕机引起锁未被释放情况。
监听:每个节点监听它前一个顺序节点,监听到删除事件后,判断自己的节点顺序是不是最小。
Redission
Redission,开箱即用,内置看门狗,可以不断延长锁的有效期。使用:
RLock lock = redissonClient.getLock("lock");
lock.lock();
……
lock.unlock();
RedLock红锁,针对多个Redis节点,多个节点可以是集群,也可以不是集群。当我们使用RedissonRedLock时,只要在大部分节点上加锁成功就算成功。
RLock lock1 = redissonClient1.getLock(resourceName);
RLock lock2 = redissonClient2.getLock(resourceName);
RLock lock3 = redissonClient3.getLock(resourceName);
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
分布式事务用过吗,seata 怎么用的
使用seata server 1.4
,并在项目中引入seata-spring-boot-starter
,常用的是AT模式和TCC模式。
使用数据库作为数据源,需要建表branch_table
, global_table
,lock_table
,业务数据库中建表undo_log
用于回滚。
配置数据库代理SqlSessionFactory
,AT模式直接在调用方法上添加@GlobalTransactional
,被调用方法上添加@Transactional
。
TCC模式下,需要先创建一个接口,指明三个阶段的方法:
@LocalTCC
public interface OrderTccAction {
@TwoPhaseBusinessAction(name="orderAction",commitMethod = "commit",rollbackMethod = "rollback")
boolean createOrder(BusinessActionContext businessActionContext,
@BusinessActionContextParameter(paramName = "order");
boolean commit(BusinessActionContext businessActionContext);
boolean rollback(BusinessActionContext businessActionContext);
}
实现这个接口,实现逻辑方法。在service中注入,调用的方法上添加@GlobalTransactional
@Service("orderTccService")
public class OrderTccServiceImpl implements OrderService{
@Autowired
OrderTccAction orderTccAction;
@Override
@GlobalTransactional
public String buy(){
Order order=new Order();
order.setOrderNumber(IdUtil.createSnowflake(1,1).nextIdStr())
.setMoney(100D);
boolean result = orderTccAction.createOrder(null, order);
// if (result){
// throw new RuntimeException("异常测试,准备rollBack");
// }
return "success";
}
}
seata 的原理,事务底层回滚怎么做的
AT模式下,RM在执行分支事务后,会首先解析sql语句,生成对应的UNDO_LOG
记录。UNDO_LOG
中记录了分支ID,全局事务ID,以及事务执行的undo和redo数据,以供二阶段恢复。
RM如果收到TM的回滚命令,会通过XID
和BranchID
查找到响应的UNDO_LOG
,将其中的后镜与当前数据比较,如果相同那么直接回滚,如果不同说明被当前全局事务之外的事务进行了修改,这种情况根据配置的策略来做处理。
分库分表怎么用的,什么时候该用
- 垂直分库:基于业务来分
- 水平分库:基于数据维度来拆分,不同库中的表是相同的
可以直接使用sharding-jdbc
,一般在单表数据超过500w行,或表容量超过2GB时使用,需要在application.yml中配置数据源及分表规则。
此外,也可以用在多租户的场景实现数据隔离。
用过哪些设计模式
- 策略模式:spring中同类型的多个Bean通过集合注入方式,简化
if/else
- 门面模式:日志门面Sl4j,实际使用桥接的log4j、jcl或jdk的日志工具
- 模板方法模式:发送短信时可以定义一个模板,不同服务商例如Aliyun、Tencent都实现这个抽象类
- 单例模式:系统中定义一个统一的雪花算法发号器,spring的singleton就是单例模式
- 代理模式:常用动态代理(jdk或cglib)、静态代理
- 观察者模式(发布/订阅模式):消息队列,Spring用
ApplicationListener
监听自定义的ApplicationEvent
,使用ApplicationEventPublisher
发布事件
抽象工厂模式,观察者模式具体实现
抽象工厂模式
抽象工厂模式是一种创建型设计模式,它提供了一种方法来创建一组相关或依赖的对象,而无需指定他们的具体类。
抽象工厂模式,也称为工厂的工厂,有一个工厂创建其他工厂。当使用抽象工厂模式时,我们首先使用超级工厂创建工厂,然后使用创建的工厂创建对象。
在 Spring Boot 开发中,抽象工厂模式可以通过使用 @Autowired 和 @Qualifier 注解来实现。
抽象工厂模式的各个角色和工厂方法的如出一辙:
- 抽象工厂角色:这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的 父类。在java中它由抽象类或者接口来实现
- 具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。在java中它 由具体的类来实现
- 抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现
- 具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现
在以下情况下可以考虑使用抽象工厂模式:
- 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是很重要的,用户无须关心对象的创建过程,将对象的创建和使用解耦。
- 系统中有多于一个的产品族,而每次只使用其中某一产品族。可以通过配置文件等方式来使得用户可以动态改变产品族,也可以很方便地增加新的产品族。
- 属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。同一个产品族中的产品可以是没有任何关系的对象,但是它们都具有一些共同的约束,如同一操作系统下的按钮和文本框,按钮与文本框之间没有直接关系,但它们都是属于某一操作系统的,此时具有一个共同的约束条件:操作系统的类型。
- 产品等级结构稳定,设计完成之后,不会向系统中增加新的产品等级结构或者删除已有的产品等级结构。
观察者模式
观察者模式是一种行为型设计模式,它定义了一种一对多的依赖关系,当一个对象的状态发生改变时,其所有依赖者都会收到通知并自动更新。
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。
优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
使用场景:
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
- 一个对象必须通知其他对象,而并不知道这些对象是谁。
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
观察者模式包含以下几个核心角色:
- 主题(Subject):也称为被观察者或可观察者,它是具有状态的对象,并维护着一个观察者列表。主题提供了添加、删除和通知观察者的方法。
- 观察者(Observer):观察者是接收主题通知的对象。观察者需要实现一个更新方法,当收到主题的通知时,调用该方法进行更新操作。
- 具体主题(Concrete Subject):具体主题是主题的具体实现类。它维护着观察者列表,并在状态发生改变时通知观察者。
- 具体观察者(Concrete Observer):具体观察者是观察者的具体实现类。它实现了更新方法,定义了在收到主题通知时需要执行的具体操作。
鉴权怎么做
使用Spring Security+JWT实现认证和鉴权。继承WebSecurityConfigurerAdapter
类,实现configure
方法先做基本配置。
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
DBUserDetailsService dbUserDetailsService;
//...
}
实现UserDetailsService
接口,在loadUserByUsername
方法中实现用户认证,以及权限的填充。
在做菜单权限分配时,可以在接口添加@PreAuthorize
注解。
注册中心用的什么
Eureka
- 只支持AP
- 定时发送心跳,属于短连接
- client默认30秒向server发送一次心跳,90秒未收到心跳会从注册表中剔除服务,server有60秒清除间隔
- 当在短时间内,统计续约失败的比例,如果达到一定阈值,则会触发自我保护的机制,在该机制下,Eureka Server不会剔除任何的微服务,等到正常后,再退出自我保护机制
Nacos
- 支持CP或AP
- 使用netty和服务直连,长连接
- client每5秒上报心跳给注册中心,15秒未收到心跳将实例设为不健康状态,可以正常接收请求,超过30秒将nacos实例删除,不再接收请求
- 当域名健康实例 (Instance) 占总服务实例(Instance) 的比例小于阈值时,无论实例 (Instance) 是否健康,都会将这个实例 (Instance) 返回给客户端。这样做虽然损失了一部分流量,但是保证了集群的剩余健康实例 (Instance) 能正常工作。
怎么处理宕机问题
集群化部署+负载均衡,使用nginx负载和gateway网关负载。
几千万黑名单数据,怎么处理
使用布隆过滤器,具体可以使用guava,或Redis的BitMap
集群部署和分布式部署区别
- 集群:实现统一业务(是为了提高效率或高可用)
- 分布式:将不同业务分布在不同地方
流水线部署,需要哪些技术
git+jenkins+docker
- 源码关联git,可以指定分支
- push源码时,触发webhook,需要配secret token
- 调用maven构建, clean package -Dmaven.test.skip=true -U
- 执行shell脚本,ftp上传jar包,用dockerfile打包镜像,更新镜像
- 可以调用自定义接口,例如发送构建完成通知等操作
消息队列区别
方向 | 概要 |
吞吐量 | 万级的ActiveMQ和RabbitMQ的吞吐量,要比十万级甚至百万级的RocketMQ和Kafka低一个数量级 |
可用性 | 都可以实现高可用。ActiveMQ 和 RabbitMQ 都是基于主从架构实现高可用性。RocketMQ基于分布式架构。Kafka也是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 |
时效性 | RabbitMQ基于Erlang开发,所以并发能力很强,性能极其好,延时很低,能达到微秒级,其他几个都是毫秒级 |
功能支持 | Pulsar 的功能更全面,支持多租户、多种消费模式和持久性模式等功能,是下一代云原生分布式消息流平台。 |
消息丢失 | ActiveMQ 和 RabbitMQ 丢失的可能性非常低, Kafka、RocketMQ 和 Pulsar 理论上可以做到 0 丢失。 |
特点:
- ActiveMQ 的社区比较成熟,但性能比较差,而且版本迭代很慢,不推荐使用。
- RabbitMQ 在吞吐量方面稍逊于 Kafka、RocketMQ 和 Pulsar,但是由于它基于 Erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。但是也因为 RabbitMQ 基于 Erlang 开发,所以国内很少有公司有实力做 Erlang 源码级别的研究和定制。如果业务场景对并发量要求不是太高(十万级、百万级),那这几种消息队列中,RabbitMQ 或许是你的首选。
- RocketMQ 和 Pulsar 支持强一致性,对消息一致性要求比较高的场景可以使用。RocketMQ 阿里出品,Java 系开源项目,源代码可以直接阅读,然后可以定制自己的 MQ,并且 RocketMQ 有阿里巴巴的实际业务场景的实战考验。
- Kafka 的特点很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时 Kafka 最好是支撑较少的 topic 数量即可,保证其超高吞吐量。Kafka 唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略。天然适合大数据实时计算以及日志收集。如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,几乎是全世界这个领域的事实性规范。
RocketMQ单条消息的大小限制
4MB
Kafka默认存储几天,要长时间存储怎么办
默认情况下,Kafka数据保留时间是7天,如果一个主题在7天内没有被消费者消费,那么这些数据将被丢弃。在某些情况下,用户可能需要改变Kafka数据默认保留的时间,以适应实际情况。
消息队列阻塞时怎么处理?
- 增加消费者:加快消息的处理速度,可以提高系统的吞吐量。但同时也会增加系统负担,因此需要根据实际情况决定增加消费者的数量
- 调整消息队列的配置:消息队列的配置也会影响消息的处理速度。例如可以调整消息队列的缓存大小、消息的最大大小、消息的过期时间等参数来优化队列性能。
- 使用多个消息队列来分散消息的负载,从而提高系统的吞吐量
- 使用消息队列监控工具,例如Kafka Manager、 RabbitMQ Management 监控消息队列的状态,及时发现积压的消息
- 优化消息的处理逻辑:例如可以使用批量处理来减少消息的处理次数,使用异步来提高消息的处理速度,使用消息过滤器来过滤掉不需要的消息
- 设置消息过期时间ttl,过期后投递到死信队列,之后对死信队列的消息进行补偿
订单支付的性能瓶颈
订单大量同时提交,可能会到达业务处理瓶颈,主要问题:高可用、一致性、高性能。
- 高性能:高读和高写情况下,针对读进行“少读”,针对写进行数据拆分。动静分离、热点优化、服务端性能优化
- 一致性:商品库存削减
- 高可用:面对业务流量激增、依赖服务的不稳定、应用自身瓶颈、物理资源损坏问题
解决方案:异步解耦方式,可以先创建支付工单,然后由消息队列削峰进行支付解耦,完成后续的支付。
设计表的三范式
- 第一范式:属性不可分割
- 第二范式:非主属性完全依赖主属性
- 第三范式:不存在传递依赖
Mysql执行计划
使用EXPLAIN
关键字模拟优化器执行SQL,从而知道MySQL是如何处理SQL语句的,达到SQL语句优化的目的。
先说说整个查询过程:
- 客户端向Mysql服务器发送一条查询请求
- 服务器先检查查询缓存,如果命中缓存,立即返回,否则进入下一阶段
- 服务器进行SQL解析、预处理、再由优化器生成对应的执行计划
- mysql根据执行计划,调用存储引擎的api来执行查询
- 将结果返回给客户端,同时缓存查询结果
再看重要指标:
ID
插叙执行顺序,先执行大的值的,相同的话从上向下执行,相同被视为一组
select_type
- SIMPLE:没有子查询,关联查询,union
- PRIMARY:主查询(查询中包含任何复杂的子部分,最外层的被标记为primary)
- SUBQUERY:内层查询,在 select 或 where 列表中包含了子查询
- DERIVED:在 from 的列表中包含的子查询被标记成 derived。派生查询或衍生查询 ,用到内部的临时表的结果时候
- UNION:用union关联,两个 select 查询时前一个标记为 PRIMARY,后一个标记为 UNION
- UNIONRESULT:从 union 表获取结果的 select 被标记成 union result
type
全名join types ,连接类型
- const:对于主键索引或唯一索引,查询到只有一行结果的情况,就是const。是性能非常好的查询。
- system:是const的一种特例,查询系统表只有一行数据时
- eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描
- ref:非唯一性索引扫描,返回匹配某个单独值得所有行。索引要和某个值相比较,可能会找到多个符合条件的行
- range:范围查询大于,小于,between and,in 都属于范围查询。前提是查询的字段要加索引,看到range就表明已经使用了索引
- index:查询索引上面全部的数据
- all:没有用到索引,或没有索引,进行全表扫描
性能:system>const>eq_ref>ref > range >index >all,优化至少要到达ref或range级别!
possible_key
可能用到的索引
key
实际用到的索引
key_len
根据使用的索引计算索引长度,与定义长度和类型有关
ref
使用哪个列,或哪一个常数与索引一起过滤数据
Extra字段
MYSQL 如何解析查询的额外信息。
- Using where:从存储引擎拿到的数据不是全部满足条件,到server层进一步过滤
- Using index:覆盖索引,查询的列正好包含在查询条件用到的索引里面,直接从索引返回数据,不用回表
- Using index condition:索引下推,server层把条件下推到存储引擎
- Using filesort:不能基于索引来排序,用到额外的其他排序
- Using temporary:用到临时表。对没有索引的表执行distinct,或对没有索引的列用group by,或用到join关联查询对任意列使用group by,就会出现这个
Mysql存储引擎
InnoDB
- 5.7版本默认存储引擎
- 支持事务和外键,数据完整性和一致性更高
- 支持行锁和表锁
- 支持读和写的并发操作,写的操作不阻塞其他事务的读操作
- 存储的文件类型是
ibd
和frm
MyISAM
- 支持表级别的锁,插入和删除会锁表
- 不支持事务
- 拥有比较高的插入和查询速度,所以如果要插入100w条数据,可以先用myISAM,插完改成innoDB
- 执行count会更快,因为存储了行数,所以是O(1)
其他
- Memory:数据只存在内存中,读写快,重启后数据丢失,只能做临时表
- CSV:逗号分隔,不允许有空行,不支持索引
- Archive:归档,数据存储很紧凑,没有索引,用于存储和检索大量的很少更新的历史数据
- NDB:另外一种支持事务的存储引擎
Mysql5.7和8.0的区别
- MySQL8.0引入了更快和更高效的编码器和解码器,它们可以通过协议来进行通信,并且支持压缩、加密、并发等方面的优化。MySQL5.7的编码器和解码器比较慢
- 认证方式不同,MySQL8.0比MySQL5.7的登录认证插件更安全。MySQL 8.0默认使用caching_sha2_password作为登录认证插件,而MySQL 5.7则默认使用mysql_native_password。前者是更安全的选项,因为它支持SHA-256密钥散列(salted)密码,后者则只支持单向的MD5散列密码。
- MySQL 8.0引入了更多窗口函数,可以进行各种分析操作,如排序、排名、移动平均值等,这使得查询操作变得更加高效和灵活。而MySQL5.7这方面没有改进。
- MySQL8.0在性能和稳定性方面比MySQL 5.7更安全高效
- 其他,如JSON数据类型、InnoDB替代方案、Atomic DDL语句、多索引扫描、表级别的DDL操作、非持久TEMPTABLE等等
Mysql为什么要用B+树做索引
- 查询效率高:查询时间复杂度是log(n)级别的,相对于链表和二叉树的O(n),性能要高的多
- 支持范围查询:可以用于
between
和in
运算符 - 支持索引和排序:可以用来实现索引,并且在查询的时候能够保证结果的有序性
- 支持动态扩展:可以动态的向两侧扩展,因此即使在数据量很大的情况下也能保证性能
- 占用空间少:占用的空间比二叉树少很多
联合索引
可以同时包含多个列,是建立在多个列上的索引,数据结构仍然是B+ Tree。
一棵B+树只能依靠一个值来构建,所以联合索引使用最左的字段来构建B+树。
联合索引 B+ Tree的 data部分存储的是联合索引所在行的主键值,也就是说需要回表。
最左匹配原则(最左前缀原则):
最左优先,以最左边为起点,任何连续的索引都能匹配上,但遇到范围查询(>,<,between,like)就会停止匹配。
创建一个 index(a,b,c) 的联合索引,就相当于建立了三个索引
- index(a)
- index(a,b)
- index(a,b,c)
如何考虑在某个字段上是否建立索引
- 选择区分度高的列建立索引,例如性别就不适合(只有两个值,区分度低)
- 经常与别的表进行连接的表,在连接字段上应该建立索引
- 经常出现在where子句中的字段,特别是大表的字段,应该建立索引
- 频繁进行数据操作的表,不要建立太多索引,会引起B+树变化
- 删除无用索引,避免对执行计划造成影响
如何避免索引失效
- 遵循最左匹配原则
- 范围查询右边的列,不走索引(
select * from user where name = 'kaka' and age > 11 and sex = 1
,name
和age
走索引,sex
失效) - 不要在索引上进行运算操作,计算、函数、类型转换都会引起失效
- 字符串不加单引号,造成索引失效。(两边类型不等)
- 以
%
开头的like模糊查询 - 如果mysql评估使用索引比全表扫描更慢,则不使用索引
- in走索引,not in索引失效
- is null,is not null 有时可能引起索引失效
建表时 NULL和NOT NULL 如何考虑?
NULL值是一种对列的特殊约束,在创建一个新列时,如果没有明确的使用关键字not null
声明该数据列,Mysql会默认的为我们添加上NULL约束。有些开发人员在创建数据表时,由于懒惰直接使用 Mysql 的默认推荐设置,而这很容易在使用NULL的场景中得出不确定的查询结果以及引起数据库性能的下降。
- 列中使用
NULL
值容易引发不受控制的事情发生,有时候还会严重托慢系统的性能 - 对含有 NULL 值的列进行统计计算,如
count()
,max()
,min()
,结果并不符合我们的期望值 - 干扰排序,分组,去重结果
- 有的时候为了消除
NULL
带来的技术债务,需要在 SQL 中使用IFNULL()
来确保结果可控,但是这使程序变得复杂 -
NULL
值并是占用原有的字段空间存储,而是额外申请一个字节去标注,这个字段添加了NULL
约束(就像额外的标志位一样) - 根据以上缺点,不推荐在列中设置 NULL 作为列的默认值,可以使用
NOT NULL
消除默认设,使用0
或者''
空字符串来代替NULL
文章转载自公众号:码农参上