2022-10月更新,基础知识内容

master
1897 3 years ago
parent 1324b369c0
commit f58e641bc9

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

@ -11,10 +11,38 @@
基本运算有一套底层逻辑 基本运算有一套底层逻辑
>>取相反数a=-b+1 >>取相反数a=-b+1
### 不带符号右移>>> ### 不带符号右移>>,一般用这个
### 除2除2+1
>- a>>1
>- a>>1|1
### 几种排序的区别
>- 冒泡排序:相邻比较
>- 选择排序: 找到最值,放入位置
>- 插入排序,在已经排好的基础上继续排序
### 数组累加和
>- 建立二维数组
>- 将二维数组的值表示对应一维数组的和
>- ![img_5.png](img_5.png)
>
> -前缀和数组
> 建立一维数组,后项等于本数组前项+目标同数组下标的值
> ![img_6.png](img_6.png)
> 1~8的累加和=H[8]-H[0]
>- H[R]-H[L-1]
>- 机器人深度搜索
![img_7.png](img_7.png)
>-
>- ![img_8.png](img_8.png)
>
> ![img_9.png](img_9.png)
![img_10.png](img_10.png)
![img_11.png](img_11.png)
![img_12.png](img_12.png)

@ -0,0 +1,27 @@
### 蒙特卡洛模拟X平方x三次方
-> Math.random函数
-> math.max(a,b)函数
### 随机数的产生
-> f()等概率生产随机数 intMath.random
-> 将函数改造为 01 发生器
-> 采用位运算生成数字的位
-> 利用左移合并生成的数字
-> 遇到不合适的数字就重做
### 通过不等概率,返回等概率
-> 两次返回的相同则重做,两次相反则保留
-> p*(1-p) 返回0
-> (1-p)*p 返回1
-> 实现对数器,生成随机样本能够检验数据
### 二分法查找数字
-> 确定循环条件
-> 调整边界状态
### 二分法查找最左数字,即有重复数字出现
-> 调整二分边界状态,存最小索引
### 归并排序
-> 先左右排序(递归)
-> 后合并merge

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

@ -36,5 +36,15 @@ Integer.MAX_VALUE 2^31-1
### hashmap扩容 ### hashmap扩容
>- ![img_3.png](img_3.png) >- ![img_3.png](img_3.png)
>- 在扩容时会出现死循环 >- 在扩容时会出现死循环
>-
### 接口
>- 必须实现全部的抽象方法
>- 接口可以多继承
>- 接口中直接带有方法体1.8 默认方法、静态方法(必须接口调用)、私有方法
>- 父类,接口 方法名字一样,优先父类方法
>- 多继承的方法可以重复,如果有默认方法需要在实现里面重写
### 拷贝
![img_4.png](img_4.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

@ -0,0 +1,24 @@
### 模拟退火算法
- >算法简介
- 通过降温使得晶粒重排有序
![img.png](img.png)
- 模拟退火算法的目的
- ![img_1.png](img_1.png)
- 求解非凸函数的最优解,防止局部最优解
- 求解大规模组合优化
- 模拟退火的流程
- ![img_2.png](img_2.png)
- 在温度高时随机调整,在温度低时有序调整
- 在温度高时有概率接受更差的解
- ![img_3.png](img_3.png)
- 候选解的方式,一般采用安装概率目睹函数对解空间进行随机采样
- 均匀分布,高斯分布,指数分布
![img_4.png](img_4.png)
- 优点:以一定概率接受恶化解,可能出现更好的最优解
- ![img_5.png](img_5.png)
- ![img_7.png](img_7.png)
- 缺点:
- ![img_6.png](img_6.png)
- 改进的模拟退火算法
- ![img_8.png](img_8.png)

@ -0,0 +1,47 @@
### 七层模型详解open system internetwork
- ![img.png](img.png)
- 物理层:电器规范、机械规范、网络光纤、铜轴电缆、光模块
- 数据链路:格式化数据,在物理介质上传输
- 网络层(地图): 路由数据包、寻址IP 、交换机
- 传输层TCP 确保数据安全、端到端连接
- 会话层:应用程序会话
- 表述层:格式化数据、提供加密、解析
- 应用层TCP 为应用进程提供网络服务、提供身份验证
### 路由
- ![img_1.png](img_1.png)
- IP地址唯一标识了网络中的一个节点每个IP地址都拥有自己的网段各个网段可能分布在网络的不同区域。
为实现IP寻址分布在不同区域的网段之间要能够相互通信。
- 路由是指导报文转发的路径信息通过路由可以确认转发IP报文的路径。
- 路由设备是依据路由转发报文到目的网段的网终设备,最常见的路由设备∶路由器。
- 路由设备维护着一张路由表,保存着路由信息。
### HTTP
- HTTP是要基于TCP连接基础上的
- HTTP是用来收发数据即实际应用上来的。
- 第一发送一个HTTP请求给主机A这个请求包括**请求头和请求内容**
- 请求的方法是POST/GET,
- 1.请求的URLhttp协议版本
- 2.请求的数据,和编码方式;
- 3.是否有cookie和cooies是否缓存等。
- 第二主机B收到了HTTP请求然后根据请求头返回HTTP响应。
- cookies或者sessions
- 状态码;
- 内容大小等;
- 主机A收到了以后就由浏览器完成一系列的渲染包括执行JS脚本等。
- HTTP是应用层协议定义的是传输数据的内容的规范
- HTTP协议中的数据是利用TCP协议传输的所以支持HTTP也就一定支持TCP
### HTTPS工作流程
- 1、TCP 三次同步握手
- 2、客户端验证服务器数字证书 CA证书第三方发布 公钥与私钥
- 3、DH 算法协商对称加密算法的密钥、hash 算法的密钥
- 4、SSL 安全加密隧道协商完成
- 5、网页以加密的方式传输
### 对称加密与非对称加密
- ![img_4.png](img_4.png)
- ![img_5.png](img_5.png)
- 加密与解密的密钥不同
- ![img_6.png](img_6.png)

@ -0,0 +1,26 @@
### 三HTTP和TCP的关系
- TCP是传输层协议而HTTP是应用层协议
- TCP是底层通讯协议定义的是数据传输和连接方式的规范
- TCP/IP实际上是一组协议它包括上百个各种功能的协议远程登录、文件传输和电子邮件等而TCP协议和IP协议是保证数据完整传输的两个基本的重要协议。通常说TCP/IP是Internet协议族而不单单是TCP和IP。
- TCP就是单纯建立连接不涉及任何我们需要请求的实际数据简单的传输。
- 要和服务端连接TCP连接需要通过三次连接包括请求确认建立连接。
### 三次握手建立连接
- ![img_2.png](img_2.png)
### 单工、全双工、半双工
- 单工:收音机
- 半双工:不能同时说话、对讲机
- 全双工可以同时收发双绞线8根线
### 三次握手
- 目的:确保双线畅通
- syn标识标志第几次握手
- 第一次客户端序列号随机数seq=x回应序列号ack=0syn标识表示探测询问
- 第二次服务端seq=yack=x+1表示收到ack标识表示收到syn标识表示收到
- 第三次客户端seq=x+1表示收到ack=y+1ack标识表示收到
- ![img_3.png](img_3.png)
- 第一次握手:客户端尝试连接服务器,向服务器发送 syn 包同步序列编号Synchronize Sequence Numberssyn=j客户端进入 SYN_SEND 状态等待服务器确认
- 第二次握手服务器接收客户端syn包并确认ack=j+1同时向客户端发送一个 SYN包syn=k即 SYN+ACK 包,此时服务器进入 SYN_RECV 状态
- 第三次握手第三次握手客户端收到服务器的SYN+ACK包向服务器发送确认包ACK(ack=k+1此包发送完毕客户端和服务器进入ESTABLISHED状态完成三次握手
- 第三次握手的时候,是可以携带数据的。 但是,第一次、第二次握手不可以携带数据

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 KiB

@ -0,0 +1,6 @@
### 邓老师springMVC源码
-> 适配器
-> servlet
-> 9大核心组件的初始化
->init方法
-> service方法

@ -0,0 +1,463 @@
## 1.6. MySQL中的锁
InnoDB中锁非常多总的来说可以如下分类
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1653286288069/c4dad088c5534409930be499ded99b42.png)![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1653286288069/f72d5746a3464c3dae9e71b1d2b35ccc.png)
这些锁都是做什么的?具体含义是什么?我们现在来一一学习。
### 1.6.1.解决并发事务问题
我们已经知道事务并发执行时可能带来的各种问题,最大的一个难点是:一方面要最大程度地利用数据库的并发访问,另外一方面还要确保每个用户能以一致的方式读取和修改数据,尤其是一个事务进行读取操作,另一个同时进行改动操作的情况下。
### 1.6.2.并发事务问题
一个事务进行读取操作,另一个进行改动操作,我们前边说过,这种情况下可能发生脏读、不可重复读、幻读的问题。
怎么解决脏读、不可重复读、幻读这些问题呢?其实有两种可选的解决方案:
#### 1.6.2.1.方案一读操作MVCC写操作进行加锁
**事务利用MVCC进行的读取操作称之为一致性读或者一致性无锁读也称之为快照读**但是往往读取的是历史版本数据。所有普通的SELECT语句plain SELECT在READ COMMITTED、REPEATABLE READ隔离级别下都算是一致性读。
一致性读并不会对表中的任何记录做加锁操作,其他事务可以自由的对表中的记录做改动。
很明显采用MVCC方式的话**读-写操作彼此并不冲突,性能更高****采用加锁方式的话,读-写操作彼此需要排队执行,影响性能**。一般情况下我们当然愿意采用MVCC来解决读-写操作并发执行的问题,但是业务在某些情况下,要求必须采用加锁的方式执行。
#### 1.6.2.2方案二:读、写操作都采用加锁的方式
**适用场景:**
业务场景不允许读取记录的旧版本,而是每次都必须去读取记录的最新版本,
比方在银行存款的事务中,你需要先把账户的余额读出来,然后将其加上本次存款的数额,最后再写到数据库中。在将账户余额读取出来后,就不想让别的事务再访问该余额,直到本次存款事务执行完成,其他事务才可以访问账户的余额。这样在读取记录的时候也就需要对其进行加锁操作,这样也就意味着读操作和写操作也像写-写操作那样排队执行。
**脏读**的产生是因为当前事务读取了另一个未提交事务写的一条记录,如果另一个事务在写记录的时候就给这条记录加锁,那么当前事务就无法继续读取该记录了,所以也就不会有脏读问题的产生了。
**不可重复读**的产生是因为当前事务先读取一条记录,另外一个事务对该记录做了改动之后并提交之后,当前事务再次读取时会获得不同的值,如果在当前事务读取记录时就给该记录加锁,那么另一个事务就无法修改该记录,自然也不会发生不可重复读了。
**幻读问题**的产生是因为当前事务读取了一个范围的记录,然后另外的事务向该范围内插入了新记录,当前事务再次读取该范围的记录时发现了新插入的新记录,我们把新插入的那些记录称之为幻影记录。采用加锁的方式解决幻读问题就有不太容易了,因为当前事务在第一次读取记录时那些幻影记录并不存在,所以读取的时候加锁就有点麻烦—— 因为并不知道给谁加锁。InnoDB中是如何解决的我们后面会讲到。
### 1.6.3锁定读LockingReads/LBCC
也称当前读, 读取的是最新版本, 并且对读取的记录加锁, 阻塞其他事务同时改动相同记录,避免出现安全问题。
哪些是当前读呢select lock in share mode (共享锁)、select for update (排他锁)、update (排他锁)、insert (排他锁/独占锁)、delete (排他锁)、串行化事务隔离级别都是当前读。
当前读这种实现方式也可以称之为LBCC基于锁的并发控制Lock-Based Concurrency Control怎么做到
#### 1.6.3.1. 共享锁和独占锁
在使用加锁的方式解决问题时,由于既要允许读-读情况不受影响,又要使写-写、读-写或写-读情况中的操作相互阻塞MySQL中的锁有好几类
**共享锁**英文名Shared Locks简称S锁。在事务要读取一条记录时需要先获取该记录的S锁。
假如事务E1首先获取了一条记录的S锁之后事务E2接着也要访问这条记录
如果事务E2想要再获取一个记录的S锁那么事务E2也会获得该锁也就意味着事务E1和E2在该记录上同时持有S锁。
**独占锁,**也常称**排他锁**英文名Exclusive Locks简称X锁。在事务要改动一条记录时需要先获取该记录的X锁。
如果事务E2想要再获取一个记录的X锁那么此操作会被阻塞直到事务E1提交之后将S锁释放掉。
如果事务E1首先获取了一条记录的X锁之后那么不管事务E2接着想获取该记录的S锁还是X锁都会被阻塞直到事务E1提交。
所以我们说S锁和S锁是兼容的S锁和X锁是不兼容的X锁和X锁也是不兼容的画个表表示一下就是这样
X 不兼容X 不兼容S
S 不兼容X 兼容S
#### 1.6.3.2.锁定读的SELECT语句
MySQ有两种比较特殊的SELECT语句格式
```
SELECT * from test LOCK IN SHARE MODE;
```
一个事务中开启S锁
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/a2a38c121e4e44e6a2045fec1724254e.png)
另一个事务中开启S锁可以读
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/b647ccce3edf4313b64b844139d7199d.png)
如果另外一个事务中开启X锁阻塞
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/02a271b4b3864be7a8a873888c11fc3d.png)
也就是在普通的SELECT语句后边加LOCK IN SHARE MODE如果当前事务执行了该语句那么它会为读取到的记录加S锁这样允许别的事务继续获取这些记录的S锁比方说别的事务也使用SELECT ... LOCK IN SHARE MODE语句来读取这些记录但是不能获取这些记录的X锁比方说使用SELECT ... FOR UPDATE语句来读取这些记录或者直接修改这些记录
如果别的事务想要获取这些记录的X锁那么它们会阻塞直到当前事务提交之后将这些记录上的S锁释放掉。
对读取的记录加X锁
```
SELECT * from test FOR UPDATE;
```
也就是在普通的SELECT语句后边加FOR UPDATE如果当前事务执行了该语句那么它会为读取到的记录加X锁这样既不允许别的事务获取这些记录的S锁比方说别的事务使用SELECT ... LOCK IN SHARE MODE语句来读取这些记录也不允许获取这些记录的X锁比如说使用SELECT ... FOR UPDATE语句来读取这些记录或者直接修改这些记录
一个事务中开启X锁
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/28116a3e03264a399c6fa5eea78b8497.png)
另外一个事务中的X锁阻塞
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/644044fd54f746b29faa4f3580cce5e9.png)
除非第一个事务提交
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/6e1d9b6a68c749609dc88477960b6fcb.png)
另外一个事务才能获得X锁
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/49662552879242f4b8e34ee640700e93.png)
同样如果另外一个事务执行X锁使用S锁也不行
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/27229c3d97f74246888b858f2d0c2e70.png)
如果别的事务想要获取这些记录的S锁或者X锁那么它们会阻塞直到当前事务提交之后将这些记录上的X锁释放掉。
#### 1.2.1.3. 写操作的锁
平常所用到的写操作无非是DELETE、UPDATE、INSERT这三种
**DELETE**
对一条记录做DELETE操作的过程其实是先在B+树中定位到这条记录的位置然后获取一下这条记录的X锁然后再执行delete mark操作。我们也可以把这个定位待删除记录在B+树中位置的过程看成是一个获取X锁的锁定读。
**INSERT**
一般情况下新插入一条记录的操作并不加锁InnoDB通过一种称之为隐式锁来保护这条新插入的记录在本事务提交前不被别的事务访问。当然在一些特殊情况下INSERT操作也是会获取锁的具体情况我们后边再说。
**UPDATE**
在对一条记录做UPDATE操作时分为三种情况
1、如果未修改该记录的键值并且被更新的列占用的存储空间在**修改前后未发生变化**则先在B+树中定位到这条记录的位置然后再获取一下记录的X锁最后在原记录的位置进行修改操作。其实我们也可以把这个定位待修改记录在B+树中位置的过程看成是一个**获取X锁的锁定读**。
2、如果未修改该记录的键值并且至少有一个被更新的列占用的存储空间在**修改前后发生变化**则先在B+树中定位到这条记录的位置然后获取一下记录的X锁将该记录彻底删除掉就是把记录彻底移入垃圾链表最后再插入一条新记录。这个定位待修改记录在B+树中位置的过程看成是一个**获取X锁的锁定读**新插入的记录由INSERT操作提供的**隐式锁进行保护**。
3、如果修改了该记录的键值则相当于在原记录上做DELETE操作之后再来一次INSERT操作加锁操作就需要按照DELETE和INSERT的规则进行了。
### 1.6.4锁的粒度
我们前边提到的锁都是针对记录的也可以被称之为行级锁或者行锁对一条记录加锁影响的也只是这条记录而已我们就说这个锁的粒度比较细其实一个事务也可以在表级别进行加锁自然就被称之为表级锁或者表锁对一个表加锁影响整个表中的记录我们就说这个锁的粒度比较粗。给表加的锁也可以分为共享锁S锁和独占锁X锁
#### 1.6.4.1.表锁与行锁的比较
**锁定粒度:表锁 > 行锁**
**加锁效率:表锁 > 行锁**
**冲突概率:表锁 > 行锁**
**并发性能:表锁 < 行锁**
#### 1.6.4.2.给表加S锁
**如果一个事务给表加了S锁那么**
别的事务可以继续获得该表的S锁
别的事务可以继续获得该表中的某些记录的S锁
别的事务不可以继续获得该表的X锁
别的事务不可以继续获得该表中的某些记录的X锁
#### 1.6.4.3.给表加X锁
**如果一个事务给表加了X锁意味着该事务要独占这个表那么**
别的事务不可以继续获得该表的S锁
别的事务不可以继续获得该表中的某些记录的S锁
别的事务不可以继续获得该表的X锁
别的事务不可以继续获得该表中的某些记录的X锁。
为了更好的理解这个表级别的S锁和X锁和后面的意向锁我们举一个现实生活中的例子。我们用曾经很火爆的互联网风口项目共享Office来说明加锁
共享Office有栋大楼楼自然有很多层。办公室都是共享的客户可以随便选办公室办公。每层楼可以容纳客户同时办公每当一个客户进去办公就相当于在每层的入口处挂了一把S锁如果很多客户进去办公相当于每层的入口处挂了很多把S锁类似行级别的S锁
有的时候楼层会进行检修比方说换地板换天花板检查水电啥的这些维修项目并不能同时开展。如果楼层针对某个项目进行检修就不允许客户来办公也不允许其他维修项目进行此时相当于楼层门口会挂一把X锁类似行级别的X锁
上边提到的这两种锁都是针对楼层而言的,不过有时候我们会有一些特殊的需求:
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/021b5f9cea2e4bd4a06ec5cca86f440d.png)
A、有投资人要来考察Office的环境。
投资人和公司并不想影响客户进去办公但是此时不能有楼层进行检修所以可以在大楼门口放置一把S锁类似表级别的S锁。此时
来办公的客户们看到大楼门口有S锁可以继续进入大楼办公。
修理工看到大楼门口有S锁则先在大楼门口等着啥时候投资人走了把大楼的S锁撤掉再进入大楼维修。
B、公司要和房东谈条件。
此时不允许大楼中有正在办公的楼层也不允许对楼层进行维修。所以可以在大楼门口放置一把X锁类似表级别的X锁。此时
来办公的客户们看到大楼门口有X锁则需要在大楼门口等着啥时候条件谈好把大楼的X锁撤掉再进入大楼办公。
修理工看到大楼门口有X锁则先在大楼门口等着啥时候谈判结束把大楼的X锁撤掉再进入大楼维修。
### 1.6.5.意向锁
但是在上面的例子这里头有两个问题:
如果我们想对大楼整体上S锁首先需要确保大楼中的没有正在维修的楼层如果有正在维修的楼层需要等到维修结束才可以对大楼整体上S锁。
如果我们想对大楼整体上X锁首先需要确保大楼中的没有办公的楼层以及正在维修的楼层如果有办公的楼层或者正在维修的楼层需要等到全部办公的同学都办公离开以及维修工维修完楼层离开后才可以对大楼整体上X锁。
我们在对大楼整体上锁表锁怎么知道大楼中有没有楼层已经被上锁行锁了呢依次检查每一楼层门口有没有上锁那这效率也太慢了吧于是InnoDB提出了一种意向锁英文名Intention Locks
**意向共享锁** 英文名Intention Shared Lock简称IS锁。当事务准备在某条记录上加S锁时需要先在表级别加一个IS锁。
**意向独占锁** 英文名Intention Exclusive Lock简称IX锁。当事务准备在某条记录上加X锁时需要先在表级别加一个IX锁。
视角回到大楼和楼层上来:
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/524d4de2c0b94fe9a5daa7618b5d5fe1.png)
如果有客户到楼层中办公那么他先在整栋大楼门口放一把IS锁表级锁然后再到楼层门口放一把S锁行锁
如果有维修工到楼层中维修那么它先在整栋大楼门口放一把IX锁表级锁然后再到楼层门口放一把X锁行锁
之后:
如果有投资人要参观大楼也就是想在大楼门口前放S锁表锁首先要看一下大楼门口有没有IX锁如果有意味着有楼层在维修需要等到维修结束把IX锁撤掉后才可以在整栋大楼上加S锁。
如果有谈条件要占用大楼也就是想在大楼门口前放X锁表锁首先要看一下大楼门口有没有IS锁或IX锁如果有意味着有楼层在办公或者维修需要等到客户们办完公以及维修结束把IS锁和IX锁撤掉后才可以在整栋大楼上加X锁。
注意: 客户在大楼门口加IS锁时是不关心大楼门口是否有IX锁的维修工在大楼门口加IX锁时是不关心大楼门口是否有IS锁或者其他IX锁的。IS和IX锁只是为了判断当前时间大楼里有没有被占用的楼层用的也就是在对大楼加S锁或者X锁时才会用到。
**总结一下**IS、IX锁是表级锁它们的提出仅仅为了在之后加表级别的S锁和X锁时可以快速判断表中的记录是否被上锁以避免用遍历的方式来查看表中有没有上锁的记录。就是说其实IS锁和IX锁是兼容的IX锁和IX锁是兼容的。我们画个表来看一下**表级别**的各种锁的兼容性:
| 兼容性 | X | IX | S | IS |
| ------ | ------ | ------ | ------ | ------ |
| X | 不兼容 | 不兼容 | 不兼容 | 不兼容 |
| IX | 不兼容 | | 不兼容 | |
| S | 不兼容 | 不兼容 | | |
| IS | 不兼容 | | | |
锁的组合性:(**意向锁没有行锁**
| 组合性 | X | IX | S | IS |
| ------ | -- | -- | -- | -- |
| 表锁 | 有 | 有 | 有 | 有 |
| 行锁 | 有 | | 有 | |
### 1.6.6.MySQL中的行锁和表锁
MySQL支持多种存储引擎不同存储引擎对锁的支持也是不一样的。当然我们重点还是讨论InnoDB存储引擎中的锁其他的存储引擎只是稍微看看。
#### 1.6.6.1.其他存储引擎中的锁
对于MyISAM、MEMORY、MERGE这些存储引擎来说它们只支持表级锁而且这些引擎并不支持事务所以使用这些存储引擎的锁一般都是针对当前会话来说的。比方说在Session 1中对一个表执行SELECT操作就相当于为这个表加了一个表级别的S锁如果在SELECT操作未完成时Session 2中对这个表执行UPDATE操作相当于要获取表的X锁此操作会被阻塞直到Session 1中的SELECT操作完成释放掉表级别的S锁后Session 2中对这个表执行UPDATE操作才能继续获取X锁然后执行具体的更新语句。
因为使用MyISAM、MEMORY、MERGE这些存储引擎的表在同一时刻只允许一个会话对表进行写操作所以这些存储引擎实际上最好用在只读或者大部分都是读操作或者单用户的情景下。
另外在MyISAM存储引擎中有一个称之为Concurrent Inserts的特性支持在对MyISAM表读取时同时插入记录这样可以提升一些插入速度。关于更多Concurrent Inserts的细节详情可以参考文档。
#### 1.6.6.2.InnoDB存储引擎中的锁
InnoDB存储引擎既支持表锁也支持行锁。表锁实现简单占用资源较少不过粒度很粗有时候你仅仅需要锁住几条记录但使用表锁的话相当于为表中的所有记录都加锁所以性能比较差。行锁粒度更细可以实现更精准的并发控制。下边我们详细看一下。
##### 1.6.6.2.1.InnoDB中的表级锁
###### 1.6.6.2.1.1.表级别的S锁、X锁、元数据锁
在对某个表执行SELECT、INSERT、DELETE、UPDATE语句时InnoDB存储引擎是不会为这个表添加表级别的S锁或者X锁的。
另外在对某个表执行一些诸如ALTER TABLE、DROP TABLE这类的DDL语句时其他事务对这个表并发执行诸如SELECT、INSERT、DELETE、UPDATE的语句会发生阻塞同理某个事务中对某个表执行SELECT、INSERT、DELETE、UPDATE语句时在其他会话中对这个表执行DDL语句也会发生阻塞。这个过程其实是通过在server层使用一种称之为元数据锁英文名Metadata Locks简称MDL来实现的一般情况下也不会使用InnoDB存储引擎自己提供的表级别的S锁和X锁。
其实这个InnoDB存储引擎提供的表级S锁或者X锁是相当鸡肋只会在一些特殊情况下比方说崩溃恢复过程中用到。不过我们还是可以手动获取一下的比方说在系统变量autocommit=0innodb_table_locks = 1时手动获取InnoDB存储引擎提供的表t的S锁或者X锁可以这么写
LOCK TABLES t
READInnoDB存储引擎会对表t加表级别的S锁。
LOCK TABLES t
WRITEInnoDB存储引擎会对表t加表级别的X锁。
**请尽量避免在使用InnoDB存储引擎的表上使用LOCK TABLES这样的手动锁表语句它们并不会提供什么额外的保护只是会降低并发能力而已。**
###### 1.6.6.2.1.2.表级别的IS锁、IX锁
当我们在对使用InnoDB存储引擎的表的某些记录加S锁之前那就需要先在表级别加一个IS锁当我们在对使用InnoDB存储引擎的表的某些记录加X锁之前那就需要先在表级别加一个IX锁。
IS锁和IX锁的使命只是为了后续在加表级别的S锁和X锁时判断表中是否有已经被加锁的记录以避免用遍历的方式来查看表中有没有上锁的记录。我们并不能手动添加意向锁只能由InnoDB存储引擎自行添加。
###### 1.6.6.2.1.3.表级别的AUTO-INC锁
在使用MySQL过程中我们可以为表的某个列添加AUTO_INCREMENT属性之后在插入记录时可以不指定该列的值系统会自动为它赋上递增的值系统实现这种自动给AUTO_INCREMENT修饰的列递增赋值的原理主要是两个
1、采用AUTO-INC锁也就是在执行插入语句时就在表级别加一个AUTO-INC锁然后为每条待插入记录的AUTO_INCREMENT修饰的列分配递增的值在该语句执行结束后再把AUTO-INC锁释放掉。这样一个事务在持有AUTO-INC锁的过程中其他事务的插入语句都要被阻塞可以保证一个语句中分配的递增值是连续的。
如果我们的插入语句在执行前不可以确定具体要插入多少条记录无法预计即将插入记录的数量比方说使用INSERT ... SELECT、REPLACE ... SELECT或者LOAD DATA这种插入语句一般是使用AUTO-INC锁为AUTO_INCREMENT修饰的列生成对应的值。
2、采用一个轻量级的锁在为插入语句生成AUTO_INCREMENT修饰的列的值时获取一下这个轻量级锁然后生成本次插入语句需要用到的AUTO_INCREMENT列的值之后就把该轻量级锁释放掉并不需要等到整个插入语句执行完才释放锁。
如果我们的插入语句在执行前就可以确定具体要插入多少条记录比方说我们上边举的关于表t的例子中在语句执行前就可以确定要插入2条记录那么一般采用轻量级锁的方式对AUTO_INCREMENT修饰的列进行赋值。这种方式可以避免锁定表可以提升插入性能。
InnoDB提供了一个称之为innodb_autoinc_lock_mode的系统变量来控制到底使用上述两种方式中的哪种来为AUTO_INCREMENT修饰的列进行赋值当innodb_autoinc_lock_mode值为0时一律采用AUTO-INC锁当innodb_autoinc_lock_mode值为2时一律采用轻量级锁当innodb_autoinc_lock_mode值为1时两种方式混着来也就是在插入记录数量确定时采用轻量级锁不确定时使用AUTO-INC锁
**不过当innodb_autoinc_lock_mode值为2时可能会造成不同事务中的插入语句为AUTO_INCREMENT修饰的列生成的值是交叉的在有主从复制的场景中是不安全的。**
```
show variables like 'innodb_autoinc_lock_mode' ;
```
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1653286288069/deb3ad3775c149f2943b98331cf9ed59.png)
MySQL5.7.X中缺省为1。
#### 1.6.7.2.InnoDB中的行级锁
行锁也称为记录锁顾名思义就是在记录上加的锁。但是要注意这个记录指的是通过给索引上的索引项加锁。InnoDB 这种行锁实现特点意味着:**只有通过索引条件检索数据InnoDB 才使用行级锁否则InnoDB 将使用表锁。**
不论是使用主键索引、唯一索引或普通索引InnoDB都会使用行锁来对数据加锁。
只有执行计划真正使用了索引,才能使用行锁:**即便在条件中使用了索引字段,但是否使用索引来检索数据是由 MySQL 通过判断不同执行计划的代价来决定的,如果 MySQL认为全表扫描效率更高比如对一些很小的表它就不会使用索引这种情况下 InnoDB** **将使用表锁,而不是行锁。**
同时当我们用范围条件而不是相等条件检索数据并请求锁时InnoDB会给符合条件的已有数据记录的索引项加锁。
不过即使是行锁InnoDB里也是分成了各种类型的。换句话说即使对同一条记录加行锁如果类型不同起到的功效也是不同的。我们使用前面的teacher增加一个索引并插入几条记录。
```
INDEX `idx_number`(`number`)
```
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1653286288069/d449e4f972f642c1a4b67c354e9f12b3.png)
我们来看看都有哪些常用的行锁类型。
**Record Locks**
也叫记录锁就是仅仅把一条记录锁上官方的类型名称为LOCK_REC_NOT_GAP。比方说我们把number值为6的那条记录加一个记录锁的示意图如下
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1653286288069/36ea705892b34f8ca4542ae51ec34680.png)
记录锁是有S锁和X锁之分的当一个事务获取了一条记录的S型记录锁后其他事务也可以继续获取该记录的S型记录锁但不可以继续获取X型记录锁当一个事务获取了一条记录的X型记录锁后其他事务既不可以继续获取该记录的S型记录锁也不可以继续获取X型记录锁
**Gap Locks**
我们说MySQL在REPEATABLE READ隔离级别下是可以解决幻读问题的解决方案有两种可以使用MVCC方案解决也可以采用加锁方案解决。但是在使用加锁方案解决时有问题就是事务在第一次执行读取操作时那些幻影记录尚不存在我们无法给这些幻影记录加上记录锁。InnoDB提出了一种称之为Gap Locks的锁官方的类型名称为LOCK_GAP我们也可以简称为gap锁。
**间隙锁实质上是对索引前后的间隙上锁,不对索引本身上锁。**
会话1开启一个事务执行
```
begin;
update teacher set domain ='JVM' where number='6';
```
会对2~6之间和6到10之间进行上锁。
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1653286288069/0428f43b0117482387536f42688dda55.png)
如图中为2~6和 6 ~ 10的记录加了gap锁意味着不允许别的事务在这条记录前后间隙插入新记录。
```
begin;
insert into teacher value(7,'晁','docker');
```
为什么不能插入因为记录7,'晁','docker')要 插入的话在索引idx_number上刚好落在6 ~ 10之间是有锁的当然不允许插入。
但是当SQL语句变为insert
into teacher value(70,'晁','docker');能插入吗?
当然能因为70这条记录不在被锁的区间内。
### 1.6.7.死锁
#### 1.6.7.1.概念
是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。
举个例子A和B去按摩洗脚都想在洗脚的时候同时顺便做个头部按摩13技师擅长足底按摩14擅长头部按摩。
这个时候A先抢到14B先抢到13两个人都想同时洗脚和头部按摩于是就互不相让扬言我死也不让你这样的话A抢到14想要13B抢到13想要14在这个想同时洗脚和头部按摩的事情上A和B就产生了死锁。怎么解决这个问题呢
第一种假如这个时候来了个15刚好也是擅长头部按摩的A又没有两个脑袋自然就归了B于是B就美滋滋的洗脚和做头部按摩剩下A在旁边气鼓鼓的这个时候死锁这种情况就被打破了不存在了。
第二种C出场了用武力强迫A和B必须先做洗脚再头部按摩这种情况下A和B谁先抢到13谁就可以进行下去另外一个没抢到的就等着这种情况下也不会产生死锁。
所以总结一下:
死锁是必然发生在多操作者M>=2个情况下争夺多个资源N>=2个且N<=M才会发生这种情况。很明显单线程自然不会有死锁只有B一个去不要2个打十个都没问题单资源呢只有13A和B也只会产生激烈竞争打得不可开交谁抢到就是谁的但不会产生死锁。同时死锁还有几个要求1、争夺资源的顺序不对如果争夺资源的顺序是一样的也不会产生死锁
2、争夺者拿到资源不放手。
#### 1.6.7.2.MySQL中的死锁
MySQL中的死锁的成因是一样的。
会话1
```
begin;
select * from
teacher where number = 1 for update;
```
会话2
```
begin;
select * from
teacher where number = 3 for update;
```
会话1
```
select * from teacher where number = 3 for
update;
```
**可以看到这个语句的执行将会被阻塞**
会话2
```
select * from
teacher where number = 1 for update;
```
MySQL检测到了死锁并结束了会话2中事务的执行此时切回会话1发现原本阻塞的SQL语句执行完成了。
同时通过
```
show engine innodb status\G
```
可以看见死锁的详细情况:
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1653286288069/8c39265a4c3c4205a40a6c815a73ffbc.png)
查看事务加锁的情况不过一般情况下看不到哪个事务对哪些记录加了那些锁需要修改系统变量innodb_status_output_locksMySQL5.6.16引入缺省是OFF。
```
show
variables like 'innodb_status_output_locks';
```
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1653286288069/691dfae0b8554417b5e4b85c18767dfd.png)
我们需要设置为ON
```
set global
innodb_status_output_locks = ON;
```
然后开启事务,并执行语句

File diff suppressed because it is too large Load Diff

@ -0,0 +1,60 @@
### Mysql查询优化
>- 慢查询:减少不需要的数据、不取出全部列、减少相同数据的查询
> 原因:数据量大 select * (禁止)
>- 使用覆盖索引
>- 改变数据库表结构(汇总表)
>- 重写复杂查询SQL
>
> mysql记录全部超过 long query time的sql语句的日志
### Mysql中的索引聚族索引、辅助索引定义索引生产B+树
>- InnoDB引擎B+树,哈希索引,全文索引
>- 聚集索引、聚族索引将表的主键构造B+树,存放在叶子节点
>- 如果没有主键mysql创建rowid看不见
>- 索引需要定义辅助索引、id索引、主键索引等等
>- 辅助索引会存放主键、但是不包含全部数据
>- 主键索引会包含全部数据
>- 联合索引多个列组合起来索引减少B+数的数量
>- ![img_5.png](img_5.png)
#### 优化:减少索引数量、联合索引
>-回表:通过辅助索引找到主键、再通过主键再找到其他数据(尽量减少)
>- 查询优化器:对比:二级索引+回表,全表扫描主键
### 索引Index 帮助Mysql高效获取数据的数据结构
>- 哈希索引用HashMap实现索引。缺点范围查找、排序、组合索引、hash冲突
>- B+树=二叉查找(左右子树的值小于根)-平衡高度差不超过1-B树多叉平衡查找树
>- B+:所有数据放在叶子结点,(非叶子结点保存索引,不保存数据)(链表指向相邻结点)Mysql
>- ![img_2.png](img_2.png)
>- ![img_3.png](img_3.png)
>- B树非叶子结点也存放数据、不用链表相连叶子结点查询效率低、范围小
>- B* 非叶子结点也存放指针Oracle用
>- ![img_4.png](img_4.png)
### 平衡二叉树调整
>- 平衡因子:左右高度差,通过旋转:改变高度差(左旋、右旋)
>- ![img.png](img.png)
>- ![img_1.png](img_1.png)
### 慢查询优化
>- 重写复杂的sql查询
>- 把指向sql按时间排序
### 事务和事务隔离级别
>- 事务:整体的执行单位、事务的特性
>- 事务并发引起的问题:脏读问题(回滚)、不可重复读(修改)、幻读(增加)
>- READ UNCOMMITTED :未提交读 READ COMMITTED提交读 REPETABLE READ :可重复读
>- SERIALIZABLE :可串行化
>- 修改SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level;
### 事务的操作
>- 开启,提交,回滚
>- 保存点高级回滚SAVEPOINT 保存点名称;ROLLBACK TO [SAVEPOINT] 保存点名称;
>- 隐式提交:(不同的SQL语言类型)可以不用commit提交
>- 开启下一个事务,上一个事务提交
### mysql中的锁
>- 锁定读for updateX锁、独占锁 in share mode
>- 锁释放时间

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

@ -0,0 +1,675 @@
## 1.4.事务和事务的隔离级别
### 1.4.1.为什么需要事务
事务是数据库管理系统DBMS执行过程中的一个逻辑单位不可再进行分割由一个有限的数据库操作序列构成多个DML语句select语句不包含事务要不全部成功要不全部不成功。
A 给B 要划钱A 的账户-1000元 B 的账户就要+1000元这两个update 语句必须作为一个整体来执行不然A 扣钱了B 没有加钱这种情况就是错误的。那么事务就可以保证A 、B 账户的变动要么全部一起发生,要么全部一起不发生。
### 1.4.2.事务特性
事务应该具有4个属性原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
**l 原子性atomicity**
**l 一致性consistency**
**l 隔离性isolation**
**l 持久性durability**
#### 1.4.2.1.原子性atomicity
一个事务必须被视为一个不可分割的最小单元,整个事务中的所有操作要么全部提交成功,要么全部失败,对于一个事务来说,不能只执行其中的一部分操作。比如:
连老师借给李老师1000元
1.连老师工资卡扣除1000元
2.李老师工资卡增加1000元
整个事务的操作要么全部成功,要么全部失败,不能出现连老师工资卡扣除,但是李老师工资卡不增加的情况。如果原子性不能保证,就会很自然的出现一致性问题。
#### 1.4.2.2.一致性consistency
一致性是指事务将数据库从一种一致性转换到另外一种一致性状态,在事务开始之前和事务结束之后数据库中数据的完整性没有被破坏。
连老师借给李老师1000元
1.连老师工资卡扣除1000元
2.李老师工资卡增加1000元
扣除的钱(-500 与增加的钱500 相加应该为0或者说连老师和李老师的账户的钱加起来前后应该不变。
#### 1.4.2.3.持久性durability
一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,已经提交的修改数据也不会丢失。
#### 1.4.2.4.隔离性isolation
一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
如果隔离性不能保证,会导致什么问题?
连老师借给李老师生活费借了两次每次都是1000连老师的卡里开始有10000李老师的卡里开始有500从理论上借完后连老师的卡里有8000李老师的卡里应该有2500。
我们将连老师向李老师同时进行的两次转账操作分别称为T1和T2在现实世界中T1和T2是应该没有关系的可以先执行完T1再执行T2或者先执行完T2再执行T1结果都是一样的。但是很不幸真实的数据库中T1和T2的操作可能交替执行的执行顺序就有可能是
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/db81c54724b54e749ec5dd801f29e91b.png)
如果按照上图中的执行顺序来进行两次转账的话最终我们看到连老师的账户里还剩9000元钱相当于只扣了1000元钱但是李老师的账户里却成了2500元钱多了10000元这银行岂不是要亏死了
所以对于现实世界中状态转换对应的某些数据库操作来说,不仅要保证这些操作以原子性的方式执行完成,而且要保证其它的状态转换不会影响到本次状态转换,这个规则被称之为隔离性。
### 1.4.3.事务并发引发的问题
我们知道MySQL是一个客户端服务器架构的软件对于同一个服务器来说可以有若干个客户端与之连接每个客户端与服务器连接上之后就可以称之为一个会话Session。每个客户端都可以在自己的会话中向服务器发出请求语句一个请求语句可能是某个事务的一部分也就是对于服务器来说可能同时处理多个事务。
在上面我们说过事务有一个称之为隔离性的特性,理论上在某个事务对某个数据进行访问时,其他事务应该进行排队,当该事务提交之后,其他事务才可以继续访问这个数据,这样的话并发事务的执行就变成了串行化执行。
但是对串行化执行性能影响太大,我们既想保持事务的一定的隔离性,又想让服务器在处理访问同一数据的多个事务时性能尽量高些,当我们舍弃隔离性的时候,可能会带来什么样的数据问题呢?
#### 1.4.3.1.脏读
当一个事务读取到了另外一个事务修改但未提交的数据,被称为脏读。
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/05e67865973a410e93190c52a74be697.png)
1、在事务A执⾏过程中事务A对数据资源进⾏了修改事务B读取了事务A修改后的数据。
2、由于某些原因事务A并没有完成提交发⽣了RollBack操作则事务B读取的数据就是脏数据。
这种读取到另⼀个事务未提交的数据的现象就是脏读(Dirty Read)。
#### 1.4.3.2.不可重复读
当事务内相同的记录被检索两次,且两次得到的结果不同时,此现象称为不可重复读。
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/3895ad68683747e88b114bba40f48ad9.png)
事务B读取了两次数据资源在这两次读取的过程中事务A修改了数据导致事务B在这两次读取出来的
数据不⼀致。
#### 1.4.3.3.幻读
在事务执行过程中,另一个事务将新记录添加到正在读取的事务中时,会发生幻读。
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/3a92feb9aa014a7eaaf784d1c7057822.png)
事务B前后两次读取同⼀个范围的数据在事务B两次读取的过程中事务A新增了数据导致事务B后⼀
次读取到前⼀次查询没有看到的⾏。
幻读和不可重复读有些类似,但是幻读重点强调了读取到了之前读取没有获取到的记录。
### 1.1.4.SQL标准中的四种隔离级别
我们上边介绍了几种并发事务执行过程中可能遇到的一些问题,这些问题也有轻重缓急之分,我们给这些问题按照严重性来排一下序:
脏读 > 不可重复读 > 幻读
我们上边所说的舍弃一部分隔离性来换取一部分性能在这里就体现在设立一些隔离级别隔离级别越低越严重的问题就越可能发生。有一帮人并不是设计MySQL的大叔们制定了一个所谓的SQL标准在标准中设立了4个隔离级别
**READ UNCOMMITTED未提交读。**
**READ COMMITTED已提交读。**
**REPEATABLE READ可重复读。**
**SERIALIZABLE可串行化。**
SQL标准中规定针对不同的隔离级别并发事务可以发生不同严重程度的问题具体情况如下
也就是说:
**READ UNCOMMITTED隔离级别下可能发生脏读、不可重复读和幻读问题。**
**READ COMMITTED隔离级别下可能发生不可重复读和幻读问题但是不可以发生脏读问题。**
**REPEATABLE READ隔离级别下可能发生幻读问题但是不可以发生脏读和不可重复读的问题。**
**SERIALIZABLE隔离级别下各种问题都不可以发生。**
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/21048532674b4486a21b90258b046a15.png)
### 1.4.5.MySQL中的隔离级别
不同的数据库厂商对SQL标准中规定的四种隔离级别支持不一样比方说Oracle就只支持READ COMMITTED和SERIALIZABLE隔离级别。本书中所讨论的MySQL虽然支持4种隔离级别但与SQL标准中所规定的各级隔离级别允许发生的问题却有些出入MySQL在REPEATABLE READ隔离级别下是可以禁止幻读问题的发生的。
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/1452d922eb0f4dda803126784436e8b2.png)
MySQL的默认隔离级别为REPEATABLE READ我们可以手动修改事务的隔离级别。
#### 1.4.5.1.如何设置事务的隔离级别
我们可以通过下边的语句修改事务的隔离级别:
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level;
其中的level可选值有4个
```
level: {
REPEATABLE READ
| READ COMMITTED
| READ UNCOMMITTED
| SERIALIZABLE
}
```
设置事务的隔离级别的语句中在SET关键字后可以放置GLOBAL关键字、SESSION关键字或者什么都不放这样会对不同范围的事务产生不同的影响具体如下
**使用GLOBAL关键字在全局范围影响**
比方说这样:
```
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;
```
则: 只对执行完该语句之后产生的会话起作用。当前已经存在的会话无效。
**使用SESSION关键字在会话范围影响**
比方说这样:
```
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
```
则:对当前会话的所有后续的事务有效
该语句可以在已经开启的事务中间执行,但不会影响当前正在执行的事务。
如果在事务之间执行,则对后续的事务有效。
**上述两个关键字都不用(只对执行语句后的下一个事务产生影响):**
比方说这样:
```
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
```
则:只对当前会话中下一个即将开启的事务有效。下一个事务执行完后,后续事务将恢复到之前的隔离级别。该语句不能在已经开启的事务中间执行,会报错的。
如果我们在服务器启动时想改变事务的默认隔离级别可以修改启动参数transaction-isolation的值比方说我们在启动服务器时指定了--transaction-isolation=SERIALIZABLE那么事务的默认隔离级别就从原来的REPEATABLE READ变成了SERIALIZABLE。
想要查看当前会话默认的隔离级别可以通过查看系统变量transaction_isolation的值来确定
```
SHOW VARIABLES LIKE 'transaction_isolation';
```
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/a863d53bff964d74a21b52de56bc97df.png)
或者使用更简便的写法:
```
SELECT @@transaction_isolation;
```
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/390b1f7f9b7947ee97716cdc08d3848a.png)
注意transaction_isolation是在MySQL 5.7.20的版本中引入来替换tx_isolation的如果你使用的是之前版本的MySQL请将上述用到系统变量transaction_isolation的地方替换为tx_isolation。
### 1.4.6.MySQL事务
#### 1.4.6.1.事务基本语法
**事务开始**
1、begin
2、START TRANSACTION推荐
3、begin work
**事务回滚**
rollback
**事务提交**
commit
使用事务插入两行数据commit后数据还在
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/874c5a5d37c44347827edc01c7646d58.png)
使用事务插入两行数据rollback后数据没有了
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/80e27d57435b4034a20c3ea4566c7674.png)
#### 1.4.6.2.保存点
如果你开启了一个事务执行了很多语句忽然发现某条语句有点问题你只好使用ROLLBACK语句来让数据库状态恢复到事务执行之前的样子然后一切从头再来但是可能根据业务和数据的变化不需要全部回滚。所以MySQL里提出了一个保存点英文savepoint的概念就是在事务对应的数据库语句中打几个点我们在调用ROLLBACK语句时可以指定会滚到哪个点而不是回到最初的原点。定义保存点的语法如下
SAVEPOINT 保存点名称;
当我们想回滚到某个保存点时可以使用下边这个语句下边语句中的单词WORK和SAVEPOINT是可有可无的
```
ROLLBACK TO [SAVEPOINT] 保存点名称;
```
不过如果ROLLBACK语句后边不跟随保存点名称的话会直接回滚到事务执行之前的状态。
如果我们想删除某个保存点,可以使用这个语句:
```
RELEASE SAVEPOINT 保存点名称;
```
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/3dd600ff2c384d5f8e348c8bc33a6c76.png)
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/2e56bef3f9954c3f943168d546ae662f.png)
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/dbca0f38164045eda2543112de99d69b.png)
#### 1.4.6.3.隐式提交
当我们使用START TRANSACTION或者BEGIN语句开启了一个事务或者把系统变量autocommit的值设置为OFF时事务就不会进行自动提交但是如果我们输入了某些语句之后就会悄悄的提交掉就像我们输入了COMMIT语句了一样这种因为某些特殊的语句而导致事务提交的情况称为隐式提交这些会导致事务隐式提交的语句包括
##### 1.4.6.3.1.执行DDL
定义或修改数据库对象的数据定义语言Datadefinition language缩写为DDL
所谓的数据库对象指的就是数据库、表、视图、存储过程等等这些东西。当我们使用CREATE、ALTER、DROP等语句去修改这些所谓的数据库对象时就会隐式的提交前边语句所属于的事务就像这样
```
BEGIN;
SELECT ... # 事务中的一条语句
UPDATE ... # 事务中的一条语句
... # 事务中的其它语句
CREATE TABLE ...
```
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/34acaa6366574b638f74d96d65f76274.png)
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/9a70501b6a0645f389deae88494426fa.png)
**此语句会隐式的提交前边语句所属于的事务**
##### 1.4.6.3.2.隐式使用或修改mysql数据库中的表
当我们使用ALTER USER、CREATE USER、DROP USER、GRANT、RENAME USER、REVOKE、SET PASSWORD等语句时也会隐式的提交前边语句所属于的事务。
##### 1.4.6.3.3.事务控制或关于锁定的语句
当我们在一个会话里一个事务还没提交或者回滚时就又使用START TRANSACTION或者BEGIN语句开启了另一个事务时会隐式的提交上一个事务比如这样
```
BEGIN;
SELECT ... # 事务中的一条语句
UPDATE ... # 事务中的一条语句
... # 事务中的其它语句
BEGIN; # 此语句会隐式的提交前边语句所属于的事务
```
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/6aaf9a1f630c4210996782d1c6fa6a97.png)
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/93a9ef0063704f31b47838b257fb6cf0.png)
或者当前的autocommit系统变量的值为OFF我们手动把它调为ON时也会隐式的提交前边语句所属的事务。
或者使用LOCK TABLES、UNLOCK TABLES等关于锁定的语句也会隐式的提交前边语句所属的事务。
##### 1.4.6.3.4.加载数据的语句
比如我们使用LOAD DATA语句来批量往数据库中导入数据时也会隐式的提交前边语句所属的事务。
##### 1.4.6.3.5.关于MySQL复制的一些语句
使用START SLAVE、STOP SLAVE、RESET SLAVE、CHANGE MASTER TO等语句时也会隐式的提交前边语句所属的事务。
##### 1.4.6.3.6.其它的一些语句
使用ANALYZE TABLE、CACHE INDEX、CHECK TABLE、FLUSH、 LOAD INDEX INTO CACHE、OPTIMIZE TABLE、REPAIR TABLE、RESET等语句也会隐式的提交前边语句所属的事务。
## 1.5. MVCC
全称Multi-Version Concurrency Control即多版本并发控制主要是为了提高数据库的并发性能。
同一行数据平时发生读写请求时会上锁阻塞住。但MVCC用更好的方式去处理读—写请求做到在发生读—写请求冲突时不用加锁。
这个读是指的快照读,而不是当前读,当前读是一种加锁操作,是悲观锁。
那它到底是怎么做到读—写不用加锁的,快照读和当前读是指什么?我们后面都会学到。
### 1.5.1.MVCC原理
#### 1.5.1.1.复习事务隔离级别![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/6a7f761e85854fc0a252a476f20f8235.png)
MySQL在REPEATABLE READ隔离级别下是可以很大程度避免幻读问题的发生的**好像解决了,但是又没完全解决**MySQL是怎么做到的
#### 1.5.1.2.版本链
**必须要知道的概念(每个版本链针对的一条数据):**
我们知道对于使用InnoDB存储引擎的表来说它的聚簇索引记录中都包含两个必要的隐藏列row_id并不是必要的我们创建的表中有主键或者非NULL的UNIQUE键时都不会包含row_id列
trx_id每次一个事务对某条聚簇索引记录进行改动时都会把该事务的事务id赋值给trx_id隐藏列。
roll_pointer每次对某条聚簇索引记录进行改动时都会把旧的版本写入到undo日志中然后这个隐藏列就相当于一个指针可以通过它来找到该记录修改前的信息。
补充点undo日志为了实现事务的原子性InnoDB存储引擎在实际进行增、删、改一条记录时都需要先把对应的undo日志记下来。**一般每对一条记录做一次改动就对应着一条undo日志**但在某些更新记录的操作中也可能会对应着2条undo日志。一个事务在执行过程中可能新增、删除、更新若干条记录也就是说需要记录很多条对应的undo日志这些undo日志会被从0开始编号也就是说根据生成的顺序分别被称为第0号undo日志、第1号undo日志、...、第n号undo日志等这个编号也被称之为undo no。
为了说明这个问题,我们创建一个演示表
```
CREATE TABLE teacher (
number INT,
name VARCHAR(100),
domain varchar(100),
PRIMARY KEY (number)
) Engine=InnoDB CHARSET=utf8;
```
然后向这个表里插入一条数据:
```
INSERT INTO teacher VALUES(1, '李瑾', 'JVM系列');
```
现在表里的数据就是这样的:
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/2ab2e19d985e462e87c1b6e7c50ebc5a.png)
假设插入该记录的事务id为60那么此刻该条记录的示意图如下所示
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/c7b3d5e8c3bd4d91942b0891b0db0956.png)
假设之后两个事务id分别为80、120的事务对这条记录进行UPDATE操作操作流程如下
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/85471f9eaddf4d42a51259b6878056ed.png)
每次对记录进行改动都会记录一条undo日志每条undo日志也都有一个roll_pointer属性INSERT操作对应的undo日志没有该属性因为该记录并没有更早的版本可以将这些undo日志都连起来串成一个链表所以现在的情况就像下图一样
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/c7dc3b48519b4961b04e03594ee80538.png)
对该记录每次更新后都会将旧值放到一条undo日志中就算是该记录的一个旧版本随着更新次数的增多所有的版本都会被roll_pointer属性连接成一个链表我们把这个链表称之为版本链版本链的头节点就是当前记录最新的值。另外每个版本中还包含生成该版本时对应的事务id。**于是可以利用这个记录的版本链来控制并发事务访问相同记录的行为,那么这种机制就被称之为多版本并发控制(Mulit-Version Concurrency Control MVCC)。**
#### 1.5.1.3.ReadView
**必须要知道的概念作用于SQL查询语句**
对于使用READ UNCOMMITTED隔离级别的事务来说由于可以读到未提交事务修改过的记录所以直接读取记录的最新版本就好了**所以就会出现脏读、不可重复读、幻读**)。
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/5e3918aac91a4ee8b6a2de3011021922.png)
对于使用SERIALIZABLE隔离级别的事务来说InnoDB使用加锁的方式来访问记录**也就是所有的事务都是串行的,当然不会出现脏读、不可重复读、幻读**)。
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/82450615bb5b4612aaccdd7008280f5e.png)
对于使用READ COMMITTED和REPEATABLE READ隔离级别的事务来说都必须保证读到已经提交了的事务修改过的记录也就是说假如另一个事务已经修改了记录但是尚未提交是不能直接读取最新版本的记录的核心问题就是READ COMMITTED和REPEATABLE READ隔离级别在不可重复读和幻读上的区别是从哪里来的其实结合前面的知识这两种隔离级别关键**是需要判断一下版本链中的哪个版本是当前事务可见的**。
**为此InnoDB提出了一个ReadView的概念作用于SQL查询语句**
这个ReadView中主要包含4个比较重要的内容
**m_ids**表示在生成ReadView时当前系统中活跃的读写事务的事务id列表。
**min_trx_id**表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id也就是m_ids中的最小值。
**max_trx_id**表示生成ReadView时系统中应该分配给下一个事务的id值。注意max_trx_id并不是m_ids中的最大值事务id是递增分配的。比方说现在有id为123这三个事务之后id为3的事务提交了。那么一个新的读事务在生成ReadView时m_ids就包括1和2min_trx_id的值就是1max_trx_id的值就是4。
**creator_trx_id**表示生成该ReadView的事务的事务id。
#### 1.5.1.4.READ COMMITTED
##### 脏读问题的解决
READ COMMITTED隔离级别的事务在每次查询开始时都会生成一个独立的ReadView。
在MySQL中READ COMMITTED和REPEATABLE READ隔离级别的的一个非常大的区别就是它们生成ReadView的时机不同。
我们还是以表teacher 为例假设现在表teacher 中只有一条由事务id为60的事务插入的一条记录接下来看一下READ COMMITTED和REPEATABLE READ所谓的生成ReadView的时机不同到底不同在哪里。
READ COMMITTED —— 每次读取数据前都生成一个ReadView
比方说现在系统里有两个事务id分别为80、120的事务在执行Transaction 80
```
UPDATE teacher SET name = '马' WHERE number = 1;
UPDATE teacher SET name = '连' WHERE number = 1;
...
```
此刻表teacher 中number为1的记录得到的版本链表如下所示
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/bd1d16d019c9405ab12fca214e271053.png)
假设现在有一个使用READ COMMITTED隔离级别的事务开始执行
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/ce38f77cc7814319b8b8b8e6bf1c7997.png)
```
使用READ COMMITTED隔离级别的事务
BEGIN;
SELECE1Transaction 80、120未提交
SELECT * FROM teacher WHERE number = 1; # 得到的列name的值为'李瑾'
```
第1次select的时间点 如下图:
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1653285598056/2cb53fc2abf649ab88804b8bc4054127.png)
这个SELECE1的执行过程如下
在执行SELECT语句时会先生成一个ReadView
ReadView的m_ids列表的内容就是[80, 120]min_trx_id为80max_trx_id为121creator_trx_id为0。
然后从版本链中挑选可见的记录从图中可以看出最新版本的列name的内容是'**连**'该版本的trx_id值为80在m_ids列表内所以不符合可见性要求trx_id属性值在ReadView的min_trx_id和max_trx_id之间说明创建ReadView时生成该版本的事务还是活跃的该版本不可以被访问如果不在说明创建ReadView时生成该版本的事务已经被提交该版本可以被访问根据roll_pointer跳到下一个版本。
下一个版本的列name的内容是'**马**'该版本的trx_id值也为80也在m_ids列表内所以也不符合要求继续跳到下一个版本。
下一个版本的列name的内容是'**李瑾**'该版本的trx_id值为60小于ReadView中的min_trx_id值所以这个版本是符合要求的最后返回给用户的版本就是这条列name为'**李瑾**'的记录。
**所以有了这种机制,就不会发生脏读问题!因为会去判断活跃版本,必须是不在活跃版本的才能用,不可能读到没有 commit的记录。**
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1653285598056/dd356cf81ec14665b7f11c82ec6021e9.png)
##### 不可重复读问题
然后我们把事务id为80的事务提交一下然后再到事务id为120的事务中更新一下表teacher 中number为1的记录
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/352c5da6030041ef820ba4408c670362.png)
```
Transaction120
BEGIN;
更新了一些别的表的记录
UPDATE teacher SET name = '严' WHERE number = 1;
UPDATE teacher SET name = '晁' WHERE number = 1;
```
此刻表teacher 中number为1的记录的版本链就长这样
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/7202b4c9114b440f9473bcfe9699ab74.png)
然后再到刚才使用READ COMMITTED隔离级别的事务中继续查找这个number为1的记录如下
使用READ COMMITTED隔离级别的事务
```
BEGIN;
SELECE1Transaction 80、120均未提交
SELECT * FROM teacher WHERE number = 1; # 得到的列name的值为'李瑾'
SELECE2Transaction 80提交Transaction 120未提交
SELECT * FROM teacher WHERE number = 1; # 得到的列name的值为'连'
```
**第2次select的时间点 如下图:**
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1653285598056/076acec8fbe7463cb9f2f709327cdfb5.png)
这个SELECE2的执行过程如下
SELECT * FROM teacher WHERE number = 1;
在执行SELECT语句时会又会单独生成一个ReadView该ReadView信息如下
m_ids列表的内容就是[120]事务id为80的那个事务已经提交了所以再次生成快照时就没有它了min_trx_id为120max_trx_id为121creator_trx_id为0。
然后从版本链中挑选可见的记录从图中可以看出最新版本的列name的内容是'**晁**'该版本的trx_id值为120在m_ids列表内所以不符合可见性要求根据roll_pointer跳到下一个版本。
下一个版本的列name的内容是'**严**'该版本的trx_id值为120也在m_ids列表内所以也不符合要求继续跳到下一个版本。
下一个版本的列name的内容是'**连**'该版本的trx_id值为80小于ReadView中的min_trx_id值120所以这个版本是符合要求的最后返回给用户的版本就是这条列name为'**连**'的记录。
以此类推如果之后事务id为120的记录也提交了再次在使用READ COMMITTED隔离级别的事务中查询表teacher 中number值为1的记录时得到的结果就是'**晁**'了,具体流程我们就不分析了。
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1653285598056/e183bd95aed6411aba3ded451871dbff.png)
##### 但会出现不可重复读问题。
明显上面一个事务中两次
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1653285598056/b1232c1150a646eda0637a914716e0fc.png)
#### 1.5.1.5.REPEATABLE READ
##### REPEATABLE READ解决不可重复读问题
REPEATABLE READ —— 在第一次读取数据时生成一个ReadView
对于使用REPEATABLE READ隔离级别的事务来说只会在第一次执行查询语句时生成一个ReadView之后的查询就不会重复生成了。我们还是用例子看一下是什么效果。
比方说现在系统里有两个事务id分别为80、120的事务在执行Transaction 80
```
UPDATE teacher SET name = '马' WHERE number = 1;
UPDATE teacher SET name = '连' WHERE number = 1;
...
```
此刻表teacher 中number为1的记录得到的版本链表如下所示
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/bd1d16d019c9405ab12fca214e271053.png)
假设现在有一个使用REPEATABLE READ隔离级别的事务开始执行
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/ce38f77cc7814319b8b8b8e6bf1c7997.png)
```
使用READ COMMITTED隔离级别的事务
BEGIN;
SELECE1Transaction 80、120未提交
SELECT * FROM teacher WHERE number = 1; # 得到的列name的值为'李瑾'
```
这个SELECE1的执行过程如下
在执行SELECT语句时会先生成一个ReadView
ReadView的m_ids列表的内容就是[80, 120]min_trx_id为80max_trx_id为121creator_trx_id为0。
然后从版本链中挑选可见的记录从图中可以看出最新版本的列name的内容是'**连**'该版本的trx_id值为80在m_ids列表内所以不符合可见性要求trx_id属性值在ReadView的min_trx_id和max_trx_id之间说明创建ReadView时生成该版本的事务还是活跃的该版本不可以被访问如果不在说明创建ReadView时生成该版本的事务已经被提交该版本可以被访问根据roll_pointer跳到下一个版本。
下一个版本的列name的内容是'**马**'该版本的trx_id值也为80也在m_ids列表内所以也不符合要求继续跳到下一个版本。
下一个版本的列name的内容是'**李瑾**'该版本的trx_id值为60小于ReadView中的min_trx_id值所以这个版本是符合要求的最后返回给用户的版本就是这条列name为'**李瑾**'的记录。
之后我们把事务id为80的事务提交一下然后再到事务id为120的事务中更新一下表teacher 中number为1的记录
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/352c5da6030041ef820ba4408c670362.png)
```
Transaction120
BEGIN;
更新了一些别的表的记录
UPDATE teacher SET name = '严' WHERE number = 1;
UPDATE teacher SET name = '晁' WHERE number = 1;
```
此刻表teacher 中number为1的记录的版本链就长这样
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/7202b4c9114b440f9473bcfe9699ab74.png)
然后再到刚才使用REPEATABLE READ隔离级别的事务中继续查找这个number为1的记录如下
使用READ COMMITTED隔离级别的事务
```
BEGIN;
SELECE1Transaction 80、120均未提交
SELECT * FROM teacher WHERE number = 1; # 得到的列name的值为'李瑾'
SELECE2Transaction 80提交Transaction 120未提交
SELECT * FROM teacher WHERE number = 1; # 得到的列name的值为'李瑾'
```
这个SELECE2的执行过程如下
因为当前事务的隔离级别为REPEATABLE READ而之前在执行SELECE1时已经生成过ReadView了所以此时直接复用之前的ReadView之前的ReadView的m_ids列表的内容就是[80, 120]min_trx_id为80max_trx_id为121creator_trx_id为0。
**根据前面的分析,返回的值还是'李瑾'。**
**也就是说两次SELECT查询得到的结果是重复的记录的列name值都是'李瑾',这就是可重复读的含义。**
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/64659425cf56414fba3a80c42c0538b7.png)
**总结一下就是:**
**ReadView中的比较规则(前两条)**
1、如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同意味着当前事务在访问它自己修改过的记录所以该版本可以被当前事务访问。
2、如果被访问版本的trx_id属性值小于ReadView中的min_trx_id值表明生成该版本的事务在当前事务生成ReadView前已经提交所以该版本可以被当前事务访问。
#### 1.5.1.6.MVCC下的幻读解决和幻读现象
前面我们已经知道了REPEATABLE READ隔离级别下MVCC可以解决不可重复读问题那么幻读呢MVCC是怎么解决的幻读是一个事务按照某个相同条件多次读取记录时后读取时读到了之前没有读到的记录而这个记录来自另一个事务添加的新记录。
我们可以想想在REPEATABLE READ隔离级别下的事务T1先根据某个搜索条件读取到多条记录然后事务T2插入一条符合相应搜索条件的记录并提交然后事务T1再根据相同搜索条件执行查询。结果会是什么按照**ReadView中的比较规则(后两条)**
3、如果被访问版本的trx_id属性值大于或等于ReadView中的max_trx_id值表明生成该版本的事务在当前事务生成ReadView后才开启所以该版本不可以被当前事务访问。
4、如果被访问版本的trx_id属性值在ReadView的min_trx_id和max_trx_id之间(min_trx_id < trx_id < max_trx_id)那就需要判断一下trx_id属性值是不是在m_ids列表中如果在说明创建ReadView时生成该版本的事务还是活跃的该版本不可以被访问如果不在说明创建ReadView时生成该版本的事务已经被提交该版本可以被访问。
不管事务T2比事务T1是否先开启事务T1都是看不到T2的提交的。请自行按照上面介绍的版本链、ReadView以及判断可见性的规则来分析一下。
但是在REPEATABLE READ隔离级别下InnoDB中的MVCC 可以很大程度地避免幻读现象,而不是完全禁止幻读。怎么回事呢?我们来看下面的情况:
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/00b3c1b4774c47cb9a2ceb19d921b504.png)
我们首先在事务T1中
```
select * from teacher where number = 30;
```
很明显这个时候是找不到number = 30的记录的。
我们在事务T2中执行
```
insert into teacher values(30,'豹','数据湖');
```
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/f1935b415db04ac482e023c6406f8de3.png)
通过执行insert into teacher values(30,'豹','数据湖');我们往表中插入了一条number = 30的记录。
此时回到事务T1执行
```
update teacher set domain='RocketMQ' where number=30;
select * from teacher where number = 30;
```
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1651212459071/7ca9e16a31d84d5ca72fdb236a95eb28.png)
怎么回事事务T1很明显出现了幻读现象。
在REPEATABLE READ隔离级别下T1第一次执行普通的SELECT 语句时生成了一个ReadView但是版本链没有之后T2向teacher 表中新插入一条记录并提交然后T1也进行了一个update语句。
ReadView并不能阻止T1执行UPDATE 或者DELETE 语句来改动这个新插入的记录但是这样一来这条新记录的trx_id隐藏列的值就变成了T1的事务id。
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/5983/1653285598056/92d29ec669db40b1a07fb0b3cc8cb730.png)
之后T1再使用普通的SELECT 语句去查询这条记录时就可以看到这条记录了也就可以把这条记录返回给客户端。因为这个特殊现象的存在我们也可以认为MVCC 并不能完全禁止幻读(**就是第一次读如果是空的情况,且在自己事务中进行了该条数据的修改**)。
#### 1.5.1.7.MVCC小结
从上边的描述中我们可以看出来所谓的MVCCMulti-Version Concurrency Control 多版本并发控制指的就是在使用READ COMMITTD、REPEATABLE READ这两种隔离级别的事务在执行普通的SELECT操作时访问记录的版本链的过程这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。
READ COMMITTD、REPEATABLE READ这两个隔离级别的一个很大不同就是生成ReadView的时机不同READ COMMITTD在每一次进行普通SELECT操作前都会生成一个ReadView而REPEATABLE READ只在第一次进行普通SELECT操作前生成一个ReadView之后的查询操作都重复使用这个ReadView就好了从而基本上可以避免幻读现象**就是第一次读如果ReadView是空的情况中的某些情况则避免不了**)。
另外所谓的MVCC只是在我们进行普通的SEELCT查询时才生效截止到目前我们所见的所有SELECT语句都算是普通的查询至于什么是个不普通的查询后面马上就会讲到锁定读

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

@ -66,3 +66,34 @@
>- 使用CAS方法获取state >- 使用CAS方法获取state
### 多线程waitnotifynotifyALl线程等待 ### 多线程waitnotifynotifyALl线程等待
### 线程
>- CPU调度的基本单位
>- 目的避免等待IO、网络等待 )操作,提高效率
>- 局限:切换上下文时,小号很大资源
>- 依赖业务场景,不能异构化任务
>- 线程安全问题
### 串行,并行,并发
>- 并行就是同时处理
>- 并发多线程的并发cpu调度线程cpu在不同的线程切换
### 同步与异步
>- 被调用者,主动反馈信息,
>- 异步:主动反馈
### 阻塞与非阻塞
>- 调用者是否等待结果
>- 同步阻塞:用锅烧水,水开不主动通知,需要一直等待着水烧开
>- 同步非阻塞:不需要等待水烧开,时不时查看
>- 异步阻塞: 用水壶烧水,水来了,会有通知,一直等着烧开
>- 异步非阻塞: 用水壶烧水,水来了,会有通知,响应通知
### 线程创建
>- Thread
>- Runnable
>- ![img_2.png](img_2.png)
>- target 就是Runnable一个实现类传入Thread作为有参构造
### run()和start() 的区别
>- start()会创建一个新的线程run是一个具体的执行方法没有新的线程创建。
Loading…
Cancel
Save