1
0
mirror of https://github.com/Snailclimb/JavaGuide synced 2025-07-28 12:22:17 +08:00

Compare commits

...

7 Commits

Author SHA1 Message Date
guide
6cb6f4f39a [docs update]图片引入格式更改 2022-04-21 11:02:04 +08:00
Guide
47f32f3f35
Merge pull request #1698 from WT-AHA/main
[修改]:修改 MySQL事务隔离级别详解 关于幻读的案例演示和解决方案
2022-04-21 10:57:02 +08:00
wangtong
d7021cdd2f [修改]:修改 Spring 设计模式总结 错别字的问题 2022-04-20 18:19:08 +08:00
wangtong
8d83d91e2d [修改]:修改 Spring 事务总结 错别字的问题 2022-04-20 16:47:48 +08:00
wangtong
3f85327ab3 [修改]:修改 Spring 事务总结 多了两个字的问题 2022-04-20 15:54:44 +08:00
wangtong
b88d22cd49 [修改]:修改 Spring 事务总结 多了一个 先 字的问题 2022-04-20 15:25:39 +08:00
wangtong
83e29192b4 [修改]:修改 MySQL事务隔离级别详解 关于幻读的案例演示和解决方案 2022-04-20 15:09:00 +08:00
4 changed files with 35 additions and 44 deletions

View File

@ -288,7 +288,7 @@ O'Reilly 家族书,性能调优的入门书,我个人觉得性能调优是
看过很多网站架构方面的书籍,比如《大型网站技术架构:核心原理与案例分析》、《亿级流量网站架构核心技术》、《架构修炼之道——亿级网关、平台开放、分布式、微服务、容错等核心技术修炼实践》等等。
目前我觉得能推荐的只有李运华老师的**[《从零开始学架构》](https://book.douban.com/subject/30335935/)** 这本。
目前我觉得能推荐的只有李运华老师的 **[《从零开始学架构》](https://book.douban.com/subject/30335935/)** 这本。
![](https://img-blog.csdnimg.cn/20210412224443177.png)

View File

@ -5,7 +5,6 @@ tag:
- MySQL
---
> 本文由 [SnailClimb](https://github.com/Snailclimb) 和 [guang19](https://github.com/guang19) 共同完成。
## 事务隔离级别(图文详解)
@ -14,13 +13,12 @@ tag:
事务是逻辑上的一组操作,要么都执行,要么都不执行。
事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账1000元这个转账会涉及到两个关键操作就是将小明的余额减少1000元将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃导致小明余额减少而小红的余额没有增加这样就不对了。事务就是保证这两个关键操作要么都成功要么都要失败。
事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账 1000 元,这个转账会涉及到两个关键操作就是:将小明的余额减少 1000 元,将小红的余额增加 1000 元。万一在这两个操作之间突然出现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。
### 事务的特性(ACID)
![事务的特性](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/事务特性.png)
1. **原子性:** 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
2. **一致性:** 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;
3. **隔离性:** 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
@ -31,7 +29,7 @@ tag:
在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对统一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题。
- **脏读Dirty read:** 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
- **丢失修改Lost to modify:** 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如事务1读取某表中的数据A=20事务2也读取A=20事务1修改A=A-1事务2也修改A=A-1最终结果A=19事务1的修改被丢失。
- **丢失修改Lost to modify:** 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务 1 读取某表中的数据 A=20事务 2 也读取 A=20事务 1 修改 A=A-1事务 2 也修改 A=A-1最终结果 A=19事务 1 的修改被丢失。
- **不可重复读Unrepeatableread:** 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
- **幻读Phantom read:** 幻读与不可重复读类似。它发生在一个事务T1读取了几行数据接着另一个并发事务T2插入了一些数据时。在随后的查询中第一个事务T1就会发现多了一些原本不存在的记录就好像发生了幻觉一样所以称为幻读。
@ -39,9 +37,9 @@ tag:
不可重复读的重点是修改,幻读的重点在于新增或者删除。
例1同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成事务2中的B先生就修改了A的工资为2000导 致A再读自己的工资时工资变为 2000这就是不可重复读。
1同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务 1 中的 A 先生读取自己的工资为 1000 的操作还没完成,事务 2 中的 B 先生就修改了 A 的工资为 2000导 致 A 再读自己的工资时工资变为 2000这就是不可重复读。
例2同样的条件, 第1次和第2次读出来的记录数不一样 假某工资单表中工资大于3000的有4人事务1读取了所有工资大于3000的人共查到4条记录这时事务2 又插入了一条工资大于3000的记录事务1再次读取时查到的记录就变为了5条这样就导致了幻读。
2同样的条件, 第 1 次和第 2 次读出来的记录数不一样 ):假某工资单表中工资大于 3000 的有 4 人,事务 1 读取了所有工资大于 3000 的人,共查到 4 条记录,这时事务 2 又插入了一条工资大于 3000 的记录,事务 1 再次读取时查到的记录就变为了 5 条,这样就导致了幻读。
### 事务隔离级别
@ -49,17 +47,17 @@ tag:
- **READ-UNCOMMITTED(读取未提交)** 最低的隔离级别,允许读取尚未提交的数据变更,**可能会导致脏读、幻读或不可重复读**。
- **READ-COMMITTED(读取已提交)** 允许读取并发事务已经提交的数据,**可以阻止脏读,但是幻读或不可重复读仍有可能发生**。
- **REPEATABLE-READ(可重复读)** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,**可以阻止脏读和不可重复读,但幻读仍有可能发生**。
- **SERIALIZABLE(可串行化)** 最高的隔离级别完全服从ACID的隔离级别。所有的事务依次逐个执行这样事务之间就完全不可能产生干扰也就是说**该级别可以防止脏读、不可重复读以及幻读**。
- **REPEATABLE-READ(可重复读)** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,**可以阻止脏读和不可重复读,但幻读仍有可能发生**。
- **SERIALIZABLE(可串行化)** 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,**该级别可以防止脏读、不可重复读以及幻读**。
----
---
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
| :---: | :---: | :---:| :---: |
| READ-UNCOMMITTED | √ | √ | √ |
| READ-COMMITTED | × | √ | √ |
| REPEATABLE-READ | × | × | √ |
| SERIALIZABLE | × | × | × |
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
| :--------------: | :--: | :--------: | :--: |
| READ-UNCOMMITTED | | | |
| READ-COMMITTED | × | | |
| REPEATABLE-READ | × | × | |
| SERIALIZABLE | × | × | × |
MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ可重读**。我们可以通过`SELECT @@tx_isolation;`命令来查看MySQL 8.0 该命令改为`SELECT @@transaction_isolation;`
@ -86,9 +84,9 @@ InnoDB 存储引擎在 **分布式事务** 的情况下一般会用到 **SERIALI
### 实际情况演示
在下面我会使用 2 个命令行mysql ,模拟多线程(多事务)对同一份数据的脏读问题。
在下面我会使用 2 个命令行 mysql ,模拟多线程(多事务)对同一份数据的脏读问题。
MySQL 命令行的默认配置中事务都是自动提交的即执行SQL语句后就会马上执行 COMMIT 操作。如果要显式地开启一个事务需要使用命令:`START TRANSACTION`
MySQL 命令行的默认配置中事务都是自动提交的,即执行 SQL 语句后就会马上执行 COMMIT 操作。如果要显式地开启一个事务需要使用命令:`START TRANSACTION`
我们可以通过下面的命令来设置隔离级别。
@ -104,36 +102,29 @@ SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTE
#### 脏读(读未提交)
<div align="center">
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-31-1脏读(读未提交)实例.jpg" width="800px"/>
</div>
![](<https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-31-1脏读(读未提交)实例.jpg>)
#### 避免脏读(读已提交)
<div align="center">
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-31-2读已提交实例.jpg" width="800px"/>
</div>
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-31-2读已提交实例.jpg)
#### 不可重复读
还是刚才上面的读已提交的图,虽然避免了读未提交,但是却出现了,一个事务还没有结束,就发生了 不可重复读问题。
<div align="center">
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-32-1不可重复读实例.jpg"/>
</div>
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-32-1不可重复读实例.jpg)
#### 可重复读
<div align="center">
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-33-2可重复读.jpg"/>
</div>
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-33-2可重复读.jpg)
#### 幻读
##### 演示幻读出现的情况
![](http://101.43.132.98:98/images/phantom_read.png)
![](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/database/phantom_read.png)
sql 脚本1 在第一次查询工资为 500 的记录时只有一条sql 脚本 2 插入了一条工资为 500 的记录提交之后sql 脚本 1 在同一个事务中再次使用当前读查询发现出现了两条工资为 500 的记录这种就是幻读。
sql 脚本 1 在第一次查询工资为 500 的记录时只有一条sql 脚本 2 插入了一条工资为 500 的记录提交之后sql 脚本 1 在同一个事务中再次使用当前读查询发现出现了两条工资为 500 的记录这种就是幻读。
幻读和不可重复读有些相似之处 ,但是不可重复读的重点是修改,幻读的重点在于新增或者删除。
@ -143,13 +134,13 @@ sql 脚本1 在第一次查询工资为 500 的记录时只有一条sql 脚
1. 将事务隔离级别调整为 `SERIALIZABLE`
2. 在可重复读的事务级别下,给事务操作的这张表添加表锁
3. 在可重复读的事务级别下,给事务操作的这张表添加 `Next-Key Locks`
3. 在可重复读的事务级别下,给事务操作的这张表添加 `Next-Key Locks`
> 说明:`Next-Key Locks` 相当于 行锁 + 间隙锁
### 参考
- 《MySQL技术内幕InnoDB存储引擎》
- 《MySQL 技术内幕InnoDB 存储引擎》
- <https://dev.mysql.com/doc/refman/5.7/en/>
- [Mysql 锁:灵魂七拷问](https://tech.youzan.com/seven-questions-about-the-lock-of-mysql/)
- [Innodb 中的事务隔离级别和锁的关系](https://tech.meituan.com/2014/08/20/innodb-lock.html)

View File

@ -5,7 +5,7 @@ tag:
- Spring
---
JDK 中用到了那些设计模式?Spring 中用到了那些设计模式?这两个问题,在面试中比较常见。我在网上搜索了一下关于 Spring 中设计模式的讲解几乎都是千篇一律,而且大部分都年代久远。所以,花了几天时间自己总结了一下,由于我的个人能力有限,文中如有任何错误各位都可以指出。另外,文章篇幅有限,对于设计模式以及一些源码的解读我只是一笔带过,这篇文章的主要目的是回顾一下 Spring 中的设计模式。
JDK 中用到了哪些设计模式?Spring 中用到了哪些设计模式?这两个问题,在面试中比较常见。我在网上搜索了一下关于 Spring 中设计模式的讲解几乎都是千篇一律,而且大部分都年代久远。所以,花了几天时间自己总结了一下,由于我的个人能力有限,文中如有任何错误各位都可以指出。另外,文章篇幅有限,对于设计模式以及一些源码的解读我只是一笔带过,这篇文章的主要目的是回顾一下 Spring 中的设计模式。
Design Patterns(设计模式) 表示面向对象软件开发中最好的计算机编程实践。 Spring 框架中广泛使用了不同类型的设计模式,下面我们来看看到底有哪些设计模式?

View File

@ -70,7 +70,7 @@ public class OrdersService {
这里再多提一下一个非常重要的知识点: **MySQL 怎么保证原子性的?**
我们知道如果想要保证事务的原子性,就需要在异常发生时,对已经执行的操作进行**回滚**,在 MySQL 中,恢复机制是通过 **回滚日志undo log** 实现的,所有事务进行的修改都会先记录到这个回滚日志中,然后再执行相关的操作。如果执行过程中遇到异常的话,我们直接利用 **回滚日志** 中的信息将数据回滚到修改之前的样子即可!并且,回滚日志会先于数据持久化到磁盘上。这样就保证了即使遇到数据库突然宕机等情况,当用户再次启动数据库的时候,数据库还能够通过查询回滚日志来回滚将之前未完成的事务。
我们知道如果想要保证事务的原子性,就需要在异常发生时,对已经执行的操作进行**回滚**,在 MySQL 中,恢复机制是通过 **回滚日志undo log** 实现的,所有事务进行的修改都会先记录到这个回滚日志中,然后再执行相关的操作。如果执行过程中遇到异常的话,我们直接利用 **回滚日志** 中的信息将数据回滚到修改之前的样子即可!并且,回滚日志会先于数据持久化到磁盘上。这样就保证了即使遇到数据库突然宕机等情况,当用户再次启动数据库的时候,数据库还能够通过查询回滚日志来回滚将之前未完成的事务。
### Spring 支持两种方式的事务管理
@ -251,7 +251,7 @@ public interface TransactionDefinition {
`PlatformTransactionManager.getTransaction(…)`方法返回一个 `TransactionStatus` 对象。
**TransactionStatus 接口接口内容如下:**
**TransactionStatus 接口内容如下:**
```java
public interface TransactionStatus{
@ -265,7 +265,7 @@ public interface TransactionStatus{
### 事务属性详解
际业务开发中,大家一般都是使用 `@Transactional` 注解来开启事务,很多人并不清楚这个参数里面的参数是什么意思,有什么用。为了更好的在项目中使用事务管理,强烈推荐好好阅读一下下面的内容。
际业务开发中,大家一般都是使用 `@Transactional` 注解来开启事务,但很多人并不清楚这个注解里面的参数是什么意思,有什么用。为了更好的在项目中使用事务管理,强烈推荐好好阅读一下下面的内容。
#### 事务传播行为
@ -273,7 +273,7 @@ public interface TransactionStatus{
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
举个例子:我们在 A 类的`aMethod`方法中调用了 B 类的 `bMethod()` 方法。这个时候就涉及到业务层方法之间互相调用的事务问题。如果我们的 `bMethod()`如果发生异常需要回滚,如何配置事务传播行为才能让 `aMethod()`也跟着回滚呢?这个时候就需要事务传播行为的知识了,如果你不知道的话一定要好好看一下。
举个例子:我们在 A 类的`aMethod()`方法中调用了 B 类的 `bMethod()` 方法。这个时候就涉及到业务层方法之间互相调用的事务问题。如果我们的 `bMethod()`如果发生异常需要回滚,如何配置事务传播行为才能让 `aMethod()`也跟着回滚呢?这个时候就需要事务传播行为的知识了,如果你不知道的话一定要好好看一下。
```java
Class A {
@ -308,7 +308,7 @@ public interface TransactionDefinition {
}
```
不过如此为了方便使用Spring 相应地定义了一个枚举类:`Propagation`
不过为了方便使用Spring 相应地定义了一个枚举类:`Propagation`
```java
package org.springframework.transaction.annotation;
@ -455,7 +455,7 @@ public interface TransactionDefinition {
}
```
和事务传播行为块一样为了方便使用Spring 也相应地定义了一个枚举类:`Isolation`
和事务传播行为块一样为了方便使用Spring 也相应地定义了一个枚举类:`Isolation`
```java
public enum Isolation {
@ -506,9 +506,9 @@ mysql> SELECT @@tx_isolation;
~~这里需要注意的是:与 SQL 标准不同的地方在于 InnoDB 存储引擎在 **REPEATABLE-READ可重读** 事务隔离级别下使用的是 Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以说 InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ可重读** 已经可以完全保证事务的隔离性要求,即达到了 SQL 标准的 **SERIALIZABLE(可串行化)** 隔离级别。~~
🐛 问题更正:**MySQL InnoDB 的 REPEATABLE-READ可重读并不保证避免幻读需要应用使用加锁读来保证。而这个加锁读使用到的机制就是 Next-Key Locks。**
🐛 问题更正:**MySQL InnoDB 的 REPEATABLE-READ可重读并不保证避免幻读需要使用加锁读来保证。而这个加锁读使用到的机制就是 Next-Key Locks。**
因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 **READ-COMMITTED(读取提交内容)** ,但是你要知道的是 InnoDB 存储引擎默认使用 **REPEAaTABLE-READ可重读** 并不会有任何性能损失。
因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 **READ-COMMITTED(读取提交内容)** ,但是你要知道的是 InnoDB 存储引擎默认使用 **REPEATABLE-READ可重读** 并不会有任何性能损失。
InnoDB 存储引擎在 **分布式事务** 的情况下一般会用到 **SERIALIZABLE(可串行化)** 隔离级别。
@ -518,7 +518,7 @@ InnoDB 存储引擎在 **分布式事务** 的情况下一般会用到 **SERIALI
#### 事务超时属性
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 `TransactionDefinition` 中以 int 的值来表示超时时间,其单位是秒,默认值为-1。
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 `TransactionDefinition` 中以 int 的值来表示超时时间,其单位是秒,默认值为-1,这表示事务的超时时间取决于底层事务系统或者没有超时时间
#### 事务只读属性