操作系统中方法调用的过程
进程间通信方式
- 匿名管道:是一种半双工通信方式,数据只能单向流动,常用于具有亲缘关系的进程间通信,主要是父子进程
- 缺点
- 只支持单向数据流
- 只能用于具有亲缘关系的进程之间
- 没有名字
- 管道的缓冲区是有限的
- 管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息
- 缺点
- 有名管道:是一种半双工通信方式,允许无亲缘关系进程间的通信
- 信号(Signal):信号可以在任何时候发给某一进程,而无需知道该进程的状态
- SIGINT:程序终止信号
- SIGQUIT:程序退出信号
- SIGKILL:用户终止进程执行信号
- 信号量(semaphore):进程间同步
- 消息队列
- 存放在内核中的消息链表
- 与管道不同的是消息队列放在内核中,只有在内核重启或者显式地删除一个消息队列时,该消息队列才会被真正的删除
- 共享内存
- 多个进程直接读写同一块内存空间,是最快的可用IPC形式
- 套接字:不同主机之间的进程进行双向通信
线程间通信方式
- 锁机制
- 信号量
- volatile:保证变量可见性,防止指令重排序
- wait/notify
进程和线程的区别
- 进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
- 一个进程可以有一个或多个线程,一个线程只能属于一个进程
- 线程是轻量级的进程
线程和协程的区别
- 协程是用户态的轻量级线程
- 协程的切换不需要进入内核态,直接在用户空间切换,开销比线程切换小
- 多个协程在线程内串行执行
三种工厂模式
简单工厂模式
![image-20230404133429385](/Users/effy/Library/Application Support/typora-user-images/image-20230404133429385.png)
- 优点:
- 实现对责任的分割,提供专门的工厂类用于创建对象
- 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可
- 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类
- 缺点:
- 工厂类集中了所有产品创建逻辑
- 增加了系统中类的个数
- 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,违反了开闭原则
- 工厂角色无法形成基于继承的等级结构
- 适用场景
- 工厂类负责创建的对象比较少
- 客户端只知道传入工厂类的参数,对于如何创建对象不关心
工厂方法模式
模式定义
- 工厂父类负责定义创建产品对象的公共接口,而工厂子类负责生成具体的产品对象
模式结构
![image-20230404134634411](/Users/effy/Library/Application Support/typora-user-images/image-20230404134634411.png)
模式优点
- 在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,只要添加一个具体工厂和具体产品即可,完全符合开闭原则
模式缺点
- 添加新产品时,需要编写新的具体产品类,还要提供与之对应的具体工厂类,系统中类的个数成对增加,增加了系统复杂度
- 增加了抽象工厂这一抽象层,增加了系统的抽象性和理解难度
适用环境
- 一个类不知道它所需要的对象的类
- 一个类通过其子类来指定创建哪个对象
- 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无需关心是哪个工厂子类创建产品子类,需要时再动态指定
抽象工厂模式
模式结构
![image-20230404140149289](/Users/effy/Library/Application Support/typora-user-images/image-20230404140149289.png)
模式优点
- 保证客户端始终只使用同一个产品族中的对象
- 增加新的具体工厂和产品族很方便,无须修改已有系统,符合开闭原则
模式缺点
- 开闭原则的倾斜性:增加新的产品族容易,增加新的产品等级结构麻烦,难以扩展抽象工厂来生产新种类的产品
适用环境
- 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节
- 有多于一个的产品族,属于同一个产品族的产品在一起使用
- 所有的产品以同样的接口出现,客户端不依赖于具体实现
面向对象六大设计原则
- 开闭原则
- 里氏代换原则
- 迪米特法则
- 依赖倒置原则
- 单一职责原则
- 接口隔离原则
HTTP重定向和转发的区别
- 重定向:浏览器访问url,服务端返回一个新的url,浏览器重新访问这个新的url
- 转发:浏览器访问url,服务器进行转发,返回对应的结果
- 访问次数:重定向需要访问两次,转发只需要访问一次
cookie和session的区别
- 存储位置不同:cookie保存在客户端,session保存在服务器
- 安全性不同:cookie保存在客户端,所以不安全,考虑到安全应当使用session
- 有效期不同:cookie可以永久保存,session一般都有时效性
- 存取值的类型不同:cookie 只支持key-value,session存放的是key-object
- 存储大小不同: 单个 cookie 保存的数据不能超过 4KB,session 可存储数据远高于 cookie,但是当访问量过多,会占用过多的服务器资源
禁用cookie了怎么办
- URL重写:直接把sessionId附加在URL路径后面,手机浏览器一般都是如此实现
- 表单隐藏域:服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把sessionId传回服务器
参考:https://www.cnblogs.com/l199616j/p/11195667.html
TCP和UDP的区别
- 是否面向连接:TCP提供面向连接的服务,UDP无连接
- 是否是可靠传输:TCP实现了可靠传输,UDP不可靠
- 传输效率: TCP 进行传输的时候多了连接、确认、重传等机制,所以 TCP 的传输效率要比 UDP 低很多
- 首部开销:TCP为了保证可靠性传输所以头部开销大(20-60字节),UDP头部开销小(8字节)
- 是否支持广播:TCP只支持点对点,UDP支持多播、组播
- 传输形式 : TCP 是面向字节流的,UDP 是面向报文的
- 是否有状态 :这个和上面的“是否可靠传输”相对应。TCP 传输是有状态的,这个有状态说的是 TCP 会去记录自己发送消息的状态比如消息是否发送了、是否被接收了等等。为此 ,TCP 需要维持复杂的连接状态表。而 UDP 是无状态服务,简单来说就是不管发出去之后的事情了
TCP传输可靠性保障
TCP如何保证可靠性传输
- 基于数据块传输
- 数据块重排序和去重
- 校验和
- 超时重传
- 流量控制
- 拥塞控制
TCP如何实现流量控制
- 滑动窗口
TCP如何实现拥塞控制
- 慢开始,拥塞窗口初始值为 1,每经过一个传播轮次,拥塞窗口翻倍
- 拥塞避免,每经过一个往返时间 RTT 就把发送方的拥塞窗口加 1
- 快重传与快恢复,接收方接收到一个不按顺序的数据段,它会立即给发送方发送一个重复确认。如果发送方收到三个重复确认,它会假定确认件指出的数据段丢失了,并立即重传这些丢失的数据段
HTTP和HTTPS的区别
- 端口:HTTP默认端口是80,HTTPS默认端口是443
- HTTP不安全,HTTPS安全
HTTP1.0和HTTP1.1
- HTTP1.0短连接,HTTP1.1长连接
- 状态码:新增状态码,100——请求大资源前的预热请求
- 带宽优化,可以传输特定范围的数据
- Host头处理:HTTP/1.1在请求头中加入了Host字段
自动拆箱和装箱的底层原理
自动装箱的底层原理:自动装箱实际上调用的是Integer中的静态方法valueOf(),将基本数据类型的int数值包装成了一个Integer对象
1 | Integer integerVal = 0; |
自动拆箱的底层原理:自动拆箱的底层实际上调用的是Integer对象的intValue(),得到对象内的int变量的数值,然后给赋值给变量
1 | int intVal = new Integer(0); |
Hash函数的设计
- 除留取余法
- 基数转换法
HashCode方法
- Java的hashcode默认返回对象的内存地址
- String的hashcode使用基数转换法,基数是31
- 规定
- 在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,如果对象进行 equals 比较时所用的信息没有被修改,则必须一致地返回相同的整数
- equals相等的对象,hashCode方法返回值必须相同
- equals不想等的对象,hashCode方法返回值不一定不同
Java异常体系
- Throwable类有Error和Exception两个子类
- Error常见的有OOMError、StackOverFlowError等
- Exception主要分为IOException(checkedException)和RuntimeException(UncheckedEception)
- IOException有ClassNotFoundException、NoSuchFieldException等
- RuntimeException有ArithmeticException、NullpointerException等
Java线程生命周期及转换
生命周期
NEW
:new
一个线程后,线程进入该状态RUNNABLE
:线程调用start()
方法后进入RUNNABLE
状态WAITING
:线程等待某个事件的发生/等待其他线程的通知(同步)TIMED_WAITING
:线程等待某个事件一段时间,超过该时间后,自动回到RUNNABLE
状态BLOCKED
:线程等待获取某个资源的锁(互斥)TERMINATED
:线程执行完成后进入终止状态
状态转换
- 新建一个线程
new Thread()
后,线程进入NEW
状态 - 线程对象创建成功后,调用该线程的
Thread.start()
,线程从NEW
进入RUNNABLE
状态 RUNNABLE
线程遇到以下方法时,会进入WATING
状态Object.wait()
:等待某个对象,JVM
把该线程放入锁等待队列Thread.join()
:主线程等待子线程结束,JVM
把该线程放入锁等待队列LockSupport.park()
:AQS框架中,阻塞一个线程时调用该方法
WAITING
线程遇到以下方法时,会回到RUNNABLE
状态Object.notify()
:唤醒锁等待队列的线程,出队的线程回到就绪状态Object.notifyAll()
:唤醒锁等待队列的线程,出队的线程回到就绪状态- 等待
join()
的线程执行完毕,唤醒锁等待队列的线程,出队的线程回到就绪状态 LockSupport.unpark(t)
:唤醒指定的线程- 注:在调用
notify
或者notifyAll
方法后,等待线程不会立刻从等待队列返回,而是从等待队列移动到同步队列,竞争对象监视器
RUNNABLE
线程遇到以下方法时,会进入BLOCKED
状态- 获取同步锁失败,
JVM
还会把该线程放入锁同步队列 - 发出
I/O
请求
- 获取同步锁失败,
BLOCKED
线程遇到以下情况时,会回到RUNNABLE
状态- 同步锁被释放时,锁同步队列会出队所有线程
I/O
处理完毕
- 线程执行结束或者因异常意外终止都会使线程进入
TERMINATED
状态
WAITING和BLOCKED状态的区别
- waiting更像同步,等待其他线程的信号
- blocked更像互斥,等待某个资源或者没拿到锁回到blocked状态
Java几种内置的线程池
线程池 | 等待队列 |
---|---|
FixedThreadPool(固定线程数) | LinkedBlockingQueue(队列最大为Integer.MAX_VALUE) |
SingleThreadExcutor(一个线程) | LinkedBlockingQueue(队列最大为Integer.MAX_VALUE) |
CacheThreadPool(最大线程为Integer.MAX_VALUE) | SynchronizedQueue(没有容量,每有一个任务就创建一个线程执行) |
ScheduledThreadPool(按delay时间排序) | DelayedWorkQueue(按delay时间出队) |
Callable和Runnable区别
- 返回值不同:Runnable方法没有返回值,Callable方法有返回值,调用FutureTask.get()获取返回值
- 异常处理:Runnable不能抛出异常,所有异常都要在run方法内部捕获并处理,Callable可以向外抛出异常
Java的I/O模型
- BIO(Blocking IO):
- NIO
- AIO
JDBC分库分页
CMS收集器和G1收集器有什么区别
- CMS(Concurrent Mark Sweep)
- 适用场景:集中在互联网站或B/S系统服务端上的java应用
- 使用算法:“标记-清除”
- 收集区域:老年代
- 收集步骤:初始标记、并发标记、重新标记、并发清除
- 优点:并发收集、低停顿
- 缺点:
- 存在并发标记的阶段,所以不能等到老年代几乎完全被填满再进行收集
- 并发清除的阶段产生浮动垃圾无法回收
- 使用“标记-清除”,产生内存碎片
- G1(Garbage-First):
- 适用场景:面向服务端应用,针对具有大内存、多处理器的机器
- 使用算法:整体上基于“标记-整理”,局部上看是基于“标记-复制”
- 收集区域:整堆收集
- 收集步骤:初始标记、并发标记、最终标记、筛选回收
- 优点:
- 将Java堆划分为多个大小相等的独立区域(Region)
- 可预测的停顿时间(有计划地避免在整个Java堆上进行全区域收集,根据允许的收集时间,只优先回收价值最大的Region)
新生代为什么用标记复制,不用标记整理
- 使用标记复制是因为新生代的对象大多存活时间较短,不需要移动待回收对象的操作,只需要将存活的直接复制到另一块空闲内存区域中,时间效率较高
- 不用标记整理是因为将存活的对象整理移动到另一端,需要大量移动操作,时间效率低
老年代为什么用标记整理,不用标记复制
用标记整理是因为老年代的对象相对较大,使用标记整理能够整合空闲内存、有效减少内存碎片,从而确保尽可能的存放晋升到老年代的对象,如果不进行整理的话会有大量的内存碎片导致放不下新晋升的大对象,从而导致频繁发生FullGC,空间效率高
不用标记复制因为老年代的对象生存时间长,使用标记复制方法会对大量存活对象进行复制,效率比较低,同时标记复制还会浪费空间,有一部分空间不能使用,空间效率低
对象创建过程
- 类加载检查
- 分配内存
- 赋初始值
- 设置对象头
- 执行init方法
JVM调优
- -Xmx和-Xms调整堆内存最大值和最小值,一般设置成相同,可以避免java 垃圾回收机制清理完堆区后重新分隔计算堆区的大小而浪费资源、增加GC次数,-Xmx默认值是物理内存的1/4,-Xms默认值是物理内存的1/64
- -Xmn,新生代大小,过小会增加Minor GC频率,过大会减小老年代的大小,官方推荐新生代占堆空间的3/8
- -Xss,线程栈空间大小
- -XX:NewRatio,设置新生代和老年代的比值,比如设置成4,即新生代:老年代=1:4,新生代占整个堆的1/5
- -XX:SuvivorRatio,设置两个Suvivor区和Eden区的比值,官方推荐8:1:1
- -XX:MaxTenuringThreshold:晋升到老年代的阈值
- -XX:MaxMetaspaceSize=10M:设置元空间大小
如何排查内存泄漏
配置JVM启动参数
-XX:+HeapDumpOnOutOfMemoryError:输出Heap Dump文件
-XX:HeapDumpPath=路径:输出Heap Dump文件的路径
确定频繁GC的现象
【jps】虚拟机进程状况工具:命令格式 jps [ options ] [ hostid ]
- jps -l查看虚拟机进程号
【jstat】虚拟机统计信息监视工具:命令格式:
jstat [ option vmid [interval[s|ms] [count]] ]
1
2
3
4jstat -gcutil 20954 1000
//gcutil指:已使用空间站总空间的百分比。
//20954指:pid
//1000指:每1000毫秒查询一次,一直查- S0 S1两个Survivor区使用情况,E是Eden区,O是老年代,M是元空间,YGC是Young GC发生次数,YGCT是Young GC时间,后面同理,GCT是GC总时长
- ![image-20230420122050808](/Users/effy/Library/Application Support/typora-user-images/image-20230420122050808.png)
查看hprof文件找出导致频繁GC的原因,查看哪些占用空间多的类,比如如果一个HashMap的元素有很多个,占用内存过多,明显不正常
JVM和Java进程的关系
一个JVM实例本身就作为操作系统的一个进程,因此JVM实例由操作系统管理。Java 进程和 JVM 进程是一种包含关系,也就是说,JVM 进程是 Java 进程中的一个子进程。当 Java 程序启动时,操作系统会为 Java 进程分配一块内存空间作为其虚拟地址空间,并在其中创建 JVM 实例。JVM 实例作为 Java 程序的运行时环境,会使用 Java 进程分配的内存空间来管理 Java 程序的执行和资源分配
因此,Java 进程可以被看作是包含 JVM 进程的容器。在 Java 进程中,JVM 进程是负责 Java 程序执行的主要组成部分,负责加载 Java 程序,解释 Java 字节码并生成本地机器代码,管理 Java 程序运行时的内存和资源等。同时,Java 进程也会包含其他组件,如 Java 类库、应用程序配置信息等
为什么线程间不能共享栈
不同线程可能会调用相同的方法,方法本身并没有状态,根据调用者传进来的参数执行方法。栈帧内有局部变量表,根据这些局部变量表才能实现对方法的不同调用,如果共享栈会导致多线程对方法调用的实效,线程之间的隔离性被破坏。
中断线程有哪些方法
- 标识位
- 设置一个标识位,将线程的运行代码放到这个while循环中
- 当标识位被设置为false时,线程结束
interrupt()
+isInterrupted()
Thread.interrupt()
:在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态Thread.isInterrupted()
:判断目标线程是否被中断,不会清除中断标记
interrupt()+InterruptException
- 跟2差不多,接收到中断异常结束线程
synchronized底层实现原理
synchronized
同步语句块的实现使用的是 monitorenter
和 monitorexit
指令,其中 monitorenter
指令指向同步代码块的开始位置,monitorexit
指令则指明同步代码块的结束位置。
synchronized
修饰的方法并没有 monitorenter
指令和 monitorexit
指令,取得代之的是 ACC_SYNCHRONIZED
标识,该标识指明了该方法是一个同步方法。
不过两者的本质都是对对象监视器 monitor 的获取
synchronized和Lock的区别
- synchronized是关键字,其实现依赖于JVM,并没有暴露给我们看
- Lock是一个接口,具体有不同的实现类,是在API层面实现的,是直接暴露给我们的,以具体实现类ReetrantLock为例
- 等待可中断:
ReentrantLock
提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()
来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情 - 可实现公平锁:
ReentrantLock
可以指定是公平锁还是非公平锁,而synchronized
只能是非公平锁。ReentrantLock
默认情况是非公平的,可以通过ReentrantLock
类的ReentrantLock(boolean fair)
构造方法来制定是否是公平的 - 可实现选择性通知:
synchronized
关键字与wait()
和notify()
/notifyAll()
方法相结合可以实现等待/通知机制。ReentrantLock
类当然也可以实现,但是需要借助于Condition
接口与newCondition()
方法
- 等待可中断:
SpringBoot使用三级缓存解决循环依赖
- singletonObjects:缓存某个beanName对应的经过了完整生命周期的bean
- earlySingletonObjects:缓存提前拿原始对象进行了AOP之后得到的代理对象,原始对象还没有进行属性注入和后续的BeanPostProcessor等生命周期
- singletonFactories:缓存的是一个ObjectFactory,主要用来去生成原始对象进行了AOP之后得到的代理对象,在每个Bean的生成过程中,都会提前暴露一个工厂,这个工厂可能用到,也可能用不到,如果没有出现循环依赖依赖本bean,那么这个工厂无用,本bean按照自己的生命周期执行,执行完后直接把本bean放入singletonObjects中即可,如果出现了循环依赖依赖了本bean,则另外那个bean执行ObjectFactory提交得到一个AOP之后的代理对象(如果有AOP的话,如果无需AOP,则直接得到一个原始对象)。
- 其实还要一个缓存,就是earlyProxyReferences,它用来记录某个原始对象是否进行过AOP了。
二级缓存能解决循环依赖吗?
如果使用二级缓存解决循环依赖,即所有的Bean在实例化后就要完成AOP代理,违背了Spring设计原则。Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理
这个工厂的目的在于延迟对实例化阶段生成的对象的代理,只有真正发生循环依赖的时候,才去提前生成代理对象,否则只会创建一个工厂并将其放入到三级缓存中,但是不会去通过这个工厂去真正创建对象
SpringBoot两种代理方式各自的缺陷
JDK动态代理
- 不支持代理没有实现接口的类
CGLIB
- 不支持代理被声明为
final
的类
Spingboot和Spring的区别
MySQL慢查询如何优化
B+树的缺点
随着叶子节点的分裂,逻辑上连续的叶子节点在物理上往往不连续,甚至分离的很远,但做范围查询时,会产生大量随机IO
MySQL联合索引
- 假设当前数据库建立联合索引
(name,age,class)
,索引页内部先按照name排序、name相同时再按age排序、age相同时再按class排序,即name
全局有序,age
和class
局部有序 - 联合索引命中
where name=‘effy’ and age=19 and class=2
,那么使用联合索引进行二分查找,先查找到name=‘effy'
,再查询age=19
,再class=2
where age=19 and class = 2 and name=‘effy’
, 数据库会自动优化查询条件顺序,因此联合索引同样命中
- 联合索引部分命中
where name > 'effy' and name < 'lyf'
,只有name
字段使用到联合索引where name > 'effy' and name < 'lyf' and age=19
,此时只有name
字段使用到联合索引,age
字段是不会使用的。因为先按照name
条件查出来的数据中age不是有序的,比如先按照name条件查出来三条数据(effy,19)
,(effy,20)
,(lyf,19)
,此时age并不有序,因此只能对其进行扫描where name >= 'effy' and age=19
,name
和age
都使用到联合索引,因为name=‘effy'时
,命中age=19
,此时继续对age进行二分查找,从name = 'effy' and age=19
的第一条数据往后扫描,而不是从name=effy
的第一条数据开始where name BETWEEN 'effy' and 'lyf' and age=19
,同上where name LIKE 'e%' and age=19
,同上上
- 联合索引失效情况
where name='effy' or age=19
,由于是or
连接,联合索引失效where age=19 and class=2
,因为联合索引首先按照name
进行排序,但查询条件中不存在name
字段,所以索引失效
总结
联合索引的最左匹配原则,在遇到范围查询(如 >、<)的时候,就会停止匹配,范围查询的字段可以用到联合索引,但是范围查询字段后的字段无法用到联合索引。(但对于 >=、<=、BETWEEN、LIKE前缀匹配的范围查询,并不会停止匹配)
联合索引和单列索引查找区别
- 现有查询条件
name=‘effy’ and age=19 and class=2
- 分别对name、age和class建立单列索引
- 当有多个索引命中后,优化器选择最高效的索引。比如是
name
索引,那么先按照name
索引查出所有name=‘effy’
的主键,再到主键索引进行回表操作,查出所有数据,再对这些数据进行扫描,丢弃不满足age=19
或者class=2
的数据
- 当有多个索引命中后,优化器选择最高效的索引。比如是
- 对name,age,class建立联合索引
- 直接扫描该联合索引的b+树,首先二分查找找到name=’effy‘,然后二分查找找到age=19,再二分查找找到class=2
- 分别对name、age和class建立单列索引
为什么推荐使用自增id而不推荐使用uid或者身份证号等
B+Tree是自底向上插入的,我们优先会将数据插入到叶子节点中,然后整个树会根据底部的叶子节点进行分裂或者合并
- 当我们使用的是自增id,叶子节点链表会根据当前最后一条的位置,将最新的一条数据顺序的插入到最后
- 但是当插入一个uid时,mysql根本不知道他该插入到哪个位置,需要从头开始寻找插入的位置。但是当中间需要插入的页满了时,会造成页的分裂和合并,极大地影响了效率
为什么叶子结点存放的是主键id而不是数据
- 节省存储空间。叶子结点存数据会导致一份数据存了多份,空间占用翻倍。
- 维护一致性。都通过主键索引来找到最终的数据,避免维护多份数据导致不一致的情况
Hash索引的缺点
- Hash索引仅仅能满足等值查询,不能使用范围查询。由于Hash索引存放的是进行Hash运算之后的Hash值 ,经过相应的Hash算法处理之后的Hash值的大小关系,并不能保证和Hash运算前完全一样
- Hash索引不能利用部分索引键查询。 对于联合索引,Hash索引在计算Hash值的时候是联合索引键合并后再一起计算Hash值,而不是单独计算Hash值,所以通过联合索引的前面一个或几个索引键进行查询的时候,Hash索引也无法被利用
- Hash索引无法被用来避免数据的排序操作。由于Hash索引中存放的是经过Hash计算之后的Hash值,而Hash值的大小关系并不一定和Hash运算前的键值完全一样,所以数据库无法利用索引的数据来避免任何排序运算
MySQL锁的相关问题
数据库通过什么方式保证了事务的隔离性
通过加锁来实现事务的隔离性
锁是基于什么实现的
数据库里面的锁是基于索引实现的,在Innodb中我们的锁都是作用在索引上面的,当我们的SQL命中索引时,那么锁住的就是命中条件内的索引节点(行锁),如果没有命中索引的话,那我们锁的就是整个索引树(表锁)
锁的分类
- 基于锁的属性分类
- 共享锁、排他锁
- 基于锁的粒度分类
- 表锁、行锁(记录锁、间隙锁、临键锁)
- 基于锁的状态分类
- 意向共享锁、意向排它锁
MySQL三大日志
redolog
- 作用:MySQl崩溃或宕机恢复,崩溃恢复能力
- 三种刷盘策略:
InnoDB
存储引擎有一个后台线程,每隔1
秒,就会把redo log buffer
中的内容写到文件系统缓存(page cache
),然后调用fsync
刷盘- 0:事务提交时不刷盘
- 1:事务提交时刷盘
- 2:事务提交时只把 redo log buffer写到page cache里
- 二阶段提交
- 更新数据后,写入redolog,并设为prepare阶段
- 提交事务阶段,先写入binlog
- 再将redolog设为commited阶段
binlog
- 作用:集群间保证数据一致性
- 三种日志格式
- statement:记录sql原文
- row:不是简单的
SQL
语句,还包含操作的具体数据,将now()替换成数据 - mixed:
MySQL
会判断这条SQL
语句是否可能引起数据不一致,如果是,就用row
格式,否则就用statement
格式
- 写入流程:write,是指把日志写入到文件系统的 page cache,并没有把数据持久化到磁盘:fsync,才是将数据持久化到磁盘的操作
- 三种刷盘策略:
- 0:每次提交事务都只write,系统决定何时fsync
- 1:每次提交事务都fsync
- N:累计N个事务后再fsync
undolog
- 作用:保证事务的原子性,事务回滚
MySQL事务隔离级别
- 脏读
- 不可重复读
- 幻读