文章目录
- 1 事务
-
- 1.1 概述
- 1.2 事务的隔离级别
-
- 1.2.1 并发异常
- 1.2.2 四个隔离级别
- 1.3 隔离实现机制
-
- 1.3.1 悲观锁
-
- 共享锁(S锁)
- 排他锁(X锁)
- 1.3.2 乐观锁
- 2 Spring事务管理
-
- 2.1 声明式事务
-
- 2.1.1 Service示例代码
- 2.1.2 测试
- 2.2 编程式事务
1 事务
【数据库入门】动力节点mysql入门基础03有关于事务的概述
1.1 概述
事务可以保证多个操作原子性,要么全成功,要么全失败。对于数据库来说事务保证批量的 DML 要么全成功,要么全失败。事务具有四个特征 ACID
- 原子性( Atomicity)
整个事务中的所有操作,必须作为一个单元全部完成(或全部取消)。 - 一致性( Consistency)
所有事务要求,在同一个事务当中,所有操作必须同时成功,或者同时失败,以保证数据的一致性。
比方说A转给B 100块,A的账户必然减少100块,B的账户必然增加100块。如果A给B转了100之后系统宕机,A减少100块,B却没有增加100块,这就是数据的不一致性。有点类似于物质守恒。 - 隔离性(Isolation)
一个事务不会影响其他事务的运行。 - 持久性(Durability)
在事务完成以后,该事务对数据库所作的更改将持久地保存在数据库之中,并不会被回滚。
1.2 事务的隔离级别
1.2.1 并发异常
? 当多个客户端并发地访问同一个表时,可能出现下面的一致性问题:
-
脏读取( Dirty Read)
一个事务开始读取了某行数据,但是另外一个事务已经更新了此数据但没有能够及时提交,这就出现了脏读取。
-
不可重复读( Non-repeatable Read)
在同一个事务中,同一个读操作对同一个数据的前后两次读取产生了不同的结果,这就是不可重复读。
-
幻像读( Phantom Read)
幻像读是指在同一个事务中以前没有的行,由于其他事务的提交而出现的新行。
-
第一类丢失更新
某一个事务的回滚,导致另外一个事务已经更新的数据丢失了
-
第二类丢失更新
某一个事务的提交,导致另外一个事务已经更新的数据丢失了
1.2.2 四个隔离级别
InnoDB 实现了四个隔离级别,用以控制事务所做的修改,并将修改通告至其它并发的事务:
- 读未提交( READ UMCOMMITTED) 没有提交就读到了
导致了:脏读 - 读已提交( READ COMMITTED) 提交之后才能读到
解决了:脏读
导致了:不可重复读
在事务开启之后,第一次读到的数据是3条,当前事务还没有结束,可能第二次再读取的时候,读到的数据是4条,3不等于4,称为不可重复读取。 - 可重复读( REPEATABLE READ) 提交之后也读不到,永远读取的都是刚开启事务时的数据
该隔离级别为 InnoDB 的缺省设置。mysql中默认的事务隔离级别就是这个
解决了:不可重复读
导致了:幻读
早晨9点开始开启了事务,只要事务不结束,到晚上9点,读到的数据还是那样。读到的是假象。不够绝对的真实。 - 串行化( SERIALIZABLE) 【序列化】
这是最高隔离级别,效率最低。解决了所有的问题。这种隔离级别表示事务排队,不能并发。每一次读取到的数据都是最真实的,并且效率是最低的。
1.3 隔离实现机制
1.3.1 悲观锁
悲观锁是数据库自带的
悲观锁看待事情比较悲观,认为如果并发就一定会有问题,既然一定会有问题就要提前对数据加锁
共享锁(S锁)
事务A对某数据加了共享锁后,其他事务只能对该数据加共享锁,但不能加排他锁。
加了共相锁之后只能读数据,不能改数据
排他锁(X锁)
事务A对某数据加了排他锁后,其他事务对该数据既不能加共享锁,也不能加排他锁。
加了排他锁既可以读又可以改
1.3.2 乐观锁
乐观锁需要自己定义
乐观锁看待事情比较乐观,认为即使并发了也不会有问题,假设它不会有问题,该读读该写写。当读取数据计算完了需要更改数据的时候,检查数据是否发生更改。如果数据发生更改,说明处理过程中有人改过数据,那就放弃这次操作;否则没人改就提交数据
怎么识别数据变没变?
一个表加上一个字段,或者是时间戳,或者是版本号
任何人更新数据之前检查时间戳或者版本号变没变,如果变了就放弃本次更新,如果没变就提交更新,修改时间戳或者版本号
2 Spring事务管理
官网文档:https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#spring-data-tier
链接可能会失效,可按如下顺序找到:
Spring对任何数据库做事务管理时,API都是统一的
具体来说有两种管理事务的方法
1.声明式事务:不需要写逻辑,只需要在xml配置文件里或者通过注解在方法上做配置就可以使用
2.编程式事务:需要编程,需要用到Transaction Template
这个类
两种管理事务的方式,平时优先选第一种,简单;如果要处理的业务比较复杂,只有中间一小部分的事务想管理,就用第二种
2.1 声明式事务
demo是模拟某一个业务,所以把代码写在业务层
杜撰一个需求:注册用户之后自动给用户发一个帖子:新人报到。一个业务完成了两个新增操作(新增用户和新增帖子),要保证业务的事务性
2.1.1 Service示例代码
@Service
public class DemoService {
@Autowiredprivate UserMapper userMapper;@Autowiredprivate DiscussPostMapper discussPostMapper;//isolation = Isolation.READ_COMMITTED:隔离级别是:读已提交//propagation传播机制 a调用b//REQUIRED 支持当前事务a,如果不存在,就创建新事务b//REQUIRES_NEW 创建新事务b且暂停当前事务//NESTED 如果当前存在事务a,则嵌套在该事务a中执行(b有独立的提交和回滚);否则和REQUIRED一样@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)public Object demo(){
//新增用户User user = new User();user.setUsername("alpha");user.setSalt(CommunityUtil.generateUUID().substring(0,5));user.setPassword(CommunityUtil.md5("123"+user.getSalt()));user.setEmail("alpha@qq.com");user.setHeaderUrl("http://image.nowcoder.com/head/99t.png");user.setCreateTime(new Date());userMapper.insertUser(user);//新增帖子DiscussPost post = new DiscussPost();post.setUserId(user.getId());post.setTitle("hello");post.setContent("hi");post.setCreateTime(new Date());discussPostMapper.insertDiscussPost(post);//人为造错,看数据能不能回滚Integer.valueOf("abc");return "ok";}
}
2.1.2 测试
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class TransactionTests {
@Autowiredprivate DemoService demoService;@Testpublic void testSave1(){
System.out.println(demoService.save1());}
}
报错
没有插进去,说明数据回滚了
2.2 编程式事务
@Autowiredprivate TransactionTemplate transactionTemplate;public Object save2(){
//设置隔离级别transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);//设置传播机制transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//执行SQL访问事务return transactionTemplate.execute(new TransactionCallback<Object>() {
@Overridepublic Object doInTransaction(TransactionStatus transactionStatus) {
//新增用户User user = new User();user.setUsername("beta");user.setSalt(CommunityUtil.generateUUID().substring(0,5));user.setPassword(CommunityUtil.md5("123"+user.getSalt()));user.setEmail("beta@qq.com");user.setHeaderUrl("http://image.nowcoder.com/head/999.png");user.setCreateTime(new Date());userMapper.insertUser(user);//新增帖子DiscussPost post = new DiscussPost();post.setUserId(user.getId());post.setTitle("nihao");post.setContent("11");post.setCreateTime(new Date());discussPostMapper.insertDiscussPost(post);Integer.valueOf("abc");return "ok";}});}
@Testpublic void testSave2(){
System.out.println(demoService.save2());}
总结:两种管理事务的方式,平时优先选第一种,简单;如果要处理的业务比较复杂,只有中间一小部分的事务想管理,就用第二种