数据库常用事务传播行为与隔离级别

数据库常用事务传播行为与隔离级别

事务传播行为

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新的事务,并在自己的事务中运行。

事务传播行为由事务传播属性指定,以下是 Spring 支持的 7 种传播行为:

传播属性 描述
REQUIRED* 如果由事务在运行,当前的方法就在这个事务内运行。否则,就开启一个新的事务,并在自己的事务中运行。
REQUIRES_NEW* 当前方法必须启动新事务,并在自己的事务中运行。如果有事务正在运行,将该事务挂起。
SUPPORTS 如果有事务在运行,当前的方法就在这个事务中运行。否则它可以不运行在事务中。(有事务环境可以运行在事务,没有也行)
NOT_SUPPORT 当前的方法不应该运行在事务中,如果有运行的事务,将它挂起。
MANDATORY 当前方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常。
NERVER 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常。
NESTED 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务中运行。否则,就开启一个新的事务,并在它自己的事务内运行。

[ 模拟一个事务方法调用另一个事务方法 ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Service("userService")
public class UserServiceImpl{

@Autowired
BookService bookService;

@Transactional(propagation = Propagation.REQUIRED) // 默认值
public void buyBooks(int userId, int[] booksId){
for(int bookId : booksId){
bookService.pay(userId, bookId);
}
}
}

@Service("bookService")
public class BookServiceImpl{
@Transactional
public void pay(int userId, int bookId){
// TODO 查询图书实体
// TODO 查询用户实体
// TODO 减少库存
// TODO 减少账户余额
}
}

上面的伪代码意思是用户调用事务方法:buyBooks() 购买多本图书,buyBooks() 多次调用事务方法:pay()。默认情况下,两个方法的传播行为都是 REQUIRED,即: 虽然pay()自带事务,但是不起作用,因为调用方buyBooks()本身是事务方法,所以共用一个事务。

考虑这样一种情况:假如用户需要购买两本书,当 buyBooks() 第一次正常调用 pay() 后,库存减少一本书,用户余额减少对应的书的价格。但是第二次调用 buyBooks() 时发生异常:用户余额不足以购买第二本书,数据库回滚。此时用户是购买了一本书成功?还是两本书购买都没成功?

  • 当传播行为是 REQUIRED 时:两本书都购买失败。尽管第一本书购买成功了,但是第二次购买还是在 buyBooks() 方法发生的,pay() 并没有起作用,所以事务一起失败。

  • 当传播行为是 REQUIREDS_NEW 时:第一本书购买成功,第二本书购买失败。buyBooks() 方法处在一个事务环境中,pay() 也处在自己的事务环境中,当 buyBooks() 执行到调用 pay() 时,当前的事务会挂起,去执行完毕 pay() 的事务之后再继续执行自己的事务。注意:buyBooks() 方法中除了 pay() 不受自己的事务控制之外,其他代码仍处于自己的事务环境之中。

事务隔离级别

事务隔离控制一般应用在多线程并发访问操作数据库的时出现的并发问题上

数据库并发问题

假设现有两个事务:Transactional-1 和 Transactional-2 并发执行。

  • 脏读

    1. Transactional-1 将某条记录的 age 值从 20 修改为 30。(未提交)
    2. Transactional-2 读取了 Transactional-1 更新后的值:30。
    3. Transactional-1 回滚,age 值回到了 20。
    4. Transactional-2 读到的 30 就是一个无效的值。(读到了其他事务更新但未提交的值)
  • 不可重复读

    1. Transactional-1 读取了 age 的值为 20。(读取无需事务)
    2. Transactional-2 将 age 的值修改为 30。(已提交)
    3. Transactional-1 再次读取 age 的值为 30,和第一次读取的不一致。
  • 幻读

    1. Transactional-1 读取了 student 表中的一部分数据。
    2. Transactional-2 向 student 表中插入了新的行。
    3. Transactional-1 读取 student 表时,多出了新的行。

隔离级别

隔离级别越高,数据一致性越好。并发性能越弱。

  1. 读未提交:READ UNCOMMITTED

    允许 Transactional-1 读取 Transactional-2 未提交的修改。

  2. 读已提交:READ COMMITTED 一般设置这个级别

    要求 Transactional-1 只能读取 Transactional-2 已提交的修改

  3. 可重复度:REPEATABLE READ

    确保 Transactional-1 可以多次从一个字段中读取相同的值,即:Transactional-1 执行期间禁止其他事务对这个字段更新。(可理解为行锁)

  4. 串行化:SERIALIZABLE

    确保 Transactional-1 可以多次从一个表读取到相同的行,在 Transactional-1 执行期间禁止其他事务对这个表进行添加、更新、删除操作。可以避免任务并发问题,但性能下降。(可理解为表锁)

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×