为了满足hashmap集合的不重复存储,为什么要重写hashcode和equals方法?
首先理解一下hashmap的插入元素的前提:
hashmap会根据元素的hashcode取模进行比较,当hashcode相等时,会再次去比较元素之间的内容值,当内容值也相等时就代表元素重复。
所以当元素与元素之间的hashcode值与内容值相同时,hashmap就会认为元素重复
重写hashCode是为了让两个具有相同值的对象的Hash值相等。
重写equals方法是为了比较两个不同对象的值是否相等。
同时重写hashCode()与equals()是为了满足HashSet、HashMap等此类集合的相同对象的不重复存储。
当前有两个不同对象,他们的内容是相同的,但是在hashmap看来他们就是重复的,所以我们重写hashcode方法,保证相同值的两个对象的hashcode相同,重写equals方式是比较两个不同对象的值是否相同。
== 与 equals的区别

默认情况下 equals方法也是比较的两个对象之间的内存地址是否相同,但是我们可以重写equals方法达到不同的效果,如String类就重写equals方法,String类的equals方法会先去比较两个对象的内存是否相同,相同就返回true,如果不相同也不会立马放回false,而是会再次比较两个String对象的数值是否相同。
“中断”状态,并没有真正的停止这个线程;需要线程自己去监视(interrupted、isinterrupted)线程的状态为并做处理通常与interrupted()、isinterrupted()配合使用,从而达到停止一个线程。
interrupted()、isinterrupted()都是监视当前线程的中断状态,当这两个方法返回的中断状态为true,可以使用return或者抛出异常来结束线程方法。
代码示例如下:
public class IsinterruptedTest {
public static void main(String[] args) {
Runnable runnable = () ->{
Thread thisThread = Thread.currentThread();
int num =0;
while (true){
// 检查当前中断标志是否为true
if (thisThread.isInterrupted()){
System.out.println("当前线程任务已被中断....");
return;
}
System.out.println(num++);
}
};
Thread thread =new Thread(runnable);
// 启动线程
thread.start();
// 让主线程休眠2ms,之后再去中断子线程
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 中断子线程
thread.interrupt();
}
}
立即终止一个线程,并立刻释放被它锁住的所有对象的锁。当线程执行到一般时突然被终止后,可能会导致资源没有被正确释放,也会导致数据损坏、数据不一致的问题。如一个线程任务中做了两件事(增加订单、减少库存),而这时线程执行一半就被强制终止了,就导致订单增加了,库存却没有减少,出现数据不一致问题。通过stop()终止线程,finally代码块中的代码也不会被执行;finally代码块通常用于资源的释放或者清理操作,所以会导致资源没有被正确释放。suspend():作用是挂起/暂停某个线程直到调用resume方法来恢复该线程,但是调用了suspend方法后并不会释放线程获取到的锁,容易造成死锁。
sleep方法:放弃cpu使用权,使当前线程暂停一段时间,当前暂停时间结束后,会重新进入就绪状态并与其它线程等待cpu调度。sleep()会释放cpu资源,但是不会释放同步锁(类锁、对象锁)
yield方法:与sleep方法相似,暂停线程,放弃cpu使用权,并马上进入就绪状态,等待cpu调度。不会释放同步锁(类锁、同步锁);需要注意的是:yield方法可能会不起作用,因为cpu调度是不可控制,我们无法控制cpu去调用指定的线程,所以可能会导致出现,当前线程调用了yield()放弃cpu使用权进入就绪状态后,cpu下次调用的线程还是当前线程。
锁池与等待池的区别:
每个对象都有一个同步锁/内置锁(互斥锁),同时也会锁池和等待池
锁池是用来存放那些想要获取对象锁,但是还没有拿到锁的线程。当拿到锁资源后,线程会进入就绪状态。
等待池存放的是那些主动释放(wait)锁去成全其它线程的线程。当等待池中的某一个线程被notfy、notfyall方法唤醒后,会进入锁池重新争夺锁,之后再从中断处继续执行任务。
线程池的好处
线程池的七大参数
常用阻塞队列
数组的有界阻塞队列。队列先进先出。链表的阻塞队列,可以说是无界的队列,当未指定容量时,则等于Integer.MAX.VALUE(2^31-1)。put之类的方法加入元素时,会触发Delayed接口中的compareTo方法进行排序,也就是说队列中元素的顺序是按到期时间排序的,而非它们进入队列的顺序常用拒绝策略
线程池的执行流程

核心线程数大小,小于则新建线程并直接执行任务线程池的最大线程数量如何设计
CPU数+1,这是为了防止线程在执行任务时发生突发情况导致线程暂停,从而使CPU空闲,这时候多加一个线程就可以利用CPU的空闲时间去完成任务。CPU数*2,程序在执行IO操作时,是不会用到CPU的,此时的CPU是空闲,所以我们可以多设置一些线程去利用CPU的空闲时间。(线程等待时间+CPU使用时间)/CPU使用时间*CPU数,如当前的任务都是1.5s的IO时间,0.5s的CPU使用时间,CPU核数是4,那最大的线程量应该为:(1.5+0.5)/0.5*4=4;线程池中的空闲线程能成为核心线程吗?
有一定概率;我们常说的空闲、核心线程只是一个概念,在线程池实际概念中并没有标识哪个是核心、空闲线程,线程池只会保留核心线程数大小的线程,其它线程就会被销毁,保留下来的就是核心线程。且销毁是随机的,那可能这次保留下来的核心线程,在下一次销毁时,核心线程是有可能被销毁,而它的位置就空闲线程替代。
线程只能在任务到达时才启动吗?
默认情况下,即使是核心线程也只能在新任务到达时才创建和启动。但是我们可以使用 prestartCoreThread(启动一个核心线程)或 prestartAllCoreThreads(启动全部核心线程)方法来提前启动核心线程
使用队列有什么需要注意的吗?
使用有界队列时,需要注意线程池满了后,被拒绝的任务如何处理。
使用无界队列时,需要注意如果任务的提交速度大于线程池的处理速度,可能会导致内存溢出
线程池如何关闭
线程池的五种状态
Spring是什么
解释:就是将对象创建和对象之间的调用过程,交给Spring管理,不用开发人员手动创建对象,减小开发人员的工作量,主要作用也是为了解耦。
解耦如下:
没有引入IOC容器之前
对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候, 自己必须主动去创建对象B或者使用已经创建的对象B,无论是创建还是使用对象B,控制权都在自己手上

引入IOC容器之后
对象A与对象B之间失去了直接联系,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方

通过前后的对比,不难看出来:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是"控制反转"这个名称的由来
”由主动行为变为了被动行为“:对象A之前需要自己创建B对象,此时对象A就依赖与对象B,对象A、B之间就耦合了,而此时引入IOC之后,对象A就不需要主动创建对象B,对象A等待Spring容器创建好之后去调用即可,对象A也不用关心对象B是怎么创建的。这里就弱化了对象A与对象B的直接联系,这里其实就起到了松耦合的作用,转而加强了对IOC的联系。
虽然是加强了对IOC的联系,但是Spring的主旨就是这么干的: 主要是为代码解耦,降低代码间的耦合度,让对象与对象(模块和模块)之间的关系不是由代码进行说明,而是用配置来说明
如果你还是不太理解IOC,那么举个例子就比如说人饿了想要吃饭。如果不使用IOC的话,你就得自己去菜市场买菜、做饭才能吃上饭。用了IOC以后,你可以到一家饭店,想吃什么菜你点好就可以了,具体怎么做你不用关心,饭店做好了,服务员端上来你负责吃就可以了,其它的交给饭店来做
控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法
DI(依赖注入)的实现方式
以下是一个使用XML配置实现Spring依赖注入的示例。假设你有一个名为UserService的服务类,它依赖于UserRepository:
public class UserRepository {
// UserRepository的实现
}
public class UserService {
private UserRepository userRepository;
// 构造函数注入
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void getUserInfo() {
// 使用userRepository获取用户信息
}
}
这时我们需要创建一个配置文件-applicationConytext.xml,这个配置文件包含了对Bean的定义与依赖注入的篇日志,我们将UserService与UserRepository对象在配置文件定义好,并声明UserRepository对象可以构造方法的形式注入到UserService对象中
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置UserRepository Bean -->
<bean id="userRepository" class="com.example.UserRepository" />
<!-- 配置UserService Bean,并注入UserRepository -->
<bean id="userService" class="com.example.UserService">
// 这行代码是关键
<constructor-arg ref="userRepository" />
</bean>
</beans>
主要注意这一行: ,这是构造方法注入的关键,通过constructor-arg标签将对象userRepository通过构造方法的形式注入到userService中
public class UserRepository {
// UserRepository的实现
}
public class UserService {
private UserRepository userRepository;
// 使用setter方法注入依赖
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void getUserInfo() {
// 使用userRepository获取用户信息
}
}
配置文件:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置UserRepository Bean -->
<bean id="userRepository" class="com.example.UserRepository" />
<!-- 配置UserService Bean,并使用setter方法注入UserRepository -->
<bean id="userService" class="com.example.UserService">
// 这行代码是关键
<property name="userRepository" ref="userRepository" />
</bean>
</beans>
主要注意这一行:,这是构造方法注入的关键,通过property标签将对象userRepository通过setter方法的形式注入到userService中
假设你有一个名为UserService的服务类,它依赖于UserRepository
public class UserRepository {
// UserRepository的实现
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void getUserInfo() {
// 使用userRepository获取用户信息
}
}
接下来,你需要配置Spring框架以扫描注解并创建Bean。通常,这可以通过配置Spring的扫描包来实现。以下是一个示例Spring配置文件(applicationContext.xml):
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.example" />
</beans>
在上述配置中,用于指示Spring扫描指定包下的类,并自动创建Bean。这样,Spring会扫描 com.example 包下的所有类,包括UserService和UserRepository,并根据@Autowired和@Service注解来处理依赖注入
当然你也可以使用@Component注解将对象注册为Spring容器的bean。这样就可以不使用配置文件的。区别就是context:component-scan 标签可以将指定包下的所有类注册为bean,放入到Spring容器中,而@Component注解只能将该注解修饰下的一个类注册为bean并放入到容器中。
Spring注解
在使用注解注入的时候,先理解几个注解的意思。
@Component:将一个类标识为Spring容器管理的Bean。
@Autowired:自动装配,通过 @Autowired 注解,Spring容器(IOC)会自动将匹配的Bean注入到标记了 @Autowired 的字段、方法参数或构造函数参数中。
@Service:标识一个类为服务层的Bean。与@Component相似,但提供了更明确的语义
@Controller:标识一个类为Spring MVC控制器
@Repository:标识一个类为数据访问层的Bean
@ComponentScan:会自动扫描包路径下面的所有被@Controller、@Service、@Repository、@Component 注解标识的类,然后装配到Spring容器中
@RequestMapping:用于映射web请求,包括访问路径和参数
@ResponseBody:将返回值转换为json格式并放入到response中。
@RequestBody:接收请求体中的json数据。
@PathVariable:用于接收路径参数,比如@RequestMapping(“/hello/{name}”)声明的路径,将注解放在参数前,即可获取该值,通常作为Restful的接口实现方法。
@RestController:该注解为一个组合注解,相当于@Controller和@ResponseBody的组合
@Configuration:标识一个类为配置类,可替换xml配置文件。通常与@bean注解配合使用
@Bean:定义一个bean,并放入到Spring容器中。
通常是与configuration一起使用,若是在非Spring管理的类中定义bean,则需要将该类添加到Spring容器中。如下:
public class Test{
@Bean
public MyBean myBean(){
return new MyBean();
}
}
这时你需要将Test类注册进入Spring容器中,MyBean这个bean才能被注册进入Spring容器中。
可以使用@Component注解,如下
@Component
public class Test{}
bean的作用域
总结:作用域session、request,在Spring中每次用户获取到的bean都是不同的,也就多例的
singleton与application在整个应用程序共享同一个Bean实例,也就是说都是单例的,区别就是:bean的生命周期
参考链接:
Spring中bean的生命周期
AOP(面向切面):就是在不修改源代码(业务代码)的情况下对程序(方法)添加额外的功能,主要场景有日志记录、权限管理、异常处理等方面。
AOP术语
joinpoint 连接点:就是指那些潜在的可以被拦截(增强)的方法,方法一旦被拦截就转为切点,切点的候选。这里可以理解成 所有方法都可以时连接点pointcut 切入点:在那些方法上切入。即被拦截的连接点。advice 通知:指拦截到连接点之后要做的事,即对切入点增强的内容,通知有很多种,如下befor(前置通知):通知方法在目标方法(切入点)调用之前执行
after-returing(返回后通知):通知方法在目标方法返回后执行
after-throwing(抛出异常通知):通知方法在目标方法抛出异常后执行
after(后置通知):通知方法在目标方法返回或异常后执行
around(环绕通知):
Target(目标对象):切入点的对象(被拦截的方法的对象)。Weaving(织入):将切面的通知代码插入到目标对象的过程。Proxy(代理):在织入之后,代理对象会被创建。代理对象包装了目标对象,并包含了切面的通知代码。客户端代码通常与代理对象交互,因此客户端对目标对象的调用实际上是通过代理对象进行的静态代理和动态代理
静态代理:
优点:在编译时创建代理类,代理类编译后存在,与目标对象的关系在编译时已经确定。代码易于理解和实现
缺点:不灵活:静态代理需要为每个被代理的类创建一个代理类,导致类增加。
动态代理:
优点:在运行时创建代理类,代理类在程序运行期间动态生成,减少了代码的重复和冗余
缺点:相对于静态代理,动态代理的实现更加复杂,需要深入理解反射机制和代理类的生成。
静态、动态代理示例代码可以参考下面这篇文章:
静态代理和动态代理
JDK动态代理与CGLIB动态代理
JDK动态代理:
优点:Java本身支持,随着版本稳定升级
缺点:目标类必须实现某个接口,没有实现接口的类型不能生成代理对象;代理的方法必须申明在接口中,否则无法代理;执行速度性能相对于cglib较低。
CGLIB动态代理:
优点:目标类无需实现接口;但是会针对目标类生成一个子类,覆盖其中的所有方法;执行速度性能会比JDK代理高
缺点:若是目标类和目标类中的方法被final修饰,则无法代理;动态创建代理对象的代价比较高
可以参考下面这篇文章:
谁与争锋,JDK动态代理大战CGLib动态代理
文章中JDK、CGLIB代理都没有明确使用通知(Advice)方法,而是仅演示了如何使用代理创建对象和调用方法。这里只是演示如何创建的,在实际开发中JDK、CGLIB动态代理是不用我们自己实现的,Spring会自己创建。在Spring中默认动态代理策略是智能的,若是目标类没有实现任何接口,Spring会尝试使用CGLIB动态代理来创建代理对象,反之则使用JDK动态代理。
实际开发中使用AOP可以参考下面链接
基于springboot实现一个简单的aop
SpringBoot是Spring开源组织下的子项目,主要简化了Spring的难度,减去了繁重的配置,是开发者能快速上手。
BeanDeFinition是什么
在Spring框架中,“BeanDefinition”是一个很重要的概念,它描述了一个Bean实例的基本信息,每个被Spring容器管理的Bean都有一个对应的“BeanDefinition”,其中包含了它的类名、作用域、构造函数参数、属性值、Bean之间的依赖关系、初始方法和销毁方法。
约定大约配置是什么意思
约定大于配置是一种开发原则,就是为了减少人为的配置数量,能使用默认的配置就使用默认的;默认配置就是所谓的“约定”;当存在特殊需求的时候,也可以自定义配置覆盖默认配置。,如我们需要在SpringBoot使用redis,我们需要去连接redis,需要用到URL、端口、密码等,SpringBoot其实就有默认的约定,默认连接本地端口6379的redis,没有密码
SpringBoot的核心注解
SpringBoot支持什么前端模板
thymeleaf、freemarker、jsp
SpringBoot实现热部署有哪几种方式?
热部署:就是程序检测到代码改动后会自动重新启动SpringBoot项目,程序员就不用手动的重启项目了。
主要有两种方式
SpringBoot事务的使用
首先使用注解@EnableTransactionManagement开启事务管理,然后在对应的方法添加注解@Transactional即可
SpringBoot有哪几种读取配置的方式
Spring Boot 可以通过 @PropertySource,@Value, @ConfigurationPropertie注
解来读取配置并赋值。
bootstrap.properties 和application.properties 有何区别 ?
Spring Profiles
Spring Profiles (Spring配置文件)是Spring框架中的一种机制,可以让程序在不同环境下使用不用的配置。对于开发、测试、生产环境之间的配置切换非常有用。
当前有三个配置文件,application.yml、application-dev.yml、application-test.yml,你可以在application.yml指定使用哪套配置文件,被指定的配置文件与application.yml会组合成一个配置文件。
applicatiom.yml:
spring:
profiles:
active: dev // 指定配置文件
application-dev.yml
my:
applicationName: dev
application-test.yml
my:
applicationName: test
SpringBoot如何实现跨域
跨域是指定客户端在使用浏览器/app发生ajax请求时,会触发同源策略(协议、ip
、端口必须相同),当发现请求的服务器地址不同源时就会出现跨域
解决跨域常用有两种方式
常见的解决方案有 实现WebMvcConfigurer接口并重写addCorsMappings方法解决跨域问题
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.maxAge(3600);
}
}
如何使用 Spring Boot 实现全局异常处理
可以结合注解 @RestControllerAdvice、@ExceptionHandler 一起使用实现全局异常处理
Spring Boot 中如何实现定时任务 ?
@Scheduled注解:用于创建定时任务;指定方法在特定的时间间隔或时间点执行。
使用全局锁之后,数据库的所有数据就处于只读状态,后续对于数据的CRUD操作都会被阻塞。
一般是用来进行数据备份的。对于MYISAM搜索引擎,它不支持事务,因此在备份数据的时候就需要使用全局锁来处理了。
表锁分为以下三种
- 表锁
表锁:作用是对于某个特定的表加锁,支持读锁与写锁。
写锁只存在一个。其它线程/事务就不能再对这个表加任何读锁或写锁。读锁与写锁之间:读读共享,读写互斥,写写互斥
- 元数据锁(MDL)
MDL加锁过程是系统控制,也就是说对于用户而言是不透明的。MDL锁主要是为了解决DDL操作与DML操作之间的数据一致性问题。內部也是有写锁与读锁
MDL写锁也只会存在一个。其中 读读共享,读写互斥,写写互斥
参考:MySQL(十二)MDL锁介绍
- Autu-INC锁
Auto-INC锁是InnoDB存储引擎用来管理自增长列(主键自增)的一个锁。该锁确保每个事务获取的自增值是唯一的且是按照递增的顺序进行分配的。 其中auto-inc锁分为以下三种模式
innodb_autoinc_lock_mode=0;在传统模式中,事务结束后才会释放锁。事务中如果有对表进行添加操作添加1/n条数据,该事务就会获取该表的auto-inc锁,其它事务若想得到该表的auto-inc锁则会阻塞,直到该事务提交。这种模式保证了在事务中进行了多次添加操作,都可以保证该事务中添加的数据的自增值都是连续的。但有一个很大的弊端:在高并发的情况下,所有事务都需要排队获取auto-inc锁,性能与并发度不是很高。
innodb_autoinc_lock_mode=0;截止到现在,mysql默认的auto-inc锁是连续模式;,连续模式就是在性能和自增值连续性之间进行一个折中选择;某种情况下放弃连续性保证性能。 连续模式针对单条/批量插入语句会出现以下两种情况。
单条插入:对于A事务中单行的插入语句,mysql在生成自增值后就会立即释放auto-inc锁。这时其它事务不用等待事务/插入语句结束才能拿到auto-inc锁。完全放弃了连续性保并发性能。实例如下:
当前有事务A、B,事务中有两条插入语句,事务B插入一条数据
流程:事务A插入第一条数据并释放锁 此时事务A需要做其它表的DML操作导致事务B拿到auto-inc锁,事务B获取到锁插入一条数据,事务A再次获取锁插入一条数据。锁的资源占用顺序为:A->B->A 来看看事务A中的添加的两条数据自增值是否是连续的
事务A:
begin
insert into auto_inc_test(name)
value ('A1');
接着事务A需要执行其它表的DML操作。
事务A开启事务并添加第一条数据。insert执行完后释放了auto-inc锁,接着事务A需要执行其它表的DML操作。导致事务B拿到了该表的auto-inc锁。
事务B:
begin
insert into auto_inc_test(name)
value ('B');
commit
事务B开启事务,获取到auto-inc锁添加了一条数据并释放锁,同时提交事务
事务A:
insert into auto_inc_test(name)
value ('A2');
commit
事务A再次获取auto-inc锁,并添加数据及提交事务
此时我们查看auto_inc_test表中的数据:

可以发现在事务A中插入的两条数据的自增值并不是连续的,所以在连续模式中针对单条的insert语句,是完全放弃了连续性保证性能。
批量插入:对于批量插入操作,事务会一直持有auto-inc锁直到语句结束。在语句结束后批量插入的数据的自增值是连续的,一定程度上放弃了连续性并保证性能。
当前有事务A、B,事务中有两条批量插入语句,事务B一条批量插入语句。
流程:事务A执行第一条批量插入并释放锁,此时事务A需要做其它表的DML操作导致事务B拿到auto-inc锁,事务B获取到锁执行批量插入,事务A再次获取锁执行批量数据。
事务A:
begin
insert into auto_inc_test(name) values
('A1'),
('A2')
接着事务A需要执行其它表的DML操作。
事务A获取auto-inc锁,执行第一条批量插入语句,语句执行完成后释放锁。
事务B:
begin
insert into auto_inc_test(name) values
('B1'),
('B2')
COMMIT
事务B获取auto-inc锁,执行批量插入语句后释放锁并提交事务
insert into auto_inc_test(name) values
('A3'),
('A4')
COMMIT
事务A再次获取auto-inc锁,执行完第二条插入语句后释放锁并提交事务
我们此时来看看事务A中批量插入的数据的自增值是否是连续,两条批量插入的数据的自增值是否是连续的

可以看到单条批量插入的数据是连续的,但是在同一个事务中多条批量插入语句之间数据的自增值并不连续。也就是该情况下 放弃一定程度的连续性保证性能。
innodb_autoinc_lock_mode=2;它完全放弃了自增值的连续,从而提高并发插入的性能。无论是单条/批量插入时,锁都是在生成自增值后立即释放。与连续模式中的批量插入不同的是,连续模式是批量插入语句执行完成之后才释放,也就是说在交错模式下可能会出现以下情况。
当前事务A、B都同时执行了批量插入语句。那他们的自增值可能会出现下面的情况。
事务A:
begin
inert into auto_inc_test(name) values
('A1'),
('A2')
commit
事务B:
begin
insert into auto_inc_test(name) values
('B1'),
('B2')
commit

仔细看自增值的连续性是完成没办法保证的,而连续模式的批量插入一定程度上是可以保证单次批量插入语句的数据的自增值是连续的。但交错模式的并发性能肯定是会比连续模式好的。
作用:是InnoDB引擎锁定数据表中的特定行,是最细粒度的锁,可以最大程度的支持并发处理,但是也很容易造成死锁。 支持共享锁及排它锁。
注意:
读读共享、读写阻塞。锁的获取与释放都是隐式的,通常在对应的操作完成后锁会隐式释放,但是对于事务,行锁就必须要等待事务提交后才会释放,这是因为事务的一致性要求,需要确保整个事务内的操作是原子的,要么全部执行成功,要么全部回滚(auto-inc锁除外)。这就是为什么说行锁很容易造成死锁。
在这种情况下,就很容易造成死锁。
当事务B发现获取锁的资源被事务A占用,而事务A刚好也在等待事务B的锁资源,MYSQL就会立马判定这是死锁,会回滚某一事务从而解决死锁。
如果事务获取锁的等待时间超过时间限制就会被认为是死锁的一部分。
当MYSQL检测到死锁的存在,会自动中止/回滚某一个事务来解决死锁。但是对于回滚事务也是会占用系统资源(CPU)的,所以我们应尽量避免死锁。
如何避免死锁?
如何解决死锁?
意向锁的粒度也可以看成是表级别的锁;支持共享锁与排它锁。
它的作用:
场景:在innoDB引擎中一个事务A正在对某个表T的第r行加了写锁,另一个事务B尝试去对整个表做操作,B尝试去对整个表加一个写锁。
那它此时就要检查这张表是否有行锁,不管是有行写锁还是行读锁都是不允许对这个表加入表的写锁的。
那mysql如何判断表中是否有行锁?
未引入意向锁前:一行一行查询是否有行锁,如果都没加,就代表可以锁表。但这样的效率太低,数据表的数据可能是百万级别的。检索起来非常麻烦。
引入意向锁后:事务A想对某一个加行锁,会先对表加上意向锁,之后再对具体行加上行锁。
事务A加行读锁:事务A—> 加意向共享锁---->加行共享锁。
事务A加行写锁:事务A----> 加意向排它锁---->加行排它锁。
这样就可以快速检测到这个表是否有行锁锁定。

注意:意向锁之间是相互兼容的
为什么是都是兼容的?
事务A加了表的IX锁,或者IS锁,只代表事务A已锁定一行或者将要锁定一行。事务B当然也可以锁定其他的行,所以事务B肯定也是可以获得表的IS锁或者IX锁的
这样也说明了 innoDB引擎支持表锁与行锁共存。
意向锁与表锁之间的兼容关系如下:

这里的S锁和X锁是表级别的,意向锁不会与行级别的S锁和X锁冲突
参考:意向锁的理解
索引是帮助MYSQL高效获取数据的数据结构。简单来说,数据库索引就像是书前面的目录,能加快数据库的查询速度。
索引会占据磁盘空间,索引虽然会提高查询速度,但是会降低更新表的效率。比如每次对表进行增删改操作,MYSQL不仅要保存数据,还要保存或更新对应的索引文件。
常见的索引
B+tree指的是三层结构的树状结构类型,相对于二叉树而言,会降低IO成本。

在B+tree中叶子节点指的是最底层的节点,其它层都叫非叶子节点,根节点也可以被称为非叶子节点,B+tree中的根节点可以是由多个节点组成的。
在B+tree中,叶子节点之间使用双向指针连接,最底层的叶子节点形成了一个双向有序链表。这样做的好处是使用范围查询时,不用每次都需要回到根节点重新遍历查找,而是从叶子节点中往后遍历即可。
B树:其它都与B+tree相同,但是叶子节点之间是没有双向指针相连的。当MYSQL使用范围查询时,B树的查询效率相对于B+tree而言就慢许多。
在MyISAM存储引擎中,叶子节点存储的数据是内存地址,而InnoDB引擎,叶子节点存储的数据为行记录。
聚簇索引:也叫主键索引,在InnoDB中,如果表中没有设置主键索引,MYSQL会创建一个6字节的长整型的列作为主键索引。
辅助索引:除聚簇(主键)索引之外的所有索引都称为辅助索引,InnoDB的辅助索引只会存储主键值而非行记录。
最左前缀法则
我们在使用组合索引时,最好遵循最左前缀法则,否则在查询时MYSQL会放弃索引查询,而去选择全表查询,这时效率就会低效。
最左前缀法则:如果你创建一个多列索引,查询中的条件必须从索引的最左侧的列开始,并且不能跳过前面的列,按照一定顺序命中索引。这样的查询才能充分利用索引。
这里可以参考:原创:史上最全最通俗易懂的,索引最左前缀匹配原则(认真脸)
其中关于使用EXPLAIN语句中出现type列的解释如下
索引失效的几种情况:
具体使用全表扫描还是索引查询,取决于查询优化器的决策。
索引篇参考文章如下:
InnoDB存储引擎
InnoDB适用于需要事务支持和高并发的场景。可以更好的确保数据的一致性。
MyISAM存储引擎
MyISAM在读取密集型的场景中可能更有优势,MyISAM不支持事务,可能在某些情况下无法保证数据一致性。
为什么说读取会比InnoDB引擎快,这是因为在使用辅助索引进行查询时,不会再出现回表的现象。因为辅助索引的叶子节点存储的也是这行数据的内存地址。
一个事务由一组DML语句组成的,事务内的DML语句要么同时成功、要么同时失败。
事务特性
注:一致性建立在原子性、持久性、隔离性的基础上。
MYSQL是如何保证事务原子性的?
MYSQL如何保证事务持久性
在事务中的DML除了会写入Undo Log日志中,也会被写入Redo Log日志中,但它们的用途不同,Undo Log 主要用于回滚,而Redo Log日志用来保证持久性。
当系统崩溃后异常关闭,MYSQL在重新启动时会检查系统崩溃的情况,会根据Redo Log日志来恢复数据。
事务的隔离级别
读未提交(Read Uncommitted):
读已提交(Read Committed):
可重复读(Repeatable Read):
串行化(Serializable):
脏读:一个事务读取了另一个事务未提交的数据
不可重复读:在一个事务内,相同的查询返回不同的结果。主要是同一行数据的修改。
幻读:在一个事务内,相同的查询返回不同的行数。查询的数据行数与前一次查询发生改变。
写偏斜:两个事务同时修改同一行数据,并最终只有一个事务的修改生效。发生在读已提交和可重复读隔离级别下。
mvcc(多版本并发控制)提高数据库的并发访问,实现读-写不冲突。
MVCC 就是为了实现读-写冲突不加锁,而这个读指的就是快照读, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现
当前读,快照读和MVCC的关系
三个隐式字段
每行记录除了我们自定义的字段外,还有数据库隐式定义的 DB_TRX_ID, DB_ROLL_PTR, DB_ROW_ID 等字段
undo log日志
记录类型主要分为两种
insert新纪录时产生的undo log日志,只在事务回滚时需要,当事务提交后立即删除丢弃update或delete时产生的undo log日志;不仅在事务回滚时需要,在快照时也需要;所以不能随便删除。undo log产生的日志记录是以单链表形式存储。其中头链表是最新的修改记录。如下:

注:undo log日志只会事务中DML操作后的记录,如果DML操作没有在事务中执行,undo log是不会记录的,这是为了方便事务回滚操作。
Read View(读试图)
什么是 Read View,说白了 Read View 就是事务进行快照读操作的时候生产的读视图 (Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的 ID (当每个事务开启时,都会被分配一个 ID , 这个 ID 是递增的,所以最新的事务,ID 值越大)
Read View遵循一个可见性算法,主要是将要被修改的数据的最新记录中的 DB_TRX_ID(即当前事务 ID )取出来,与系统当前其他活跃事务的 ID 去对比(由 Read View 维护),如果 DB_TRX_ID 跟 Read View 的属性做了某些比较,不符合可见性,那就通过 DB_ROLL_PTR 回滚指针去取出Undo Log中的 DB_TRX_ID 再比较,即遍历链表的 DB_TRX_ID(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条件的 DB_TRX_ID , 那么这个 DB_TRX_ID 所在的旧记录就是当前事务能看见的最新老版本
Read View的三个全局属性
可见性算法如下:
DB_TRX_ID < up_limit_id ,这个DB_TRX_ID指的是快照读的记录中的事务ID, 如果小于,则说明当前的记录的事务在快照生成时刻就已经提交了,则当前事务能看到 DB_TRX_ID 所在的记录,如果大于等于进入下一个判断DB_TRX_ID >= low_limit_id , 如果大于等于则代表 DB_TRX_ID 所在的记录在 Read View 生成后才出现的,那对当前事务肯定不可见,如果小于则进入下一个判断 DB_TRX_ID 是否在活跃事务列表之中,trx_list.contains (DB_TRX_ID),如果不在,则说明,你这个事务在 Read View 生成之前就已经 Commit 了,你修改的结果,我当前事务是能看见的;如果在,则代表我 Read View 生成时刻,你这个事务还在活跃,还没有 Commit,你修改的数据,我当前事务也是看不见的,此时就会去undo log中找到前一版本的记录,重新执行可见性算法,直到找到满足条件的记录。RC , RR 级别下的 InnoDB 快照读有什么不同?
RC(读已提交) 隔离级别下,是每个快照读都会生成并获取最新的 Read View;而在 RR (可重复读)隔离级别下,则是同一个事务中的第一个快照读才会创建 Read View, 之后的快照读获取的都是同一个 Read View。
快照命中的是某个时间点的所有数据,在RR模式下事务将一直使用这个一致的数据视图。
参考文章:【MySQL笔记】正确的理解MySQL的MVCC及实现原理
String(字符串)
数据类型:key value
哈希(hash)
数据类型:key field value1,其中field1,value可以看出是key的value之一,也就是说hash类型是集合,在这个key中也会有field2 value2等。
列表(list)
数据类型:key values
该数据类型是有序,根据你插入的顺序进行排序
集合(set)
数据类型:key values ,其中內部元素是无序并且不允许重复元素
有序集合(Zset)
数据类型:key values;其中內部元素有序且不允许重复元素,其中value被拆分为 score(分数) member(成员名称,也可以将这个看成value)
地理空间数据类型(GEO)
数据类型:key values,其中value被拆分为 longitude(经度) latitude(纬度) member(位置名称),常用于存储地理位置信息。
存储一个或多个地理空间:GEOADD key longitude latitude member [longitude latitude member …]

取出key中指定成员的地理位置:GEOpos key member[member …]
范围取出:Zrange key start end
获取两个成员之间的距离:GEODIST key member1 member2 [unit];unit可以不填,默认单位为m
在指定的键中,查找给定成员周围一定范围内地临近成员:GEORADIUSBYMEMBER key member radius m|km|mi|ft [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]

删除指定成员:ZREM key member[member …];与Zset数据类型的删除指令一样
基数统计(Hyperloglog)
基数:不重复的元素
Hyperloglog常用来统计一个网站的浏览量,统计出来的数据是去重的,但是估算值不是特别准确。但在大多数实际应用场景中,其对基数估算的性能和内存效率都是相当可观的
数据类型:key values
位存储(Bitmap)
应用场景,统计网站中登录和未登录的用户人数,这时候我们知道用户都是一个个user对象,统计起来也是特别麻烦,这时候可以使用Bitmaps,它是位图
它是一种数据结构,都是操作二进制来进行记录,就只有0和1两个状态
模拟一个用户该周的七天登录情况

语法:setbit key offset(偏移量) value(0/1)
zhangsan:泛指单个用户
0:本周的第一天
1:登录 0 :未登录
这样该周的打卡情况就出来了
查看某一天有没有登录

getbit zhangsan 0:可以看成是该用户周一有没有登录,
统计打卡的天数

语法:bitcount key
统计key为zhangsan的value为1的总和,看成是总打卡量
缺点:消息无法持久化,PubSub的消息是不会持久化的,redis宕机就相当于一个消费者都没有,所有消息直接丢弃。如果开始有三个消费者,一个消费者突然挂掉了,生产者会继续发送消息,另外两个消费者可以持续收到消息。但是挂掉的消费者重新连上的时候,这断连期间生产者发送的消息,对于这个消费者来说就是彻底丢失了。
参考链接:Redis发布订阅以及应用场景介绍
redis事务的本质是一组命令的集合。
参考链接:Redis之Redis事务
watch监视器
watch监视器通常与事务一起使用,用来实现乐观锁。
watch用来监视一个或多个键,如果被监视的任何一个键被其他客户端修改,当前客户端的事务就会被打断,EXEC 命令将返回 nil,表示事务执行失败。
WATCH 命令必须在事务开始前使用,通常是在 MULTI 命令之后,EXEC 命令之前。
主要使用场景为: