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 {
@Autowired
private UserMapper userMapper;
@Autowired
private DiscussPostMapper discussPostMapper;
@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 {
@Autowired
private DemoService demoService;
@Test
public void testSave1(){
System.out.println(demoService.save1());
}
}
报错 没有插进去,说明数据回滚了
2.2 编程式事务
@Autowired
private TransactionTemplate transactionTemplate;
public Object save2(){
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
return transactionTemplate.execute(new TransactionCallback<Object>() {
@Override
public 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";
}
});
}
@Test
public void testSave2(){
System.out.println(demoService.save2());
}
总结:两种管理事务的方式,平时优先选第一种,简单;如果要处理的业务比较复杂,只有中间一小部分的事务想管理,就用第二种
|