事务总结与练习

一、什么是事务?

事务是完成一件业务的所有动作。
比如把大象装进冰箱需要三步:1、打开冰箱 2、装进大象 3、关上冰箱
那这三步都完成了,才是完成装大象的业务。所以完成这个事情的三步动作就属于一个事务。
再比如张三给李四汇款一万元。那么张三账户要扣减一万元,李四账号要新增一万元。只有张三扣款成功,李四到账成功,两个动作都完成后,这件汇款的业务才算完成。所以这两个动作就是一个事务。

在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit),该单元可以有一条或多条sql组成。事务要有事务开始标志和结束标志来分隔。开始和结束标志之间属于同一个事务。

二、事务有什么特点?

事务是恢复和并发控制的基本单位。
事务应该具有这4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
原子性(atomicity):一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
一致性(consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性(isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(durability):持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

三、为什么使用事务?

因为有些事情是一系列动作协同完成的,一旦某一个没有完成,这件事情就没有完成,为了不受个别动作未完成而对原有数据业务造成影响,就使用事务把这些协同的动作纳入到一个事务中,利用事务的原子性,这些动作要么全部完成,要么全部失败来保证数据的准确。简单来说:做就做好,做不好就别做(恢复回原来的样子或状态)

四、事务并发常见问题

1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据

2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。

3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

五、数据库事务隔离级别

事务具有隔离性,所以数据库定义隔离的强弱时划定了不同的隔离级别。级别越高隔离越强,出现事务并发问题就越少,性能也就渐弱。
1.读未提交(Read Uncommitted)引发脏读(读取了未提交的数据)
2.读已提交(Read Committed)这是大多数数据库系统默认的隔离级别,但不是MySQL默认的,只能看见已经提交事务所做的改变,引发不可重复读。不可重读读意味着我们同一事务执行完全相同的select语句时,可能看到不一样的结果。导致这种情况的原因可能有:(1)有一个交叉的事务有新的commit,导致了数据的改变;(2)一个数据库被多个实例操作时,同一事务的其他实例在该实例处理期间,可能会有新的commit多个commit提交时,只读一次出现结果不一致
3.可重复读(Repeatable Read)这是MySQL的默认事务隔离级别
它确保同一事务的多个实例在并发读取数据时,看到同样的数据行。
此级别可能出现的问题–幻读(Phantom Read),当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
4.可串行化(Serializable)这是最高的隔离级别它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它在每个读的数据行上加上共享锁。可能导致大量的超时现象和锁竞争。

1、数据库事务隔离级别及可能出现的事务并发问题:是:有可能出现 否:不会出现。
Sql Server , Oracle 默认隔离级别是不可重复读,mysql默认隔离级别可重复读

事务隔离级别脏读不可重复读幻读
读未提交(read-uncommitted)
读已提交(read-committed)
可重复读(repeatable-read)
可串行化(serializable)

六、程序中如何使用事务?

以SpringBoot项目注解形式使用注解为例。其他形式后续补充。
1、打开事务开关,主函数添加注解@EnableTransactionManagement
2、打开注解扫描开关,主函数添加注解@ComponentScan(basePackages = “com.*”)
3、在需要使用事务的类、接口、public方法上添加注解@Transactional
类或接口上添加,默认所有pubic方法都会支持事务。类或接口不加,只在具体的方法上加,只有加注解的方法支持事务。

七、程序中测试事务

@Service
//@Transactional(rollbackFor = Exception.class)
public class TransationStudy {

@Autowired
private UserDao userDao;

/**
 * 测试发生异常是否回滚
 */
@Transactional(rollbackFor = Exception.class)
public void testTransation() {

// testRollback1();// 正常情况正常保存
// testRollback2();// 异常发生数据回滚,但是表自增字段序号不回滚
// testRollback3();// 异常被捕获数据不回滚
// testRollback4();// 私有函数、静态函数不支持事务
// testRollback5();// 调用的函数发生异常,无论调用层次多深都会回滚
// testRollback5();// 本函数不加事务注解,加到interface 接口上userDao.addUser,发生异常会回滚
// testRollback5();// 本函数不加事务注解,加到interface 接口类上,发生异常也会回滚
testRollback5();// 本函数不加事务注解,加到当前类上,发生异常也会回滚

}

public void testTransation2() {

    testRollback3();// 异常被捕获数据不回滚
}

/**
 * 测试正常情况下
 */
private void testRollback1(){
    UserInfoModel user = constructData("testRollback1");
    userDao.addUser(user);
}

/**
 * 测试发生异常是否回滚
 * 结论会回滚,但是指定空指针异常才回滚,模拟个数学异常结果也回滚了?
 */
private void testRollback2(){
    UserInfoModel user = constructData("testRollback2");
    userDao.addUser(user);
    UserInfoModel user2 = userDao.queryUserById("testRollback2");
    System.out.printf(user2.toString());
    // 模拟异常
    int num = 1/0;
}

/**
 * 测试异常被捕获是否回滚
 * 结论不会回滚
 */
private void testRollback3(){
    try {
        UserInfoModel user = constructData("testRollback3");
        userDao.addUser(user);
        UserInfoModel user2 = userDao.queryUserById("testRollback3");
        System.out.printf(user2.toString());
        // 模拟异常
        int num = 1 / 0;
    }catch(Exception e){
        System.out.printf("捕获了异常,查看数据是否回滚");
    }
}

/* @Transactional(rollbackFor = Exception.class)
private void testRollback4(){
UserInfoModel user = constructData(“testRollback2”);
userDao.addUser(user);
UserInfoModel user2 = userDao.queryUserById(“testRollback2”);
System.out.printf(user2.toString());
// 模拟异常
int num = 1/0;
}*/

/* @Transactional(rollbackFor = Exception.class)
public static void testRollback4(){

    // 模拟异常
    int num = 1/0;
}*/

/**
 * 测试在调用函数中发生异常是否回滚
 * 结论会回滚
 */
private void testRollback5(){
    out();
}

private void out(){
    inner();
}

private void inner(){
    UserInfoModel user = constructData("testRollback5");
    userDao.addUser(user);
    UserInfoModel user2 = userDao.queryUserById("testRollback5");
    System.out.printf(user2.toString());
    // 模拟异常
    int num = 1/0;
}

}

总结:@Transactional可以添加到类上,接口上,公有方法上。加到类或接口上时,当前类或接口中的public函数发生异常都会回滚。
public函数添加事务注解,不管调用层次多深的函数发生异常只要没有被捕获都会回滚。异常被捕获了就不回滚

事务失效不起作用场景
1、启动类没有开启事务 main类上面没有加注解@EnableTransactionManagement
2、数据库引擎不支持事务 如果是MySQL,注意表要使用支持事务的引擎,比如innodb,如果是myisam,事务是不起作用的
3、事务不支持 protected default private static 函数
4、异常被捕获了
5、发生了指定的不回滚异常,事务不会回滚 norollbackFor 用于指定不回滚的异常
6、不指定回滚异常,默认回滚运行时异常,非运行时异常不回滚

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值