详解事务:Mysql事务、Spring事务、分布式事务

详解事务:Mysql事务、Spring事务、分布式事务

【一】Mysql事务

【1】4种隔离级别

(1)序列化
同一时间只能有一个事务执行,所有事务串行执行
(2)可重复读
InnoDB引擎默认的隔离级别,当前事务的变化不会被外部看到
(3)提交读
当前事务可以看到其他事务对数据的修改
(4)未提交读

【2】测试案例

(1)查看全局的隔离级别和查看当前会话的隔离级别(mysql5.7版本的命令)
在这里插入图片描述(2)修改当前会话的隔离级别
在这里插入图片描述(3)测试序列化
先执行会话1的这前两行代码,但是不执行提交事务
在这里插入图片描述然后执行会话2的这前两行代码,也不执行提交事务
在这里插入图片描述会发现事务2的事务开启不了,必须要等前一个事务提交完
在这里插入图片描述执行完会话1的commit后,会话2就能开启事务

安全性最高,但是效率不好

(4)测试可重复读
修改当前会话的隔离级别
在这里插入图片描述然后会话1开启事务,并且查询值,但是不提交事务。此时查询的money结果是99
在这里插入图片描述然后会话2开启事务,并且修改值(此时两个事务可以同时进行了),将money值修改为100。但是不提交事务
在这里插入图片描述
再回到会话1,执行查询语句,money结果还是99
在这里插入图片描述会话1的事务提交完后,重新执行查询,money结果才是100(此时会话2的事务还没有提交)

也就是说,在当前事务中查询的结果不变,即使这个数据已经被其他事务修改过了,当前事务对其他事务的修改是不可见的

(5)可重复读造成的幻读
会话2事务提交成功了,但是因为事务未提交前不可见,所以会话1读到的值跟最终的实际值还是可能不一样。

(6)测试提交读(不可重复读)
会话1的事务提交完后,必须等到会话2的事务也提交完,才能查到会话2修改的值。即使会话2把money改成100了,只要会话2还没有提交事务,会话1读到的就一直是99。

这种隔离级别也可能会出现幻读。

可重复读造成幻读的原因就是会话2事务提交成功,导致会话1读到的值跟最终的实际值不一样。而不可重复读造成的幻读的原因就是会话2修改值后还没有提交事务,会话1就可以读到修改后的值100了,但是如果会话2回滚了呢,最终的真实结果应该还是99。但是会话1已经获取100并使用了

例如两个会话开启事务后,都先执行select语句,都发现没有这个数据,然后都执行一个insert方法插入数据,执行insert就会加锁,那第二个事务进来就会被锁住。

(7)未提交读

【二】Mysql事务实现的原理

【1】事务想要做到什么效果?

事务想要做到的效果主要就是可靠性和并发处理
(1)可靠性
数据库要保证当insert或者update操作的时候,如果抛异常或者数据crash需要保障数据的操作前后一致,想要达到这个效果,就需要知道修改之前和修改之后的状态,所以就有了undo log和redo log

(2)并发处理
就是说当多个并发请求过来,并且其中有一个请求是对数据修改操作的时候会有影响,为了避免读到脏数据,就需要对事务之间的读写进行隔离,至于隔离到什么程度要看业务系统的场景了,实现这个就得用到Mysql的隔离级别

【2】redo log和undo log介绍

(1)什么是redo log
redo log叫做重做日志,是用来实现事务的持久性。该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做
在这里插入图片描述

start transaction;
select balance from bank where name="zhangsan";
// 生成 重做日志 balance=600
update bank set balance = balance - 400; 
// 生成 重做日志 amount=400
update finance set amount = amount + 400;
commit;

在这里插入图片描述
(2)redo log有什么作用?
mysql为了提升性能不会把每次的修改都实时同步到磁盘,而是会先存到Buffer Pool(缓冲池)里面,把这个当做缓存来用,然后使用后台线程去做缓冲池和磁盘之间的同步

那么问题来了,如果还没来得及同步就发生了宕机或断电怎么办?还没来得及执行上面图中红色的操作,这样就会导致丢失部分已提交事务的修改信息。

所以引入了redo log来记录已成功提交事务的修改信息,并且会把redo log持久到磁盘,系统重启后再读取redo log恢复最新数据

(3)总结
redo log是用来恢复数据的,用于保障已提交事务的持久化特性

(1)什么是undo log?
undo log叫做回滚日志,用于记录数据被修改前的信息。它正好跟前面说的重做日志锁记录的相反,重做日志记录数据被修改后的信息。undo log主要记录的是数据的逻辑变化,为了在繁盛错误的时候回滚之前的操作,需要把之前的操作都记录下来,然后再发生错误时才可以回滚。

在这里插入图片描述每次写入数据或者修改数据之前都会把修改前的信息记录到undo log

(2)undo log有什么作用?
undo log记录事务修改之前版本的数据信息,因此加入由于系统错误或者rollback操作而回滚的话可以根据undo log的信息来进行回滚到没被修改前的状态。

(3)总结
undo log是用来回滚数据的,用于保障未提交事务的原子性

【3】mysql锁技术以及MVCC基础

(1)mysql锁技术

当有多个请求来读取表中的数据时,可以不采取任何操作,但是多个请求里有读请求,又有写请求时,必须有一种措施来进行并发控制,不然很有可能会造成数据不一致。

(1)读写锁
解决上面的问题很简单,只需要用两种锁的组合来对读写请求进行控制即可,这两种锁被称为:
1-共享锁(shared lock),又叫做”读锁“
读锁是可以共享的,或者说多个读请求可以共享一把锁读数据,不会造成阻塞

2-排他锁(exclusive lock),又叫做”写锁“
写锁会排斥其他所有获取锁的请求,一致阻塞,直到写入完成释放锁
在这里插入图片描述
(2)总结
通过读写锁,可以做到读读可以并行,但是不能做到写读,写写并行。事务的隔离性就是根据读写锁来实现的。

(2)MVCC基础

MVCC (MultiVersion Concurrency Control) 叫做多版本并发控制。

MVCC在mysql中的实现依赖的是undo log和read view
(1)undo log:undo log中记录某行数据的多个版本的数据
(2)read view:用来判断当前版本数据的可见性
在这里插入图片描述

【4】事务的实现原理

(1)介绍

前面讲的重做日志,回滚日志以及锁技术就是实现事务的基础。
(1)事务的原子性是通过undo log来实现的
(2)事务的持久性是通过redo log来实现的
(3)事务的隔离性是通过(读写锁+MVCC)来实现的
(4)事务的大boss 一致性是通过原子性+持久性+隔离性来实现的

所以说,原子性,持久性,隔离性的目的都是为了保障数据的一致性。总之,ACID只是个概念,事务最终目的是要保障数据的可靠性,一致性。

(2)原子性的实现

(1)什么是原子性
一个事务必须被视为不可分割的最小工作单位,一个事务中的所有操作要么全部成功提交,要么全部失败回滚,对于一个事务来说不可能只执行其中的部分操作,这就是事务的原子性。

上面这段话取自《高性能MySQL》这本书对原子性的定义,原子性可以概括为就是要实现要么全部失败,要么全部成功。

以上概念相信大家伙儿都了解,那么数据库是怎么实现的呢?就是通过回滚操作。
所谓回滚操作就是当发生错误异常或者显式的执行rollback语句时需要把数据还原到原先的模样,所以这时候就需要用到undo log来进行回滚,接下来看一下undo log在实现事务原子性时怎么发挥作用的

(2)undo log的生成
假设有两个表 bank和finance,表中原始数据如图所示,当进行插入,删除以及更新操作时生成的undo log如下面图所示:
在这里插入图片描述从上图可以了解到数据的变更都伴随着回滚日志的产生:
1-产生了被修改前数据(zhangsan,1000) 的回滚日志
2-产生了被修改前数据(zhangsan,0) 的回滚日志

根据上面流程可以得出如下结论:
1-每条数据变更(insert/update/delete)操作都伴随一条undo log的生成,并且回滚日志必须先于数据持久化到磁盘上
2-所谓的回滚就是根据回滚日志做逆向操作,比如delete的逆向操作为insert,insert的逆向操作为delete,update的逆向为update等。

思考:为什么先写日志后写数据库?

(3)根据undo log 进行回滚
为了做到同时成功或者失败,当系统发生错误或者执行rollback操作时需要根据undo log 进行回滚
在这里插入图片描述回滚操作就是要还原到原来的状态,undo log记录了数据被修改前的信息以及新增和被删除的数据信息,根据undo log生成回滚语句,比如:
1-如果在回滚日志里有新增数据记录,则生成删除该条的语句
2-如果在回滚日志里有删除数据记录,则生成生成该条的语句
3-如果在回滚日志里有修改数据记录,则生成修改到原先数据的语句

(3)持久性的实现

事务一旦提交,其所作做的修改会永久保存到数据库中,此时即使系统崩溃修改的数据也不会丢失。

先了解一下MySQL的数据存储机制,MySQL的表数据是存放在磁盘上的,因此想要存取的时候都要经历磁盘IO,然而即使是使用SSD磁盘IO也是非常消耗性能的。
为此,为了提升性能InnoDB提供了缓冲池(Buffer Pool),Buffer Pool中包含了磁盘数据页的映射,可以当做缓存来使用:
(1)读数据:会首先从缓冲池中读取,如果缓冲池中没有,则从磁盘读取在放入缓冲池;
(2)写数据:会首先写入缓冲池,缓冲池中的数据会定期同步到磁盘中;

上面这种缓冲池的措施虽然在性能方面带来了质的飞跃,但是它也带来了新的问题,当MySQL系统宕机,断电的时候可能会丢数据!!!

因为我们的数据已经提交了,但此时是在缓冲池里头,还没来得及在磁盘持久化,所以我们急需一种机制需要存一下已提交事务的数据,为恢复数据使用。

于是 redo log就派上用场了。下面看下redo log是什么时候产生的

在这里插入图片描述
既然redo log也需要存储,也涉及磁盘IO为啥还用它?
(1)redo log 的存储是顺序存储,而缓存同步是随机操作。
(2)缓存同步是以数据页为单位的,每次传输的数据大小大于redo log。

(4)隔离性的实现

1:

隔离性是事务ACID特性里最复杂的一个。在SQL标准里定义了四种隔离级别,每一种级别都规定一个事务中的修改,哪些是事务之间可见的,哪些是不可见的。

级别越低的隔离级别可以执行越高的并发,但同时实现复杂度以及开销也越大。

Mysql 隔离级别有以下四种(级别由低到高):
(1)READ UNCOMMITED (未提交读)
(2)READ COMMITED (提交读)
(3)REPEATABLE READ (可重复读)
(4)SERIALIZABLE (可重复读)

只要彻底理解了隔离级别以及他的实现原理就相当于理解了ACID里的隔离型。前面说过原子性,隔离性,持久性的目的都是为了要做到一致性,但隔离型跟其他两个有所区别,原子性和持久性是为了要实现数据的可性保障靠,比如要做到宕机后的恢复,以及错误后的回滚。

那么隔离性是要做到什么呢?隔离性是要管理多个并发读写请求的访问顺序。这种顺序包括串行或者是并行说明一点,写请求不仅仅是指insert操作,又包括update操作。

在这里插入图片描述总之,从隔离性的实现可以看出这是一场数据的可靠性与性能之间的权衡。
(1)可靠性性高的,并发性能低(比如 Serializable)
(2)可靠性低的,并发性能高(比如 Read Uncommited)

2:READ UNCOMMITTED

在READ UNCOMMITTED隔离级别下,事务中的修改即使还没提交,对其他事务是可见的。事务可以读取未提交的数据,造成脏读。

因为读不会加任何锁,所以写操作在读的过程中修改数据,所以会造成脏读。好处是可以提升并发处理性能,能做到读写并行。

换句话说,读的操作不能排斥写请求。
在这里插入图片描述 优点:读写并行,性能高
缺点:造成脏读

3:READ COMMITTED

一个事务的修改在他提交之前的所有修改,对其他事务都是不可见的。其他事务能读到已提交的修改变化。在很多场景下这种逻辑是可以接受的。

InnoDB在 READ COMMITTED,使用排它锁,读取数据不加锁而是使用了MVCC机制。或者换句话说他采用了读写分离机制。
但是该级别会产生不可重读以及幻读问题。

(1)什么是不可重读?
在一个事务内多次读取的结果不一样。

(2)为什么会产生不可重复读?
这跟 READ COMMITTED 级别下的MVCC机制有关系,在该隔离级别下每次 select的时候新生成一个版本号,所以每次select的时候读的不是一个副本而是不同的副本。

在每次select之间有其他事务更新了我们读取的数据并提交了,那就出现了不可重复读
在这里插入图片描述
(3)REPEATABLE READ(Mysql默认隔离级别)
在一个事务内的多次读取的结果是一样的。这种级别下可以避免,脏读,不可重复读等查询问题。mysql 有两种机制可以达到这种隔离级别的效果,分别是采用读写锁以及MVCC。

采用读写锁实现:
在这里插入图片描述
为什么能可重复度?只要没释放读锁,在次读的时候还是可以读到第一次读的数据。
(1)优点:实现起来简单
(2)缺点:无法做到读写并行

采用MVCC实现:
在这里插入图片描述
为什么能可重复读?因为多次读取只生成一个版本,读到的自然是相同数据。
1-优点:读写并行
2-缺点:实现的复杂度高

但是在该隔离级别下仍会存在幻读的问题,关于幻读的解决我打算另开一篇来介绍。

4:SERIALIZABLE

该隔离级别理解起来最简单,实现也最单。在隔离级别下除了不会造成数据不一致问题,没其他优点。

在这里插入图片描述

(5)一致性的实现

数据库总是从一个一致性的状态转移到另一个一致性的状态.

下面举个例子:zhangsan 从银行卡转400到理财账户

start transaction;
select balance from bank where name="zhangsan";
// 生成 重做日志 balance=600
update bank set balance = balance - 400; 
// 生成 重做日志 amount=400
update finance set amount = amount + 400;
commit;

(1)假如执行完 update bank set balance = balance - 400;之发生异常了,银行卡的钱也不能平白无辜的减少,而是回滚到最初状态。
(2)又或者事务提交之后,缓冲池还没同步到磁盘的时候宕机了,这也是不能接受的,应该在重启的时候恢复并持久化。
(3)假如有并发事务请求的时候也应该做好事务之间的可见性问题,避免造成脏读,不可重复读,幻读等。在涉及并发的情况下往往在性能和一致性之间做平衡,做一定的取舍,所以隔离性也是对一致性的一种破坏。

【5】总结

实现事务采取了哪些技术以及思想?
(1)原子性:使用 undo log ,从而达到回滚
(2)持久性:使用 redo log,从而达到故障后恢复
(3)隔离性:使用锁以及MVCC,运用的优化思想有读写分离,读读并行,读写并行
(4)一致性:通过回滚,以及恢复,和在并发环境下的隔离做到一致性。

【四】Spring声明式事务@Transactional

【1】准备测试代码

(1)添加依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.9.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.allen</groupId>
    <artifactId>transactional</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>transactional</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.21</version>
        </dependency>

        <!--<dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

(2)配置文件

server.port=8081

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/sharding-jdbc-order?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

logging.level.root=debug

(3)添加代码Controller、Service

package com.allen.transactional.Con;

import com.allen.transactional.Service.UserService01;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName: TestController
 * @Author: AllenSun
 * @Date: 2022/12/20 下午10:37
 */
@RestController
public class TestController {

    @Autowired
    UserService01 userService01;

    @GetMapping("/hello")
    public void hello () {
        userService01.transfer();
    }
}
package com.allen.transactional.Service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * @ClassName: UserService01
 * @Author: AllenSun
 * @Date: 2022/12/20 下午10:38
 */
@Service
public class UserService01 {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Autowired
    UserService02 userService02;

    @Transactional
    public void transfer() {
        jdbcTemplate.update("update order_1 set count = ? where user_id=?;",1,101);
        int i=1/0;
    }
}
package com.allen.transactional.Service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

/**
 * @ClassName: UserService01
 * @Author: AllenSun
 * @Date: 2022/12/20 下午10:38
 */
@Service
public class UserService02 {

    @Autowired
    JdbcTemplate jdbcTemplate;

    public void update() {
        jdbcTemplate.update("update order_1 set count = ? where user_id=?;",100,103);
    }

}

(4)测试路径

http://127.0.0.1:8081/hello

(5)查看日志

注意的是在transfer方法中添加了一行int i=1/0;导致抛出异常,触发事务,可以看看日志的详细内容

【2】隔离性

【3】传播性

(1)什么是事务的传播性

事务传播行为是为了解决业务层方法之间互相调用的事务问题,当一个事务方法被另一个事务方法调用时,事务该以何种状态存在?例如新方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行,等等,这些规则就涉及到事务的传播性。

(2)传播性的7种种类

默认是Required。

用的比较多的就是REQUIRED、REQUIRES_NEW、NESTED这三种,90%的情况下使用REQUIRED就可以解决了
在这里插入图片描述

(3)Required(内部方法和外部方法共用同一个事务)

@Transactional注解的默认属性就是REQUIRED
方法和它的内部方法共用同一个事务
(1)如果transfer方法已经开启事务了,那么update方法就不会再开启新的事务,而是加到transfer方法的事务中来
(2)如果transfer方法没有开启事务,那么update才会开启自己的新的事务
(3)如果transfer方法抛出异常也会造成update方法的回滚

在这里插入图片描述
测试前的数据
在这里插入图片描述
测试后的数据没有变化

查看控制台的日志内容

# 创建transfer方法的事务
o.s.j.d.DataSourceTransactionManager     : Creating new transaction with name [com.allen.transactional.Service.UserService01.transfer]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
# 执行tranfer方法的sql
o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [update order_1 set count = ? where user_id=?;]
o.s.jdbc.core.JdbcTemplate               : SQL update affected 1 rows

# 将update方法添加到transfer方法的事务中去
o.s.j.d.DataSourceTransactionManager     : Participating in existing transaction
o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [update order_1 set count = ? where user_id=?;]
o.s.jdbc.core.JdbcTemplate               : SQL update affected 1 rows

# 抛出异常,开始回滚
o.s.j.d.DataSourceTransactionManager     : Initiating transaction rollback
o.s.j.d.DataSourceTransactionManager     : Rolling back JDBC transaction on Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@79ede17d]]]
o.s.j.d.DataSourceTransactionManager     : Releasing JDBC Connection 

因为是把update方法加入到transfer方法的事务中去的,所以两个方法共用一个事务,如果发生回滚的话,两个方法是都要回滚的

(4)Requires_NEW(外部方法和内部方法各自有自己独立的事务)

(1)如果外部有事务,内部也会开启自己的事务,会将外部的事务挂起,内部的事务独自运行。可能会同时存在两个事务
1-transfer方法抛出异常后会回滚,但是update方法不会跟着回滚,因为update方法开启的是自己的事务
2-如果transfer方法正常执行,而update方法抛出异常回滚,那就要看update方法的异常是否会处理掉,如果update的异常被处理了,transfer方法就不会回滚,否则受到update的异常影响也会回滚
(2)如果外部没有事务,内部还是会开启自己的事务

案例一:transfer方法是REQUIRED,update方法是Requires_NEW,两个方法都没有异常

在这里插入图片描述这里有个细节,如果对user_id字段没有加数据库索引,在执行的时候,transfer方法会对表加表锁,那么update方法执行sql的时候就会陷入死锁的情况,访问超时。所以可以给user_id字段加上唯一索引,这样transfer方法就只会加行锁,就不会出现死锁的问题了

查看控制台日志

# 创建transfer方法的事务
2022-12-21 00:01:31.146 DEBUG 65541 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Creating new transaction with name [com.allen.transactional.Service.UserService01.transfer]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
2022-12-21 00:01:31.147 DEBUG 65541 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Acquired Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@5f489349]]] for JDBC transaction
2022-12-21 00:01:31.147 DEBUG 65541 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Switching JDBC Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@5f489349]]] to manual commit
# 准备执行transfer方法的sql
2022-12-21 00:01:31.147 DEBUG 65541 --- [nio-8081-exec-2] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2022-12-21 00:01:31.147 DEBUG 65541 --- [nio-8081-exec-2] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [update order_1 set count = ? where user_id=?;]
2022-12-21 00:01:31.149 DEBUG 65541 --- [nio-8081-exec-2] o.s.jdbc.core.JdbcTemplate               : SQL update affected 1 rows

# 挂起transfer方法的事务,创建update方法的事务
2022-12-21 00:01:31.149 DEBUG 65541 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Suspending current transaction, creating new transaction with name [com.allen.transactional.Service.UserService02.update]
2022-12-21 00:01:31.150 DEBUG 65541 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Acquired Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@201ca654]]] for JDBC transaction
2022-12-21 00:01:31.151 DEBUG 65541 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Switching JDBC Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@201ca654]]] to manual commit
# 准备执行update方法的事务
2022-12-21 00:01:31.151 DEBUG 65541 --- [nio-8081-exec-2] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2022-12-21 00:01:31.151 DEBUG 65541 --- [nio-8081-exec-2] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [update order_1 set count = ? where user_id=?;]
2022-12-21 00:01:31.151 DEBUG 65541 --- [nio-8081-exec-2] o.s.jdbc.core.JdbcTemplate               : SQL update affected 1 rows

# 提交update方法的事务
2022-12-21 00:01:31.152 DEBUG 65541 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Initiating transaction commit
2022-12-21 00:01:31.152 DEBUG 65541 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Committing JDBC transaction on Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@201ca654]]]
2022-12-21 00:01:31.152 DEBUG 65541 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Releasing JDBC Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@201ca654]]] after transaction
2022-12-21 00:01:31.152 DEBUG 65541 --- [nio-8081-exec-2] o.s.jdbc.datasource.DataSourceUtils      : Returning JDBC Connection to DataSource

# 唤醒transfer方法的事务,然后提交
2022-12-21 00:01:31.153 DEBUG 65541 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Resuming suspended transaction after completion of inner transaction
2022-12-21 00:01:31.153 DEBUG 65541 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Initiating transaction commit
2022-12-21 00:01:31.153 DEBUG 65541 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Committing JDBC transaction on Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@5f489349]]]

查看方法执行的结果,两个方法的sql都执行成功了
在这里插入图片描述

案例二:transfer方法是REQUIRED,没有异常;update方法是Requires_NEW,有异常(要看内部方法update的异常有没有被处理)

是因为内部的update方法抛出异常后没有进行处理,导致外部的方法也会出现异常,导致两个方法都会回滚
在这里插入图片描述查看控制台日志

# 创建transfer方法的事务
2022-12-21 00:11:17.147 DEBUG 67348 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Creating new transaction with name [com.allen.transactional.Service.UserService01.transfer]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
2022-12-21 00:11:17.153 DEBUG 67348 --- [nio-8081-exec-2] o.a.tomcat.jdbc.pool.PooledConnection    : Instantiating driver using class: com.mysql.jdbc.Driver [url=jdbc:mysql://127.0.0.1:3306/sharding-jdbc-order?characterEncoding=UTF-8]
2022-12-21 00:11:17.414 DEBUG 67348 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Acquired Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@771338ee]]] for JDBC transaction
2022-12-21 00:11:17.416 DEBUG 67348 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Switching JDBC Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@771338ee]]] to manual commit
# 执行transfer方法的sql
2022-12-21 00:11:17.421 DEBUG 67348 --- [nio-8081-exec-2] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2022-12-21 00:11:17.421 DEBUG 67348 --- [nio-8081-exec-2] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [update order_1 set count = ? where user_id=?;]
2022-12-21 00:11:17.433 DEBUG 67348 --- [nio-8081-exec-2] o.s.jdbc.core.JdbcTemplate               : SQL update affected 1 rows
# 挂起transfer方法的事务,并且创建update方法的事务
2022-12-21 00:11:17.435 DEBUG 67348 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Suspending current transaction, creating new transaction with name [com.allen.transactional.Service.UserService02.update]
2022-12-21 00:11:17.435 DEBUG 67348 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Acquired Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@6e1ad23e]]] for JDBC transaction
2022-12-21 00:11:17.435 DEBUG 67348 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Switching JDBC Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@6e1ad23e]]] to manual commit
# 执行update方法的sql
2022-12-21 00:11:17.437 DEBUG 67348 --- [nio-8081-exec-2] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2022-12-21 00:11:17.437 DEBUG 67348 --- [nio-8081-exec-2] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [update order_1 set count = ? where user_id=?;]
2022-12-21 00:11:17.438 DEBUG 67348 --- [nio-8081-exec-2] o.s.jdbc.core.JdbcTemplate               : SQL update affected 1 rows
# 发现异常,准备回滚update方法
2022-12-21 00:11:17.439 DEBUG 67348 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Initiating transaction rollback
2022-12-21 00:11:17.439 DEBUG 67348 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Rolling back JDBC transaction on Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@6e1ad23e]]]
2022-12-21 00:11:17.440 DEBUG 67348 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Releasing JDBC Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@6e1ad23e]]] after transaction
2022-12-21 00:11:17.440 DEBUG 67348 --- [nio-8081-exec-2] o.s.jdbc.datasource.DataSourceUtils      : Returning JDBC Connection to DataSource

# 唤醒transfer方法的事务
2022-12-21 00:11:17.440 DEBUG 67348 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Resuming suspended transaction after completion of inner transaction
# transfer方法也开始回滚
2022-12-21 00:11:17.440 DEBUG 67348 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Initiating transaction rollback
2022-12-21 00:11:17.440 DEBUG 67348 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Rolling back JDBC transaction on Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@771338ee]]]
2022-12-21 00:11:17.442 DEBUG 67348 --- [nio-8081-exec-2] o.s.j.d.DataSourceTransactionManager     : Releasing JDBC Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@771338ee]]] after transaction

两个方法都发生了回滚,接下来对内部update方法的异常进行处理然后再看update的方法对transfer方法有没有影响
在这里插入图片描述再来看看控制台日志

# 创建transfer方法的事务
2022-12-21 00:22:55.704 DEBUG 68808 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Creating new transaction with name [com.allen.transactional.Service.UserService01.transfer]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
2022-12-21 00:22:55.710 DEBUG 68808 --- [nio-8081-exec-1] o.a.tomcat.jdbc.pool.PooledConnection    : Instantiating driver using class: com.mysql.jdbc.Driver [url=jdbc:mysql://127.0.0.1:3306/sharding-jdbc-order?characterEncoding=UTF-8]
2022-12-21 00:22:55.955 DEBUG 68808 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Acquired Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@559824e7]]] for JDBC transaction
2022-12-21 00:22:55.956 DEBUG 68808 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Switching JDBC Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@559824e7]]] to manual commit
# 执行transfer方法的sql
2022-12-21 00:22:55.959 DEBUG 68808 --- [nio-8081-exec-1] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2022-12-21 00:22:55.959 DEBUG 68808 --- [nio-8081-exec-1] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [update order_1 set count = ? where user_id=?;]
2022-12-21 00:22:55.973 DEBUG 68808 --- [nio-8081-exec-1] o.s.jdbc.core.JdbcTemplate               : SQL update affected 1 rows
# 挂起transfer方法的事务,并且创建update方法的事务
2022-12-21 00:22:55.974 DEBUG 68808 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Suspending current transaction, creating new transaction with name [com.allen.transactional.Service.UserService02.update]
2022-12-21 00:22:55.975 DEBUG 68808 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Acquired Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@6b1e08a1]]] for JDBC transaction
2022-12-21 00:22:55.975 DEBUG 68808 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Switching JDBC Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@6b1e08a1]]] to manual commit
# 执行update方法的sql
2022-12-21 00:22:55.977 DEBUG 68808 --- [nio-8081-exec-1] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2022-12-21 00:22:55.977 DEBUG 68808 --- [nio-8081-exec-1] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [update order_1 set count = ? where user_id=?;]
2022-12-21 00:22:55.979 DEBUG 68808 --- [nio-8081-exec-1] o.s.jdbc.core.JdbcTemplate               : SQL update affected 1 rows
# update方法出现异常,开始回滚update的sql
2022-12-21 00:22:55.979 DEBUG 68808 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Initiating transaction rollback
2022-12-21 00:22:55.979 DEBUG 68808 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Rolling back JDBC transaction on Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@6b1e08a1]]]
2022-12-21 00:22:55.981 DEBUG 68808 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Releasing JDBC Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@6b1e08a1]]] after transaction
2022-12-21 00:22:55.982 DEBUG 68808 --- [nio-8081-exec-1] o.s.jdbc.datasource.DataSourceUtils      : Returning JDBC Connection to DataSource
# 唤醒transfer方法的事务
2022-12-21 00:22:55.982 DEBUG 68808 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Resuming suspended transaction after completion of inner transaction
2022-12-21 00:22:55.982 DEBUG 68808 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Initiating transaction commit
# 提交transfer方法的事务
2022-12-21 00:22:55.982 DEBUG 68808 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Committing JDBC transaction on Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@559824e7]]]
2022-12-21 00:22:55.983 DEBUG 68808 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Releasing JDBC Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@559824e7]]] after transaction

查看结果,可以看到transfer方法修改成功了,update方法没有修改成功
在这里插入图片描述

案例三:transfer方法是REQUIRED,有异常;update方法是Requires_NEW,没有异常

在这里插入图片描述查看控制台日志信息

# 创建transfer方法的事务
2022-12-21 00:31:25.591 DEBUG 69835 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Creating new transaction with name [com.allen.transactional.Service.UserService01.transfer]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
2022-12-21 00:31:25.833 DEBUG 69835 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Acquired Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@17ce73b0]]] for JDBC transaction
2022-12-21 00:31:25.835 DEBUG 69835 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Switching JDBC Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@17ce73b0]]] to manual commit
# 执行transfer方法的sql
2022-12-21 00:31:25.839 DEBUG 69835 --- [nio-8081-exec-1] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2022-12-21 00:31:25.839 DEBUG 69835 --- [nio-8081-exec-1] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [update order_1 set count = ? where user_id=?;]
2022-12-21 00:31:25.852 DEBUG 69835 --- [nio-8081-exec-1] o.s.jdbc.core.JdbcTemplate               : SQL update affected 1 rows
# 挂起transfer方法的事务,创建update方法的事务
2022-12-21 00:31:25.854 DEBUG 69835 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Suspending current transaction, creating new transaction with name [com.allen.transactional.Service.UserService02.update]
2022-12-21 00:31:25.854 DEBUG 69835 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Acquired Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@7f5ec022]]] for JDBC transaction
2022-12-21 00:31:25.854 DEBUG 69835 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Switching JDBC Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@7f5ec022]]] to manual commit
# 执行update方法的sql
2022-12-21 00:31:25.856 DEBUG 69835 --- [nio-8081-exec-1] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2022-12-21 00:31:25.856 DEBUG 69835 --- [nio-8081-exec-1] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [update order_1 set count = ? where user_id=?;]
2022-12-21 00:31:25.856 DEBUG 69835 --- [nio-8081-exec-1] o.s.jdbc.core.JdbcTemplate               : SQL update affected 1 rows
# 提交update方法的事务
2022-12-21 00:31:25.857 DEBUG 69835 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Initiating transaction commit
2022-12-21 00:31:25.857 DEBUG 69835 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Committing JDBC transaction on Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@7f5ec022]]]
2022-12-21 00:31:25.858 DEBUG 69835 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Releasing JDBC Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@7f5ec022]]] after transaction
2022-12-21 00:31:25.858 DEBUG 69835 --- [nio-8081-exec-1] o.s.jdbc.datasource.DataSourceUtils      : Returning JDBC Connection to DataSource
# 唤醒transfer方法的事务
2022-12-21 00:31:25.859 DEBUG 69835 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Resuming suspended transaction after completion of inner transaction
# transfer方法出现异常,开始回滚transfer方法的sql
2022-12-21 00:31:25.859 DEBUG 69835 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Initiating transaction rollback
2022-12-21 00:31:25.859 DEBUG 69835 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Rolling back JDBC transaction on Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@17ce73b0]]]
2022-12-21 00:31:25.861 DEBUG 69835 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Releasing JDBC Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@17ce73b0]]] after transaction
2022-12-21 00:31:25.861 DEBUG 69835 --- [nio-8081-exec-1] o.s.jdbc.datasource.DataSourceUtils      : Returning JDBC Connection to DataSource

查看数据库结果,transfer方法回滚了,没有修改成功,而update方法事务成功提交了,修改成功
在这里插入图片描述

(5)NESTED(外部方法为主事务,内部方法为子事务,事务嵌套)

如果主事务发生了回滚,子事务也会跟着回滚

案例一:transfer方法有异常并回滚,update方法没有异常但是也会回滚

在这里插入图片描述

# 创建transfer方法的事务
2022-12-21 00:43:43.676 DEBUG 71230 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Creating new transaction with name [com.allen.transactional.Service.UserService01.transfer]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
2022-12-21 00:43:43.915 DEBUG 71230 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Switching JDBC Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@5e363a1d]]] to manual commit
# 执行transfer方法的sql
2022-12-21 00:43:43.920 DEBUG 71230 --- [nio-8081-exec-1] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2022-12-21 00:43:43.921 DEBUG 71230 --- [nio-8081-exec-1] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [update order_1 set count = ? where user_id=?;]
2022-12-21 00:43:43.937 DEBUG 71230 --- [nio-8081-exec-1] o.s.jdbc.core.JdbcTemplate               : SQL update affected 1 rows
# 创建嵌套子事务update方法的事务
2022-12-21 00:43:43.938 DEBUG 71230 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Creating nested transaction with name [com.allen.transactional.Service.UserService02.update]
# 执行update方法的sql
2022-12-21 00:43:43.943 DEBUG 71230 --- [nio-8081-exec-1] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2022-12-21 00:43:43.943 DEBUG 71230 --- [nio-8081-exec-1] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [update order_1 set count = ? where user_id=?;]
2022-12-21 00:43:43.944 DEBUG 71230 --- [nio-8081-exec-1] o.s.jdbc.core.JdbcTemplate               : SQL update affected 1 rows
2022-12-21 00:43:43.945 DEBUG 71230 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Releasing transaction savepoint
# transfer方法出现异常,开始回滚,主事务和子事务都会回滚,两个事务都没有提交
2022-12-21 00:43:43.945 DEBUG 71230 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Initiating transaction rollback
2022-12-21 00:43:43.945 DEBUG 71230 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Rolling back JDBC transaction on Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@5e363a1d]]]
2022-12-21 00:43:43.946 DEBUG 71230 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Releasing JDBC Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@5e363a1d]]] after transaction
2022-12-21 00:43:43.946 DEBUG 71230 --- [nio-8081-exec-1] o.s.jdbc.datasource.DataSourceUtils      : Returning JDBC Connection to DataSource

两个方法都没有修改数据库,数据库的数据不变

案例二:transfer方法没有异常,update方法有异常,子事务回滚不影响主事务

给update方法加上异常处理,否则主事务也还是会回滚
在这里插入图片描述

# 创建transfer方法的事务
2022-12-21 00:53:26.042 DEBUG 72498 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Creating new transaction with name [com.allen.transactional.Service.UserService01.transfer]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
# 执行transfer方法的sql
2022-12-21 00:53:26.317 DEBUG 72498 --- [nio-8081-exec-1] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2022-12-21 00:53:26.318 DEBUG 72498 --- [nio-8081-exec-1] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [update order_1 set count = ? where user_id=?;]
2022-12-21 00:53:26.332 DEBUG 72498 --- [nio-8081-exec-1] o.s.jdbc.core.JdbcTemplate               : SQL update affected 1 rows
# 创建内部方法update的子事务
2022-12-21 00:53:26.334 DEBUG 72498 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Creating nested transaction with name [com.allen.transactional.Service.UserService02.update]
# 执行update方法的sql
2022-12-21 00:53:26.339 DEBUG 72498 --- [nio-8081-exec-1] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
2022-12-21 00:53:26.339 DEBUG 72498 --- [nio-8081-exec-1] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [update order_1 set count = ? where user_id=?;]
2022-12-21 00:53:26.340 DEBUG 72498 --- [nio-8081-exec-1] o.s.jdbc.core.JdbcTemplate               : SQL update affected 1 rows
# update方法出现异常,开始回滚
2022-12-21 00:53:26.341 DEBUG 72498 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Rolling back transaction to savepoint
# 提交transfer方法的事务
2022-12-21 00:53:26.342 DEBUG 72498 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Initiating transaction commit
2022-12-21 00:53:26.342 DEBUG 72498 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Committing JDBC transaction on Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@57a99f1]]]
2022-12-21 00:53:26.344 DEBUG 72498 --- [nio-8081-exec-1] o.s.j.d.DataSourceTransactionManager     : Releasing JDBC Connection 

在这里插入图片描述

(6)MANDATORY(强制性)

MANDATORY的方法事务不能独立存在,必须加到一个事务中去
(1)如果transfer方法有事务,那么update方法就加入到transfer方法的事务中去
(2)如果transfer方法没有事务,那么update方法的事务就会报错
在这里插入图片描述

(7)SUPPORTS

(1)如果transfer方法有事务,那么update方法就加入到transfer方法的事务中去
(2)如果transfer方法没有事务,那么update方法的事务加了也没用,直接用非事务的方式继续运行

(8)NOT_SUPPORTED(有事务就挂起)

加了这个属性以后,如果当前方法加了事务,就把事务挂起,以非事务的方式运行

(9)NEVER(禁止添加事务)

加了这个属性以后,如果当前方法加了事务,就会直接抛出异常,必须以非事务的方式运行

【4】回滚规则

什么情况下才会回滚?
只有在遇到运行时异常RuntimeException 的时候才会回滚,如果遇到的是检查性异常,就不会触发回滚

(1)添加一个IOException,那么就不会触发回滚,因为不是运行时异常
在这里插入图片描述
(2)添加rollbackfor参数,修改回滚的规则,指定哪种异常会回滚
这个时候就可以触发回滚了
在这里插入图片描述
(3)也可以用noRollbackFor参数,指定哪种异常不回滚

【5】是否只读

如果一个业务方法只有一个查询sql,那就没有必要添加事务了,添加事务反而会导致执行的效率变慢。

但是如果一个业务方法中有多个查询sql,默认情况下,每个查询sql都会开启一个独立的事务,这个时候如果有并发任务修改了数据的话,一个方法中的多个查询sql可能就会查出不同的结果,多个事务使用隔离级别就无法解决。

开启只读之后,就可以保证多个查询事务的查询结果是一样的
在这里插入图片描述如果只有一个查询sql就没必要添加了,如果有多个查询sql,可以视情况判断是否要添加

【6】超时时间

设置一个超时时间,如果在这个时间内事务还没有执行完,就会自动回滚这个事务,避免出现大事务

在这里插入图片描述

【7】事务失效的场景和原因

(1)事务注解只有加在public方法上才会有效

如果加在private方法,会直接爆红线;如果加在protected方法,虽然不会爆红,但是事务不会生效
在这里插入图片描述

(2)自调用会造成事务失效

没有事务的m1方法内部调用了有事务的transfer方法,但是其他方法调用m1方法的时候,transfer方法的事务会失效
在这里插入图片描述
原因就是事务注解的原理为AOP代理,代理模式只会拦截通过代理传入的方法

注解写到方法的实现类上,不要写到接口上去

(3)

【五】spring声明式事务@Transactional实现的底层原理

【1】原生的事务管理

在没有Spring存在的时候,事务就已经诞生了。其实框架依赖的还是底层提供的能力,只不过它对这一过程的抽象和复用。Spring事务 的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。
这里我们用底层的API来了解下事务管理的过程(JDBC为例):

// 获取mysql数据库连接
Connection conn = DriverManager.getConnection("xxxx");
   conn.setAutoCommit(false);
statement = conn.createStatement();
// 执行sql,返回结果集
resultSet = statement.executeQuery("xxxx");
conn.commit(); //提交
//conn.rollback();//回滚

上面是一个原生操作事务的一个例子,这些过程也是Spring事务逃不开的,只不过在为了编程的效率让这一过程自动化或是透明化的你无法感知罢了。
而我们之后做的就是逐步还原这一自动化的过程。

【2】Spring提供的事务API

Spring提供了很多关于事务的API。但是最为基本的就是 PlatformTransactionManager 、 TransactionDefintion 和 TransactionStatus 。

(1)事务管理器——PlatformTransactionManager

PlatformTransactionManager 是事务管理器的顶层接口。事务的管理是受限于具体的数据源的(例如,JDBC对应的事务管理器就是 DatasourceTransactionManager ),因此 PlatformTransactionManager 只规定了事务的基本操作:创建事务,提交事物和回滚事务。

public interface PlatformTransactionManager extends TransactionManager {
 
    /**
     * 打开事务
     */
	TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
			throws TransactionException;
 
	/**
	 * 提交事务
	 */
	void commit(TransactionStatus status) throws TransactionException;
 
	/**
	 * 回滚事务
	 */
	void rollback(TransactionStatus status) throws TransactionException;
}

同时为了简化事务管理器的实现,Spring提供了一个抽象类 AbstractPlatformTransactionManager ,规定了事务管理器的基本框架,仅将依赖于具体平台的特性作为抽象方法留给子类实现。

(2)事务状态——TransactionStatus

事务状态是我对 TransactionStatus 这个类的直译。其实我觉得这个类可以直接当作事务的超集来看(包含了事务对象,并且存储了事务的状态)。 PlatformTransactionManager.getTransaction() 时创建的也正是这个对象。
这个对象的方法都和事务状态相关:

public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
 
	/**
	 * 是否有Savepoint Savepoint是当事务回滚时需要恢复的状态
	 */
	boolean hasSavepoint();
 
	/**
	 * flush()操作和底层数据源有关,并非强制所有数据源都要支持
	 */
	@Override
	void flush();
 
}

此外, TransactionStatus 还从父接口中继承了其他方法,都归总在下方:

	/**
	 * 是否是新事务(或是其他事务的一部分)
	 */
	boolean isNewTransaction();
 
	/**
	 * 设置rollback-only 表示之后需要回滚
	 */
	void setRollbackOnly();
 
	/**
	 * 是否rollback-only
	 */
	boolean isRollbackOnly();
 
	/**
	 * 判断该事务已经完成
	 */
	boolean isCompleted();
	
	
	/**
	 * 创建一个Savepoint
	 */
	Object createSavepoint() throws TransactionException;
 
	/**
	 * 回滚到指定Savepoint
	 */
	void rollbackToSavepoint(Object savepoint) throws TransactionException;
 
	/**
	 * 释放Savepoint 当事务完成后,事务管理器基本上自动释放该事务所有的savepoint
	 */
	void releaseSavepoint(Object savepoint) throws TransactionException;
 

(3)事务属性的定义——TransactionDefinition

TransactionDefinition 表示一个事务的定义,将根据它规定的特性去开启事务。
事务的传播等级和隔离级别的常量同样定义在这个接口中。

	/**
	 * 返回事务的传播级别
	 */
	default int getPropagationBehavior() {
		return PROPAGATION_REQUIRED;
	}
 
	/**
	 * 返回事务的隔离级别
	 */
	default int getIsolationLevel() {
		return ISOLATION_DEFAULT;
	}
 
	/**
	 * 事务超时时间
	 */
	default int getTimeout() {
		return TIMEOUT_DEFAULT;
	}
 
	/**
	 * 是否为只读事务(只读事务在处理上能有一些优化)
	 */
	default boolean isReadOnly() {
		return false;
	}
 
	/**
	 * 返回事务的名称
	 */
	@Nullable
	default String getName() {
		return null;
	}
 
 
	/**
	 * 默认的事务配置
	 */
	static TransactionDefinition withDefaults() {
		return StaticTransactionDefinition.INSTANCE;
	}

(4)编程式使用Spring事务

有了上述这些API,就已经可以通过编程的方式实现Spring的事务控制了。
但是Spring官方建议不要直接使用 PlatformTransactionManager 这一偏低层的API来编程,而是使用 TransactionTemplate 和 TransactionCallback 这两个偏向用户层的接口。
示例代码如下:

        //设置事务的各种属性;可以猜测TransactionTemplate应该是实现了TransactionDefinition
        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        transactionTemplate.setTimeout(30000);
        
        //执行事务 将业务逻辑封装在TransactionCallback中
        transactionTemplate.execute(new TransactionCallback<Object>() {
            @Override
            public Object doInTransaction(TransactionStatus transactionStatus) {
                    //....   业务代码
            }
        });

以上就是Spring事务最基本的原理。但是为什么这些过程对我们似乎都不可见呢?那是因为这些过程都 通过AOP的方式被织入 了我们的业务逻辑中。
所以,像要深入了解Spring事务原理,还需要了解AOP的原理。

【3】AOP原理

AOP的实现机制有两种:Proxy-based和Weaving-based。
前者是依赖动态代理的方式达到对代理类增强的目的。后者应该是通过字节码增强的方式达到增强的目的。
在Spring中,一般默认使用前者。之后也仅是针对前者进行分析。

而Spring声明AOP的方式也有两种,一种是通过声明Aspect,另一种是通过声明Advisor。
无论是哪种方式,都需要表达清楚你要进行增强的逻辑 (what)和你要增强的地方(where)。即,需要告诉Spring你要增强什么逻辑,并且对哪些Bean/哪些方法增强。

这里的what和where换成AOP中的概念分别就是对应 Advice 和 Pointcut 。

因为事务是通过Advisor声明AOP的,因此本文也只针对Advisor的实现展开分析。

(1)动态代理

既然是动态代理,那么必然存在被代理类(Target),代理类(Proxy),以及类被代理的过程(因为对用户而言,并不知道类被代理了)。

被代理的类

被代理类是最容易知道的,就是那些被Advisor的Pointcut匹配(classFliter匹配或是methodMatches)到的类。

代理的类

而代理类是在运行时直接创建的。通常有两种方式:
(1)JDK的动态代理
(2)CGLIB的动态代理

二者的区别是JDK动态代理是通过实现接口的方式(代理的对象为接口),因此只能代理接口中的方法。

而CGLIB动态代理是通过继承的方式,因此可以对对象中的方法进行代理,但是由于是继承关系,无法代理final的类和方法(无法继承),或是private的方法(对子类不可见)。

创建代理及取代目标类的过程

创建代理及取代目标类主要是应用了Spring容器在获取Bean时留下的一个拓展点。
Spring在 getBean 的时候,如果Bean还不存在会分三步去创建Bean:
(1)实例化
(2)填充属性
(3)初始化
实例化通常是通过反射创建Bean对象的实例,此时得到的 Bean还只是一个空白对象。
填充属性主要是为这个Bean注入其他的Bean,实现自动装配。
而初始化则是让用户可以控制Bean的创建过程。
为Bean创建代理,并取代原有的Bean就是发生在初始化这一步,更具体的是在 BeanPostProcessor.postProcessorAfterInitialization() 中。

在众多的 BeanPostProcessor 中有一类后置处理器就是专门用于创建代理的。例如,我们要介绍的 AbstractAdvisorAutoProxyCreator 。
看一下 AbstractAutoProxyCreator 创建代理的流程:
(1)先确认是否已经创建过代理对象( earlyProxyReferences ,避免对代理对象在进行代理)
(2)如果没有,则考虑是否需要进行代理(通过 wrapIfNecessary )
(3)如果是特殊的Bean 或者之前判断过不用创建代理的Bean则不创建代理
(4)否则看是否有匹配的Advise(匹配方式就是上文介绍的通过PointCut或者IntroducationAdvisor可以直接匹配类)
(5)如果找到了Advisor,说明需要创建代理,进入 createProxy
(6)首先会创建 ProxyFactory ,这个工厂是用来创建AopProxy的,而 AopProxy 才是用来创建代理对象的。因为底层代理方式有两种(JDK动态代理和CGLIB,对应到 AopProxy 的实现就是 JdkDynamicAopProxy 和 ObjenesisCglibAopProxy ),所以这里使用了一个简单工厂的设计。 ProxyFactory 会设置此次代理的属性,然后根据这些属性选择合适的代理方式,创建代理对象。
(7)创建的对象会替换掉被代理对象(Target),被保存在 BeanFactory.singletonObjects ,因此当有其他Bean希望注入Target时,其实已经被注入了Proxy。
以上就是Spring实现动态代理的过程。

【4】spring注解式事务

上文中,我们从编程式事务了解了Spring事务API的基本使用方式,又了解了Spring Advisor的原理。现在,我们在回到Spring注解式事务中,验证下注解式事务是否就是通过以上这些方式隐藏了具体的事务控制逻辑。

(1)从@EnableTransactionManagement说起

@EnableTransactionManagement 是开启注解式事务的事务。如果注解式事务真的有玄机,那么 @EnableTransactionManagement 就是我们揭开秘密的突破口。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
 
	/**
	 * 用来表示默认使用JDK Dynamic Proxy还是CGLIB Proxy
	 */
	boolean proxyTargetClass() default false;
 
	/**
	 * 表示以Proxy-based方式实现AOP还是以Weaving-based方式实现AOP
	 */
	AdviceMode mode() default AdviceMode.PROXY;
 
	/**
	 * 顺序
	 */
	int order() default Ordered.LOWEST_PRECEDENCE;
 
}

@EnableTransactionManagement 注解看起来并没有特别之处,都是一些属性的配置。但它却通过 @Import 引入了另一个配置 TransactionManagentConfigurationSelector 。

(2)TransactionManangementConfigurationSelector

在Spring中, Selector 通常都是用来选择一些Bean,向容器注册BeanDefinition的(严格意义上Selector仅时选择过程,注册的具体过程是在 ConfigurationClasspathPostProcessor 解析时,调用 ConfigurationClassParser 触发)。
主要的逻辑就是根据代理模式,注册不同的BeanDefinition。
对Proxy的模式而言,注入的有两个:
1-AutoProxyRegistrar
2-ProxyTransactionManagementConfiguration

1:AutoProxyRegistrar

Registrar同样也是用来向容器注册Bean的,在Proxy的模式下,它会调用 AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry); 向容器中注册 InfrastructureAdvisorAutoProxyCreator 。而这个类就是我们上文提到的 AbstractAdvisorAutoProxyCreator 的子类。
从而,我们完成了我们的第一个条件——AOP代理。

2:ProxyTransactionManagementConfiguration

ProxyTransactionManagementConfiguration 是一个配置类,如果算上其继承的父类,一共是声明了四个类:
1-TransactionalEventListenerFactory
2-BeanFactoryTransactionAttributeSourceAdvisor
3-TransactionAttributeSource
4-TransactionInterceptor

后三个类相对比较重要,我们一一分析。

(1)BeanFactoryTransactionAttributeSourceAdvisor
从名字看就知道这是一个Advisor,那么它身上应该有Pointcut和Advise。
其中的Pointcut是 TransactionAttributeSourcePointcut ,主要是一些filter和matches之类的方法,用来匹配被代理类。
而Adivise就是我们之后要介绍的 TransactionInterceptor 。

(2)TransactionAttributeSource
TransactionAttributeSource 只是一个接口,扩展了 TransactionDefinition ,增加了 isCandidateClass() 的方法(可以用来帮助Pointcut匹配)。
这里使用的具体实现是 AnnotationTransactionAttributeSource 。因为注解式事务候选类(即要被代理的类)是通过 @Transactional 注解标识的,并且所有的事务属性也都来自 @Transactional 注解。

(3)TransactionInterceptor
刚才我们说了, TransactionInterceptor 就是我们找的Advise。
这个类稍微复杂一点,首先根据事务处理相关的逻辑都放在了其父类 TransactionAspectSupport 中。此外,为了适配动态代理的反射调用(两种代理方式),实现了 MethodInterceptor 接口。
也就是说,反射发起的入口是 MethodInterceptor.invoke() ,而反射逻辑在 TransactionAspectSupport.invokeWithinTransaction() 中。
我们可以简单看 invokeWithTransaction() 方法中的部分代码:

	@Nullable
	protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
			final InvocationCallback invocation) throws Throwable {
 
		
		TransactionAttributeSource tas = getTransactionAttributeSource();
		final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
		final TransactionManager tm = determineTransactionManager(txAttr);
 
		//省略部分代码
        
        //获取事物管理器
		PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
		final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
 
		if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
			// 打开事务(内部就是getTransactionStatus的过程)
			TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
 
			Object retVal;
			try {
				// 执行业务逻辑 invocation.proceedWithInvocation();
			}
			catch (Throwable ex) {
				// 异常回滚
				completeTransactionAfterThrowing(txInfo, ex);
				throw ex;
			}
			finally {
				cleanupTransactionInfo(txInfo);
			}
 
			//省略部分代码
            
            //提交事物
			commitTransactionAfterReturning(txInfo);
			return retVal;
		}

【5】事务失效的常见情况及其背后的原因

【六】Spring编程式事务TransactionTemplate案例和实现原理

【1】TransactionTemplate使用案例

public Object getObject(String str) {
        /*
         *  执行带有返回值<Object>的事务管理
         */
        transactionTemplate.execute(new TransactionCallback<Object>() {
            @Override
            public Object doInTransaction(TransactionStatus transactionStatus) {

                try {
                      ...
                    //.......   业务代码
                    return new Object();
                } catch (Exception e) {
                    //回滚
                    transactionStatus.setRollbackOnly();
                    return null;
                }
            }
        });
}

可以使用lamada表达式简写

    @Autowired
    TransactionTemplate transactionTemplate;

    public Boolean update() {

        return transactionTemplate.execute(status->{
            try {
                int result1 = jdbcTemplate.update("update order_1 set count = ? where user_id=?;",1000,103);
                int result2 = jdbcTemplate.update("update order_1 set count = ? where user_id=?;",1000,101);
                // int i=1/0;
            } catch (Exception e) {
                status.setRollbackOnly();
            }
            return true;
        });
    }

【2】实现原理

  • Spring提供的最原始的事务管理方式是基于TransactionDefinition、PlatformTransactionManager、TransactionStatus 编程式事务。
  • 而TransactionTemplate的编程式事务管理是使用模板方法设计模式对原始事务管理方式的封装。

(1)TransactionTemplate.java源码

public class TransactionTemplate extends DefaultTransactionDefinition implements TransactionOperations, InitializingBean {

    protected final Log logger = LogFactory.getLog(this.getClass());
    private PlatformTransactionManager transactionManager;

    public TransactionTemplate() {
    }

    public TransactionTemplate(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public TransactionTemplate(PlatformTransactionManager transactionManager, TransactionDefinition transactionDefinition) {
        super(transactionDefinition);
        this.transactionManager = transactionManager;
    }

    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public PlatformTransactionManager getTransactionManager() {
        return this.transactionManager;
    }

    public void afterPropertiesSet() {
        if (this.transactionManager == null) {
            throw new IllegalArgumentException("Property 'transactionManager' is required");
        }
    }

    /*
     *  控制事务管理主要依赖于这个方法
     */
    public <T> T execute(TransactionCallback<T> action) throws TransactionException {
        if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
            return ((CallbackPreferringPlatformTransactionManager)this.transactionManager).execute(this, action);
        } else {
            TransactionStatus status = this.transactionManager.getTransaction(this);

            Object result;
            try {
                result = action.doInTransaction(status);
            } catch (RuntimeException var5) {
                this.rollbackOnException(status, var5);
                throw var5;
            } catch (Error var6) {
                this.rollbackOnException(status, var6);
                throw var6;
            } catch (Exception var7) {
                this.rollbackOnException(status, var7);
                throw new UndeclaredThrowableException(var7, "TransactionCallback threw undeclared checked exception");
            }

            this.transactionManager.commit(status);
            return (T) result;
        }
    }

    private void rollbackOnException(TransactionStatus status, Throwable ex) throws TransactionException {
        this.logger.debug("Initiating transaction rollback on application exception", ex);

        try {
            this.transactionManager.rollback(status);
        } catch (TransactionSystemException var4) {
            this.logger.error("Application exception overridden by rollback exception", ex);
            var4.initApplicationException(ex);
            throw var4;
        } catch (RuntimeException var5) {
            this.logger.error("Application exception overridden by rollback exception", ex);
            throw var5;
        } catch (Error var6) {
            this.logger.error("Application exception overridden by rollback error", ex);
            throw var6;
        }
    }


}

(2)execute方法的参数 TransactionCallback

查看接口TransactionCallback.java 发现其仅有一个方法doInTransaction:

public interface TransactionCallback<T> {
    T doInTransaction(TransactionStatus var1);
}

并且有一个抽象类TransactionCallbackWithoutResult实现了接口TransactionCallback。

public abstract class TransactionCallbackWithoutResult implements TransactionCallback<Object> {
    public TransactionCallbackWithoutResult() {
    }

    public final Object doInTransaction(TransactionStatus status) {
        this.doInTransactionWithoutResult(status);
        return null;
    }

    protected abstract void doInTransactionWithoutResult(TransactionStatus var1);
}

所以当我们借助TransactionTemplate.execute( … )执行事务管理的时候,传入的参数有两种选择:
1、TransactionCallback
2、TransactionCallbackWithoutResult
两种区别从命名看就相当明显了,一个是有返回值,一个是无返回值。这个的选择就取决于你是读还是写了。

(3)重要组件介绍

(1)TransactionCallback
(2)TransactionStatus

(3)流程分析

(1)使用TransactionTemplate调用execute方法,参数为TransactionCallback。TransactionTemplate会捕捉TransactionCallback或者TransactionCallbackWithoutResult事务操作中抛出的unchecked exception并回滚事务,然后将unchecked exception抛给上层处理。如果事务处理期间没有任何问题,TransactionTemplate最终会为我们提交事务,唯一需要我们干预的就只剩下某些情况下的事务回滚了。
(2)然后重写TransactionCallback接口里的doInTransaction方法,doInTransaction方法内就是我们的业务代码
(3)doInTransaction方法的参数是TransactionStatus,TransactionStatus是this.transactionManager.getTransaction(this);来的
(4)如果需要回滚的话,TransactionStatus对象调用setRollbackOnly方法。使用Callback接口公开的TransactionStatus将事务标记为rollBackonly。TransactionTemplate在最终提交事务的时候,如果检测到rollBackOnly标志状态被设置,将把提交事务改为回滚事务。

【七】mysql事务和spring事务的关系

【八】分布式事务

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MySQL事务隔离级别决定了在并发环境下多个事务之间的隔离程度。MySQL提供了四个事务隔离级别,分别是读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。以下是对这四个隔离级别的详细解释: 1. 读未提交(Read Uncommitted):这是最低级别的隔离级别。在该级别下,一个事务可以看到其他事务未提交的修改。这可能导致脏读(Dirty Read)和不可重复读(Non Repeatable Read)的问题。 2. 读已提交(Read Committed):在该级别下,一个事务只能看到其他事务已经提交的修改。这可以避免脏读的问题,但仍可能导致不可重复读的问题。 3. 可重复读(Repeatable Read):在该级别下,一个事务在执行期间能够看到同一结果集的一致性快照。这可以避免脏读和不可重复读的问题,但仍可能导致幻读(Phantom Read)的问题。 4. 串行化(Serializable):在该级别下,事务之间是完全隔离的,每个事务必须按照顺序执行。这可以避免脏读、不可重复读和幻读的问题,但也会导致并发性能的严重下降。 要查看MySQL的默认隔离级别和当前会话的隔离级别,可以使用以下命令: ```sql SELECT @@GLOBAL.tx_isolation, @@tx_isolation; ``` 请注意,MySQL 8之前可以使用上述命令,而MySQL 8及更高版本可以使用以下命令: ```sql SELECT @@global.transaction_isolation, @@transaction_isolation; ``` 这样可以查看默认的全局隔离级别和当前会话的隔离级别。这些隔离级别可以通过设置`transaction_isolation`参数来进行更改。<span class="em">1</span><span class="em">2</span><span class="em">3</span>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值