MySql中的父事务与子事务

MySql中的父事务与子事务

事务的范围

事务的范围指的是事务的开始和结束位置。在MySQL中,事务可以通过以下语句来显式地开始和结束:

1
2
3
4
5
START TRANSACTION; -- 开始事务

COMMIT; -- 提交事务

ROLLBACK; -- 回滚事务

当在MySQL的一个事务中设置了事务隔离级别,该级别的设置将仅对当前事务有效。事务隔离级别定义了事务对数据的读取和修改的可见性和隔离程度。事务隔离级别可以通过以下语句在MySQL中设置:

1
SET TRANSACTION ISOLATION LEVEL <isolation_level>;

其中,****可以是以下之一:

  • READ UNCOMMITTED:允许脏读、不可重复读和幻读。
  • READ COMMITTED:禁止脏读,但允许不可重复读和幻读。
  • REPEATABLE READ:禁止脏读和不可重复读,但允许幻读。
  • SERIALIZABLE:禁止脏读、不可重复读和幻读。

一旦设置了事务隔离级别,该级别将在当前事务中生效,其他并发的事务将继续使用各自的隔离级别。这样可以确保每个事务在进行读取和修改时都遵循指定的隔离级别,从而提供一致的数据视图和隔离性。

父事务与子事务的传递

在MySQL中,父事务和子事务之间的传递涉及到保存点(Savepoint)的概念。保存点是在事务中创建的标记,用于标识事务中的一个特定位置。通过保存点,我们可以在事务中实现更细粒度的回滚。
父事务可以创建保存点,并在子事务中回滚到保存点。这样,子事务可以恢复到保存点时的状态,包括隔离级别。父事务的隔离级别会被子事务继承,除非子事务显式地设置了自己的隔离级别。注意,并不是所有的数据库都支持保存点技术,如果不支持,通常会新建一个事务执行事务。
假设我们有两个表:Orders(订单)和 OrderItems(订单项)。在一个事务中插入一个订单和相关的订单项,并在插入订单项之后创建一个保存点。然后,启动一个子事务,在子事务中删除刚才插入的订单项,并回滚到保存点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
START TRANSACTION; -- 开始父事务

-- 在父事务中插入订单和订单项
INSERT INTO Orders (order_id, order_date) VALUES (1, '2023-07-13');
INSERT INTO OrderItems (order_id, item_id, quantity) VALUES (1, 1, 10);
SAVEPOINT my_savepoint; -- 创建保存点

-- 启动子事务
START TRANSACTION; -- 开始子事务

-- 在子事务中删除订单项
DELETE FROM OrderItems WHERE order_id = 1;

ROLLBACK TO SAVEPOINT my_savepoint; -- 回滚到保存点

COMMIT; -- 提交子事务

COMMIT; -- 提交父事务

在上述示例中,父事务设置了隔离级别为 REPEATABLE READ,而子事务没有显式设置隔离级别,因此继承了父事务的隔离级别。通过回滚到保存点,子事务恢复到保存点时的隔离级别,即 REPEATABLE READ。因此,删除操作在回滚后被撤销,订单项保留在数据库中。

Spring中的事务传播行为

事务传播行为定义了事务在方法调用链中的传播方式。Spring提供了多种事务传播行为选项来实现父事务与子事务的传播。如REQUIRED、REQUIRES_NEW、NESTED等。不同的传播行为对应不同的事务行为,能够满足各种业务需求。在Spring中,事务传播行为通过AOP和动态代理实现。Spring会在方法调用前后织入事务管理的逻辑,确保事务的正确传播和管理。(这里建议去看其他专门写Spring中的事务传播行为的博客)。
然而,由于Spring的事务传播行为通过AOP和动态代理实现,有一个常见的问题是自调用方法无法进行事务传递。即当一个方法内部自调用另一个方法时,实际上是在同一个类的实例中进行方法调用,而不是通过代理对象。因此,事务管理的拦截器无法拦截自调用方法,事务传播行为无法被应用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Service
public class MyService {

@Autowired
private MyService self;

@Transactional
public void doSomething() {
// 事务传播行为将生效
// ...

self.doSomethingElse(); // 自调用方法,事务传播行为无效,无法代理

// ...
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void doSomethingElse() {
// 这个方法无法进行事务传递
// ...
}
}

在上述示例中,doSomething()方法是一个事务方法,它调用了自身的doSomethingElse()方法。由于自调用无法进行事务传递,doSomethingElse()方法的事务传播行为将无效。
为了解决这个问题,有两种方式:

  1. 将doSomethingElse()方法提取到一个独立的Bean中,并通过依赖注入的方式进行调用。这样,doSomethingElse()方法将通过代理对象进行调用,事务传播行为将得到正确应用。
  2. 将doSomethingElse()中加入从Spring容器中直接取出代理Bean的操作代码,这样会对Spring Api造成强依赖。

总结

在使用父事务与子事务时,需了解其原理。我们在日常开发中通常是直接使用第三方框架来调用这些事务,如Spring。


MySql中的父事务与子事务
http://xjl.info/2023/07/13/mysql/transaction/
作者
XJl
发布于
2023年7月13日
许可协议