spring并发更新mysql_多事务运行并发问题spring学习笔记——数据库事务并发与锁详解...

多事务运行并发问题

在实际应用中,往往是一台(或多台)服务器向无数客户程序提供服务,当服务器查询数据库获取数据时,如果没有采用必要的隔离机制,可能会存在数据库事务的并发问题,下面是一些常见的并发问题分类:

1. 第一类丢失更新:撤销一个事务,其他事务已提交的更新数据覆盖

2. 第二类丢失更新:一个事务覆盖另一个事务已提交的更新数据

3. 脏读:一个事务读到另一个事务未提交的数据

4. 虚读:一个事物读到另一个已提交的新插入数据

5. 不可重复读:事务读到另一个事务已提交的更新数据

下面对这几类并发问题进行详细介绍:

1. 第一类丢失更新

产生过程:两个事务更新同一数据,第一个事务被提交,第一个事务被撤销,会把第一个事务所做的更新也撤销,示意图如下所示:

a6409fbfd2ddfa647f553ce1671bd7b8.png

2. 第二类丢失更新

产生过程:两个事务各自基于最初的查询结果提交数据,实例流程如下图所示:

720e0f393ddd183117906ad794ce2c58.png

3. 脏读

产生过程:事务二查询到事务一未提交数据,事务二根据此数据进行操作,事务一紧接着撤销数据,导致事务二操纵的是“脏数据”,实例流程如下图所示:

00c2aa0ac7ef268ceeef889877b5f9eb.png

4.虚读(幻象读)

产生过程:对某行执行插入或删除操作,而该行属于某个事务正在读取的行的范围。若对数据精度要求不高,则影响不大,实例流程如下图所示:

39ec3ff1ff96a53ec701113d2b2fe7ab.png

5.不可重复读

产生过程:一个事务查询到另一事务已提交的对数据的更新,实例流程如下图所示:

d06d4d7286d1174458a4c292958c1d78.png 

与脏读区别:脏读->读取前一事务未提交的脏数据,不可重复读->重新读取了前一事务已提交的数据

数据库系统锁的基本原理

1. 锁的多粒度及自动升级:

锁的多粒度按类型可分成下面几类:

数据库级锁

表级锁

区域级锁

页面级锁

键值级锁:锁定数据库表中带有索引的一行数据

行级锁

对数据的封锁力度越大,往往隔离性越高,但并发性能变差。

对数据施行的锁粒度越高,并发性往往越大,但这意味着在大并发量时锁的数量的急剧增多,由此会带来系统资源的严重负载,影响系统系统。常用的数据库都有事务锁自动升级功能,当系统性能因为锁数量过大而急剧变差时,往往会升级使用更粗粒度的锁来扩大封锁面,减少锁数优化系统资源。

2. 锁的类型和兼容性:

共享锁

加锁条件:读取数据

解锁条件:读取完毕

兼容性:放置了共享锁的资源,能再放置共享锁和独占锁

并发性:多事务访问相同数据,多锁同时读

独占锁(排它锁)

加锁条件:修改数据且无其他锁存在

解锁条件:事务结束

兼容性:不与其他锁兼容

更新锁

加锁条件:update操作

加锁更新步骤:

获得一个共享锁,读数据

升级为独占锁,更新数据

兼容性:与共享锁兼容,同一资源最多一把更新锁,能有效避免死锁产生,我们知道,共享锁的兼容性是最好的,加入一个数据同时被两把共享锁锁住,如果这时两条线程并发修改数据,由于共享锁的非排他性,可能会同时出现两把独占锁锁住数据,这样就会产生死锁了,而更新锁排斥独占锁,当出现更新操作时,只能将更新锁本身升级为独占锁,而有效避免死锁产生。

并发性:性能一般,多事务访问,单事务修改。

-

3. 死锁产生及其解决方法:

产生过程:多事务锁定一个资源->试图去锁定对方已锁定的资源->多事务处于等待对方释放锁资源状态,示意图如下所示:

70b83fb44fed10f7cad964ef14e8b181.png

防止方法

合理安排表访问顺序

对事务要求不高,允许脏读(不加锁)

错开多事务访问相同资源的时间

使用尽可能低的事务隔离级别

使用短事务(操作尽可能少,时间尽可能短)

将大事务分解成多个小事务顺序执行

4. 悲观锁与悲观锁

悲观锁:显式为数据加锁,常见有如下两种加锁方式

显式指定独占锁:select … for update

在数据库增加表明状态的LOCK字段

乐观锁:通过版本控制实现,示意图如下所示:

21a1df7747efadd521ef362865a2e0f8.png 

通过乐观锁的版本控制,我们能够更好地提高并发事务的性能。

3. 数据库的事务隔离级别

4种隔离级别从高到低依次为:

- Serializable:串行化

- Repeatable Read: 可重复读

- Read Commited:读已提交的数据

- Read UnCommited:读未提交的数据

它们对并发事务问题的支持如下图所示

adf10b000962a05d6c1d3fc62d08e8d9.png

4. spring的事务传播行为

事务传播行为类型说明

PROPAGATION_REQUIRED

如果当前没有事务,就新建一个,如果已经存在,则加入到事务中这是最常见的选择。

PROPAGATION_SUPPORTS

表示事务对当前方法可有可无,如果有,则按该事务执行,如果当前没有事务,就以非事务方式执行。

PROPAGATION_MANDATORY

表示当前方法必须在一个事务中执行,而且如果当前没有事务,就抛出异常。

PROPAGATION_REQUIRES_NEW

表示当前方法总是需要独立的新事务,如果当前已存在事务,就会把当前事务挂起,直到新的事务提交或者回滚才恢复执行

PROPAGATION_NOT_SUPPORTED

表示当前方法不需要事务,如果当前存在事务,就把当前事务挂起。

PROPAGATION_NEVER

表示当前方法不需要事务,而且如果当前存在事务,则抛出异常。

PROPAGATION_NESTED

如果当前存在事务,则在嵌套事务内执行,如果嵌套事务存在,并且外层事务抛出异常回滚,那么内层事务必须回滚,反之,内层事务并不影响外层事务

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring中,事务隔离级别由`Isolation`枚举类定义,其中包括以下五个级别: - DEFAULT:使用底层数据库默认的隔离级别。 - READ_UNCOMMITTED:允许读取未提交数据更改。可能导致脏读、幻读或不可重复读。 - READ_COMMITTED:只允许读取已经提交数据。可以避免脏读,但可能会有幻读或不可重复读。 - REPEATABLE_READ:确保在同一事务中多次读取相同的数据时,每次读取的数据都是一致的。可以避免脏读和不可重复读,但仍可能会出现幻读。 - SERIALIZABLE:确保在同一事务中多次读取相同的数据时,每次读取的数据都是一致的,并且可以避免脏读、不可重复读和幻读。 下面是一个使用`Isolation.READ_COMMITTED`隔离级别的示例代码: ```java @Service public class UserServiceImpl implements UserService { private final UserRepository userRepository; public UserServiceImpl(UserRepository userRepository) { this.userRepository = userRepository; } @Transactional(isolation = Isolation.READ_COMMITTED) @Override public User updateUserNickname(Long userId, String nickname) { User user = userRepository.findById(userId).orElseThrow(() -> new RuntimeException("User not found")); user.setNickname(nickname); userRepository.save(user); return user; } } ``` 在上面的代码中,`@Transactional`注解指定了使用`Isolation.READ_COMMITTED`隔离级别。当调用`updateUserNickname`方法时,Spring会在事务中执行该方法的所有数据库操作,并将它们隔离到其他事务之外,以确保数据的一致性和可靠性。此外,如果在事务中发生任何错误,Spring会自动回滚该事务中的所有操作。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值