Spring 事务和事务传播机制

✏️作者:银河罐头
📋系列专栏:JavaEE

🌲“种一棵树最好的时间是十年前,其次是现在”

Spring 中事务的实现

Spring 中的事务操作分为两类:

  1. 编程式事务(⼿动写代码操作事务)。
  2. 声明式事务(利⽤注解⾃动开启和提交事务)。

事务在 MySQL 有 3 个重要的操作:开启事务、提交事务、回滚事务,它们对应的操作命令如下:

-- 开启事务
start transaction;
-- 业务执⾏

-- 提交事务
commit;

-- 回滚事务
rollback;

Spring 编程式事务

Spring ⼿动操作事务和上⾯ MySQL 操作事务类似,它也是有 3 个重要操作步骤:

开启事务(获取事务)。

提交事务。

回滚事务。

image-20230606203219409

application.properties:

spring.datasource.url= jdbc:mysql://localhost:3306/mycnblog?characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#设置 MyBatis
mybatis.mapper-locations=classpath:/mybatis/*Mapper.xml
#打印 MyBatis 执行的 sql
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#因为打印 MyBatis 执行的 sql 日志级别是 debug,而默认级别是 info,所以要修改日志的默认级别为 debug
logging.level.com.example.demo=debug
package com.example.demo.controller;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    //编程式事务
    @Autowired
    private DataSourceTransactionManager transactionManager;
    @Autowired
    private TransactionDefinition transactionDefinition;
    @RequestMapping("/del")
    public int del(Integer id){
        if(id != null && id > 0){
            // 开启事务
            TransactionStatus transactionStatus =
                    transactionManager.getTransaction(transactionDefinition);
            // 业务操作: 删除用户
            int result = userService.del(id);
            System.out.println(result);
            // 提交事务
//            transactionManager.commit(transactionStatus);
            // 回滚事务
            transactionManager.rollback(transactionStatus);
            return result;
        }
        return 0;
    }
}
mysql> select * from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  1 | admin    | 123456   |       | 2021-12-06 17:10:48 | 2021-12-06 17:10:48 |     1 |
|  2 | zhangsan | 123456   |       | 2023-05-29 20:10:14 | 2023-05-29 20:10:14 |     1 |
|  3 | lisi     | 123456   |       | 2023-05-29 20:27:53 | 2023-05-29 20:27:53 |     1 |
|  4 | wangwu   | 123456   |       | 2023-05-30 14:43:23 | 2023-05-30 14:43:23 |  NULL |
|  5 | wangwu2  | 123456   |       | 2023-05-30 14:44:30 | 2023-05-30 14:44:30 |     1 |
| 13 | liliu    | 123456   |       | 2023-05-30 15:57:37 | 2023-05-30 15:57:37 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
6 rows in set (0.00 sec)

image-20230607090646410

再次查询数据库:

mysql> select * from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  1 | admin    | 123456   |       | 2021-12-06 17:10:48 | 2021-12-06 17:10:48 |     1 |
|  2 | zhangsan | 123456   |       | 2023-05-29 20:10:14 | 2023-05-29 20:10:14 |     1 |
|  3 | lisi     | 123456   |       | 2023-05-29 20:27:53 | 2023-05-29 20:27:53 |     1 |
|  4 | wangwu   | 123456   |       | 2023-05-30 14:43:23 | 2023-05-30 14:43:23 |  NULL |
|  5 | wangwu2  | 123456   |       | 2023-05-30 14:44:30 | 2023-05-30 14:44:30 |     1 |
| 13 | liliu    | 123456   |       | 2023-05-30 15:57:37 | 2023-05-30 15:57:37 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
6 rows in set (0.00 sec)

发现 id = 13 这条数据还在,就是因为回滚操作。

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    //编程式事务
    @Autowired
    private DataSourceTransactionManager transactionManager;
    @Autowired
    private TransactionDefinition transactionDefinition;
    @RequestMapping("/del")
    public int del(Integer id){
        if(id == null || id <= 0){
            return 0;
        }
        // 开启事务
        TransactionStatus transactionStatus = null;
        // 业务操作: 删除用户
        int result = 0;
        try{
            transactionStatus =
                    transactionManager.getTransaction(transactionDefinition);
            result = userService.del(id);
            System.out.println("删除: " + result);
            // 提交事务/回滚事务
            transactionManager.commit(transactionStatus); // 提交事务
        }catch (Exception e){
            if(transactionStatus != null) {
                transactionManager.rollback(transactionStatus); // 回滚事务
            }
        }
        return result;
    }
}

Spring 声明式事务

声明式事务的实现很简单,只需要在需要的⽅法上添加 @Transactional 注解就可以实现了,⽆需⼿动 开启事务和提交事务,进⼊⽅法时⾃动开启事务,⽅法执⾏完会⾃动提交事务,如果中途发⽣了没有处理的异常会⾃动回滚事务。

@Transactional 在单元测试中使用,无论结果如何都会 rollback;

@Transactional 在 普通方法中使用,没有出现异常就会提交事务,如果出现异常才会 rollback.

@RestController
@RequestMapping("/user2")
public class UserController2 {
    @Autowired
    private UserService userService;
    @RequestMapping("/del")
    @Transactional
    public int del(Integer id){
        if(id == null || id <= 0){
            return 0;
        }
        return userService.del(id);
    }
}

image-20230607093736244

image-20230607093751056

image-20230607093810036

id = 5 的这条数据 成功删除。

  • 下面来验证 "回滚"效果:
@RestController
@RequestMapping("/user2")
public class UserController2 {
    @Autowired
    private UserService userService;
    @RequestMapping("/del")
    @Transactional
    public int del(Integer id){
        if(id == null || id <= 0){
            return 0;
        }
        int result = userService.del(id);
        System.out.println("删除: " + result);
        int num = 10/0;
        return result;
    }
}
mysql>  select * from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  1 | admin    | 123456   |       | 2021-12-06 17:10:48 | 2021-12-06 17:10:48 |     1 |
|  2 | zhangsan | 123456   |       | 2023-05-29 20:10:14 | 2023-05-29 20:10:14 |     1 |
|  3 | lisi     | 123456   |       | 2023-05-29 20:27:53 | 2023-05-29 20:27:53 |     1 |
|  4 | wangwu   | 123456   |       | 2023-05-30 14:43:23 | 2023-05-30 14:43:23 |  NULL |
+----+----------+----------+-------+---------------------+---------------------+-------+
4 rows in set (0.00 sec)

image-20230607094625351

@Transactional 作⽤范围

@Transactional 可以⽤来修饰⽅法或类:

修饰⽅法时:需要注意只能应⽤到 public ⽅法上,否则不⽣效。

修饰类时:表明该注解对该类中所有的 public ⽅法都⽣效。

@Transactional 参数说明

image-20230607100905576

Spring 事务隔离级别

Spring 中事务隔离级别包含以下 5 种:

  1. Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。
  2. Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读。
  3. Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重 复读。
  4. Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级 别)。
  5. Isolation.SERIALIZABLE:串⾏化,可以解决所有并发问题,但性能太低。

可以看出,相比于 MySQL 的事务隔离级别,Spring 的事务隔离级别只是多了⼀个 Isolation.DEFAULT(以数据库的全局事务隔离级别为主)。

image-20230607163631990

事务类型:

1.普通事务

2.只读事务,没设置事务隔离级别的情况下(可重复读) => 可以设置隔离级别

3.无事务(默认的隔离级别可重复读)

@Transactional(readOnly = true, isolation = Isolation.SERIALIZABLE)
  • 再来看一个例子:
@RestController
@RequestMapping("/user2")
public class UserController2 {
    @Autowired
    private UserService userService;
    @RequestMapping("/del")
    @Transactional(readOnly = true, isolation = Isolation.SERIALIZABLE)
    public int del(Integer id){
        if(id == null || id <= 0){
            return 0;
        }
        int result = userService.del(id);
        System.out.println("删除: " + result);
        try {
            int num = 10/0;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
}

在 int num = 10/0; 语句外面加 try - catch ,事务还会 rollback 吗?

image-20230607185941608

mysql> select * from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  1 | admin    | 123456   |       | 2021-12-06 17:10:48 | 2021-12-06 17:10:48 |     1 |
|  2 | zhangsan | 123456   |       | 2023-05-29 20:10:14 | 2023-05-29 20:10:14 |     1 |
|  3 | lisi     | 123456   |       | 2023-05-29 20:27:53 | 2023-05-29 20:27:53 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
3 rows in set (0.00 sec)

image-20230607190038288

事务直接提交了,没有 rollback。删除了一条数据。

声明式事务发生异常,并添加 try-catch 有可能出现异常,事务不会自动回滚,那么就会导致业务出错。

解决方案有 2 种:

1.将异常抛出去,让框架感知到异常,框架感知到异常之后会自动回滚事务。

@RestController
@RequestMapping("/user2")
public class UserController2 {
    @Autowired
    private UserService userService;
    @RequestMapping("/del")
    @Transactional
    public int del(Integer id){
        if(id == null || id <= 0){
            return 0;
        }
        int result = userService.del(id);
        System.out.println("删除: " + result);
        try {
            int num = 10/0;
        } catch (Exception e) {
            throw e;
        }
        return result;
    }
}

image-20230607191050448

image-20230607191123723

image-20230607191147004

回滚了。

2.通过代码的方式手动回滚事务。

@RestController
@RequestMapping("/user2")
public class UserController2 {
    @Autowired
    private UserService userService;
    @RequestMapping("/del")
    @Transactional
    public int del(Integer id){
        if(id == null || id <= 0){
            return 0;
        }
        int result = userService.del(id);
        System.out.println("删除: " + result);
        try {
            int num = 10/0;
        } catch (Exception e) {
            // 手动回滚事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return result;
    }
}

image-20230607191606380

image-20230607191706919

image-20230607191641489

手动回滚成功。

面试题:Spring 事务失效的场景有哪些?

类没有修饰符,默认是 default,

接口没有修饰符,默认是 public.

Spring 事务传播机制

Spring 事务传播机制定义了多个包含了事务的⽅法,相互调⽤时,事务是如何在这些⽅法间进⾏传递 的。

事务隔离级别是保证多个并发事务执⾏的可控性(稳定性的),⽽事务传播机制是保证⼀个事务在多个调⽤⽅法间传递的可控性。

事务隔离级别解决的是多个事务同时调⽤⼀个数据库的问题。

image-20230607194330428

⽽事务传播机制解决的是⼀个事务在多个节点(⽅法)中传递的问题。

image-20230607194438392

事务传播机制

Spring 事务传播机制包含以下 7 种:

  1. Propagation.REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建⼀个新的事务。
  2. Propagation.SUPPORTS:如果当前存在事务,则加⼊该事务;如果当前没有事务,则以⾮事务的 ⽅式继续运⾏。
  3. Propagation.MANDATORY:(mandatory:强制性)如果当前存在事务,则加⼊该事务;如果当 前没有事务,则抛出异常。
  4. Propagation.REQUIRES_NEW:表示创建⼀个新的事务,如果当前存在事务,则把当前事务挂 起。也就是说不管外部⽅法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部⽅法会新开 启⾃⼰的事务,且开启的事务相互独⽴,互不⼲扰。
  5. Propagation.NOT_SUPPORTED:以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起。
  6. Propagation.NEVER:以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常。
  7. Propagation.NESTED:如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏;如 果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED。

以上7种传播机制,可根据“是否支持当前事务”的维度分为一下3类:

img

接下来我们用一个例子,来说明这3类事务传播机制的区别:

以情侣之间是否买房为例,我们将以上3类事务传播机制看作是恋爱中的3类女生类型:

  • 普通型
  • 强势型
  • 懂事型

这三类女生如下图:

img

演示事务传播机制

1.支持当前事务 Propagation.REQUIRED.

“一荣俱荣一损俱损”.

package com.example.demo.controller;
@RestController
@RequestMapping("/user3")
public class UserController3 {
    @Autowired
    private UserService userService;
    @RequestMapping("/add")
    @Transactional(propagation = Propagation.REQUIRED)
    public int add(String username, String password){
        if(null == username || null == password || username.equals(" ") || password.equals(" ")){
            return 0;
        }
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername(username);
        userInfo.setPassword(password);
        int result = userService.add(userInfo);
        return result;
    }
}
package com.example.demo.service;

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private LogService logService;

    public int del(Integer id){
        return userMapper.del(id);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public int add(UserInfo userInfo){
        //给用户表添加用户信息
        int addUserResult = userMapper.add(userInfo);
        System.out.println("添加用户结果: " + addUserResult);
        //添加日志信息
        Log log = new Log();
        log.setMessage("添加用户信息");
        logService.add(log);
        return addUserResult;
    }
}
package com.example.demo.service;

@Service
public class LogService {
    @Autowired
    private LogMapper logMapper;
    @Transactional(propagation = Propagation.REQUIRED)
    public int add(Log log){
        int result = logMapper.add(log);
        System.out.println("添加日志结果: " + result);
        int num = 10/0;
        return result;
    }
}
mysql> select * from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  1 | admin    | 123456   |       | 2021-12-06 17:10:48 | 2021-12-06 17:10:48 |     1 |
|  2 | zhangsan | 123456   |       | 2023-05-29 20:10:14 | 2023-05-29 20:10:14 |     1 |
|  3 | lisi     | 123456   |       | 2023-05-29 20:27:53 | 2023-05-29 20:27:53 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
3 rows in set (0.01 sec)

mysql>  select * from log;
Empty set (0.00 sec)

image-20230617214541900

image-20230617214646066

image-20230617214754389

算数异常,log 和 userinfo 都回滚了。

Propagation.REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建⼀个新的事务。

2.不支持当前事务 Propagation.REQUIRES_NEW.

把上面例子调用链中所有的 Propagation.REQUIRED 都改成 Propagation.REQUIRES_NEW.

预期结果是 添加日志失败,添加用户成功。

image-20230618102531362

image-20230618102821127

发现 添加用户操作也回滚了?!和预期不符。因为UserController 感知到了异常,整个调用链都回滚了。

  • 为了演示 添加日志失败,添加用户成功 这种效果。把代码稍微改动。
@RestController
@RequestMapping("/user3")
public class UserController3 {
    @Autowired
    private UserService userService;
    @RequestMapping("/add")
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public int add(String username, String password){
        if(null == username || null == password || username.equals(" ") || password.equals(" ")){
            return 0;
        }
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername(username);
        userInfo.setPassword(password);
        int result = userService.add(userInfo);
        return result;
    }
}
@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private LogService logService;

    public int del(Integer id){
        return userMapper.del(id);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public int add(UserInfo userInfo){
        //给用户表添加用户信息
        int addUserResult = userMapper.add(userInfo);
        System.out.println("添加用户结果: " + addUserResult);
        //添加日志信息
        Log log = new Log();
        log.setMessage("添加用户信息");
        logService.add(log);
        return addUserResult;
    }
}
@Service
public class LogService {
    @Autowired
    private LogMapper logMapper;
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public int add(Log log){
        int result = logMapper.add(log);
        System.out.println("添加日志结果: " + result);
        //回滚操作
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        return result;
    }
}

image-20230618103224050

image-20230618103242655

image-20230618103305198

日志回滚添加失败,用户没有回滚添加成功。

mysql> delete from userinfo where id = 5;
Query OK, 1 row affected (0.00 sec)

mysql> select * from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  1 | admin    | 123456   |       | 2021-12-06 17:10:48 | 2021-12-06 17:10:48 |     1 |
|  2 | zhangsan | 123456   |       | 2023-05-29 20:10:14 | 2023-05-29 20:10:14 |     1 |
|  3 | lisi     | 123456   |       | 2023-05-29 20:27:53 | 2023-05-29 20:27:53 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
3 rows in set (0.00 sec)
  • 把调用链中所有的 Propagation.REQUIRES_NEW 都改成 Propagation.REQUIRED.

image-20230618103948758

  • 为什么这里会报错?

日志(内层事务)要求回滚,用户(外层事务)没有感知到异常要提交事务,二者矛盾。

image-20230618104005764

image-20230618104043098

用户和日志都回滚了。

  • 对于 Propagation.REQUIRED,如果外部事物回滚,那么内部事务也会回滚。但是不会报错。
@RestController
@RequestMapping("/user3")
public class UserController3 {
    @Autowired
    private UserService userService;
    @RequestMapping("/add")
    @Transactional(propagation = Propagation.REQUIRED)
    public int add(String username, String password){
        if(null == username || null == password || username.equals(" ") || password.equals(" ")){
            return 0;
        }
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername(username);
        userInfo.setPassword(password);
        int result = userService.add(userInfo);
        //回滚操作
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        return result;
    }
}
@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private LogService logService;

    public int del(Integer id){
        return userMapper.del(id);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public int add(UserInfo userInfo){
        //给用户表添加用户信息
        int addUserResult = userMapper.add(userInfo);
        System.out.println("添加用户结果: " + addUserResult);
        //添加日志信息
        Log log = new Log();
        log.setMessage("添加用户信息");
        logService.add(log);
        return addUserResult;
    }
}
@Service
public class LogService {
    @Autowired
    private LogMapper logMapper;
    @Transactional(propagation = Propagation.REQUIRED)
    public int add(Log log){
        int result = logMapper.add(log);
        System.out.println("添加日志结果: " + result);

        return result;
    }
}

image-20230618105300497

没有报错。

mysql> select * from log;
Empty set (0.00 sec)

mysql>  select * from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  1 | admin    | 123456   |       | 2021-12-06 17:10:48 | 2021-12-06 17:10:48 |     1 |
|  2 | zhangsan | 123456   |       | 2023-05-29 20:10:14 | 2023-05-29 20:10:14 |     1 |
|  3 | lisi     | 123456   |       | 2023-05-29 20:27:53 | 2023-05-29 20:27:53 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
3 rows in set (0.00 sec)

而且 用户和日志都回滚。

3.嵌套事务 Propagation.NESTED.

@RestController
@RequestMapping("/user3")
public class UserController3 {
    @Autowired
    private UserService userService;
    @RequestMapping("/add")
    @Transactional(propagation = Propagation.NESTED)
    public int add(String username, String password){
        if(null == username || null == password || username.equals(" ") || password.equals(" ")){
            return 0;
        }
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername(username);
        userInfo.setPassword(password);
        int result = userService.add(userInfo);

        return result;
    }
}
@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private LogService logService;

    public int del(Integer id){
        return userMapper.del(id);
    }

    @Transactional(propagation = Propagation.NESTED)
    public int add(UserInfo userInfo){
        //给用户表添加用户信息
        int addUserResult = userMapper.add(userInfo);
        System.out.println("添加用户结果: " + addUserResult);
        //添加日志信息
        Log log = new Log();
        log.setMessage("添加用户信息");
        logService.add(log);
        return addUserResult;
    }
}
@Service
public class LogService {
    @Autowired
    private LogMapper logMapper;
    @Transactional(propagation = Propagation.NESTED)
    public int add(Log log){
        int result = logMapper.add(log);
        System.out.println("添加日志结果: " + result);
        //回滚操作
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        return result;
    }
}

预期结果:日志回滚,用户不回滚。添加日志失败,添加用户成功。

image-20230618105927945

image-20230618110039895

image-20230618110003172

总结:

1.REQUIRED

image-20230618111743988

是一个整体。如果外部事物回滚,那么内部事务也会回滚,不报错;如果内部事务回滚,那么外部事务也会回滚,报错。

2.REQUIRES_NEW

无论如何都会新建一个事务。

image-20230618112108089

外部事务和内部事务相互独立,互不影响。

3.NESTED

image-20230618112210950

虽然是嵌套关系,

但是外部事务和内部事务相互独立,互不影响。

嵌套事务 NESTED 原理

嵌套事务只所以能够实现部分事务的回滚,是因为事务中有⼀个保存点(savepoint)的概念,嵌套事务 进⼊之后相当于新建了⼀个保存点,⽽滚回时只回滚到当前保存点,因此之前的事务是不受影响的。

⽽ REQUIRED 是加⼊到当前事务中,并没有创建事务的保存点,因此出现了回滚就是整个事务回滚, 这就是嵌套事务和加⼊事务的区别。

MySQL :: MySQL 5.7 Reference Manual :: 13.3.4 SAVEPOINT, ROLLBACK TO SAVEPOINT, and RELEASE SAVEPOINT Statements

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/33566.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[State of GPT] OpenAI讲座随笔记

原版&#xff1a;State of GPT B站翻译版&#xff1a;【精校版】Andrej Karpathy微软Build大会精彩演讲&#xff1a; GPT状态和原理 - 解密OpenAI模型训练 1 GPT Training Pipeline图解 记录一下对这个图的理解&#xff1a; 大模型训练的四个阶段&#xff1a; Pretraining 阶…

【深入了解Spring Cloud Alibaba Nacos:服务注册和配置中心】—— 每天一点小知识

&#x1f4a7; 深入了解 S p r i n g C l o u d A l i b a b a N a c o s &#xff1a;服务注册和配置中心 \color{#FF1493}{深入了解Spring Cloud Alibaba Nacos&#xff1a;服务注册和配置中心} 深入了解SpringCloudAlibabaNacos&#xff1a;服务注册和配置中心&#x1f4a7;…

带你用Python制作一个经典必收藏的游戏:地铁跑酷(含完整代码)

名字&#xff1a;阿玥的小东东 学习&#xff1a;Python、C/C 主页链接&#xff1a;阿玥的小东东的博客_CSDN博客-python&&c高级知识,过年必备,C/C知识讲解领域博主 目录 一、游戏简介 二、游戏设计 引入必要的库 初始化游戏 定义游戏元素 定义游戏循环 更新游戏…

Qt QGraphicsScene、QGraphicsView类实现仪表盘

Qt QGraphicsScene、QGraphicsView类实现仪表盘 【1】UI界面设计【2】效果【3】QGraphicsScene简介【4】QGraphicsEllipseItem简介【5】QGraphicsPolygonItem简介【6】QGraphicsLineItem简介【7】QGraphicsView简介【8】仪表源码头文件源码 【1】UI界面设计 【2】效果 【3】QGr…

caffeine和google-guava cache缓存使用详解和源码介绍

google-guava cache 1.pom引入其依赖 <dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>20.0</version></dependency> 2.具体使用 com.google.common.cache.LoadingCache<Strin…

leetcode题集训 sql

目录 背景步骤175组合两个表&#xff08;多表联查&#xff09;176 177 第n高的薪水&#xff08;Distinct关键字 排序&#xff09;178分数排名 &#xff08;排序 order over关键字&#xff09;179 连续出现的数字 &#xff08;模拟多张表联查&#xff09;181. 超过经理收入的员工…

TCP/IP协议是什么?

78. TCP/IP协议是什么&#xff1f; TCP/IP协议是一组用于互联网通信的网络协议&#xff0c;它定义了数据在网络中的传输方式和规则。作为前端工程师&#xff0c;了解TCP/IP协议对于理解网络通信原理和调试网络问题非常重要。本篇文章将介绍TCP/IP协议的概念、主要组成部分和工…

Elasticsearch:使用 SIMD 指令加速向量搜索

作者&#xff1a;Chris Hegarty, Elastic Principal Engineer, Lucene PMC 翻译&#xff1a;杰瑞朱 多年来&#xff0c;Java 平台上运行的代码一直受益于自动向量化 —— HotSpot C2 编译器中的 superword 优化&#xff0c;将多个标量操作打包到 SIMD&#xff08;单指令多数据…

Git:git merge和git rebase的区别

分支合并 git merge是用来合并两个分支的。比如&#xff1a;将 b 分支合并到当前分支。同样git rebase b&#xff0c;也是把 b 分支合并到当前分支。他们的 「原理」如下&#xff1a; 假设你现在基于远程分支"origin"&#xff0c;创建一个叫"mywork"的分支…

【react全家桶学习】react的 (新/旧) 生命周期(重点)

目录 生命周期&#xff08;旧&#xff09; 挂载时的生命周期 constructor&#xff08;props&#xff09; componentWillMount&#xff08;&#xff09;-------------新生命周期已替换 render&#xff08;&#xff09; componentDidMount&#xff08;&#xff09;--- 组件…

PACS/RIS医学影像管理系统源码 提供先进图像处理和算法

PACS&#xff08;医学影像存档与通信系统&#xff09;主要应用于医学影像的存储、传输和显示。它可以使医生突破胶片的局限&#xff0c;对病人的影像进行全方位的处理和观察&#xff0c;以便得出更准确的诊断。同时&#xff0c;PACS可以节省大量的胶片&#xff0c;降低成本。医…

flex布局瀑布流占位两边对齐不对称

.page{display: flex;justify-content: space-between;flex-wrap: wrap; }.page:after {content: ;width: 400px; // 也可以 flex:1}

jmeter:BeanShell预处理程序获取/设置/引用变量

BeanShell预处理程序 1、局部变量 获取局部变量&#xff1a;vars.get("变量名") 设置局部变量&#xff1a;vars.put("变量名",变量值) 调用 ${变量名} 2、全局变量 获取局部变量&#xff1a;props.get("变量名") 设置局部变量&#xff1a…

KNIME工作流和节点比较功能

KNIME工作流和节点比较功能是一个在 << KNIME 视觉化数据分析 >> 中没有讲到的知识点。 KNIME工作流和节点比较功能在以下几种情况下非常有用&#xff1a; 版本控制&#xff1a;此功能可以跟踪工作流和节点中的更改。如果需要返回到之前的工作流或节点版本&#xf…

Vscode platformio Arduino开发STM32,点灯+串口调试

1.工具 USB-TTL(非常便宜&#xff0c;几块钱)STM32F103C8T6(几块钱) 2.引脚连线 USB-TTLSTM32TXPA10RXPA9VCC3.3VGNDGND 注意事项&#xff1a; 跳线帽位置&#xff1a;BOOT0接高电平(1)&#xff0c;BOOT1接低电平(0)每次上传程序前需要按一下复位键(之后&#xff0c;跳线帽…

2020年全国硕士研究生入学统一考试管理类专业学位联考逻辑试题——纯享题目版

&#x1f3e0;个人主页&#xff1a;fo安方的博客✨ &#x1f482;个人简历&#xff1a;大家好&#xff0c;我是fo安方&#xff0c;考取过HCIE Cloud Computing、CCIE Security、CISP等证书。&#x1f433; &#x1f495;兴趣爱好&#xff1a;b站天天刷&#xff0c;题目常常看&a…

Nginx SSL使用自制证书

1. 生成证书 keytool -genkey -v -alias <Alias别名> -keyalg RSA -keystore <KeyStore文件> -validity <有效期> keytool -genkey -v -alias nginx -keyalg RSA -keystore nginx.keystore -validity 36500 alias别名为 nginxkeystore文件为 nginx.keystore…

【Nginx】第七章 Nginx原理与优化参数配置

7.1 Nginx原理 master-workers的机制的好处 首先&#xff0c;对于每个worker进程来说&#xff0c;独立的进程&#xff0c;不需要加锁&#xff0c;所以省掉了锁带来的开销&#xff0c;同时在编程以及问题查找时&#xff0c;也会方便很多。 其次&#xff0c;采用独立的进程&…

第11节 跟上板块轮动的节奏

板块 文章目录 板块什么是板块板块的分类板块的轮动 板块相关接口本节课任务 什么是板块 股票板块是一些具有相同特征的股票的集合&#xff0c;命名通常也会简单明了的直接按照特征命名。例如沪深300板块&#xff0c;蓝筹板块。对上市公司进行“分班”不论是对于企业还是对于投…

Restful风格笔记

Restful风格知识点 RestController注解 在类上添加RestController可以默认类中的所有方法都带有ResponseBody注解&#xff0c;可以省去一个个添加的麻烦。 RestController RequestMapping("/restful") //CrossOrigin(origins {"http://localhost:8080"…