事务
事务这一节内容以后工作中使用的不多,但是面试问的很多,很重要。
1. 事务的介绍
事务是指逻辑上的一组操作,组成这个操作的各个单元,要么就都成功,要么就都成功。
举个例子:
例如A给B转账,这个业务包含两个步骤
- 给A的账户扣钱
- 给B的账户加钱
组成这个业务的这两个步骤,要么就都成功,要么就都不成功。
2. 事务的使用
事务相关的API
// 开启事务
connection.setAutoCommit(false);
// 步骤1
// 步骤2
// 步骤3
// 提交事务
connection.commit();
// 回滚事务
connection.rollback();
我们通过转账的案例一起来看一下事务的使用
// 转账的方法
public static Boolean transfer(String fromName,String toName,Integer money){
// 获取连接
Connection connection = JDBCUtils.getConnection();
// 开启事务
try {
connection.setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
try {
// 给fromName扣钱 money
PreparedStatement preparedStatement = connection.prepareStatement("update account set money = money - ? where name = ?");
preparedStatement.setInt(1, money);
preparedStatement.setString(2, fromName);
// 执行
int affectedRows = preparedStatement.executeUpdate();
if (affectedRows != 1) {
System.out.println("扣钱失败!!!");
return false;
}
int i = 1 / 0;
// 给toName加钱 money
PreparedStatement preparedStatement1 = connection.prepareStatement("update account set money = money + ? where name = ?");
preparedStatement1.setInt(1, money);
preparedStatement1.setString(2, toName);
int affectedRows1 = preparedStatement1.executeUpdate();
if (affectedRows1 != 1) {
System.out.println("加钱失败");
return false;
}else {
// 提交事务
connection.commit();
return true;
}
}catch (Exception ex) {
try {
// 回滚事务
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
System.out.println("回滚成功...");
}
return false;
}
3. 事务的四个特性
事务的四个特性就是指ACID
-
原子性(A)
原子性是指事务是不可分割的工作单位,事务中的操作,要么就都发生,要么就都不发生。
-
一致性(C)
一致性指的是事务必须使数据库从一个一致性状态到另外一个一致性状态。
-
隔离性(I)
事务与事务之间应该是互相隔离的,互不影响的。
-
持久性(D)
事务一旦提交,对于数据库的改变应该是永久性的,即使数据库发生障碍(软件障碍)也应该不受影响。
4. 事务的隔离级别(面试)
事务与事务之间从理论上来说,应该是互相隔离,互不影响的,但是数据库针对事务的隔离性定义了不同的隔离级别,这些不同的隔离级别对于事务与事务的隔离有不同的效果。
如果我们做不好事务的隔离控制,那么在多个线程去访问数据库的同一份数据的时候,假如每个线程都是一个事务,那么这个时候就会有多线程并发访问共同数据的问题,那么就会导致出现以下的几个问题:
- 脏读
脏读是指一个事务读取到了另外一个事务还没有提交的数据
- 不可重复读
不可重复读是指同一份数据,在同一个事务中,读取的结果前后不一致。实际上就是一个事务读取到了另外一个事务已经提交的数据。
- 虚幻读
虚幻读是指在同一个事务中,有些记录数据有时候能读到,有时候读不到,就好像虚无缥缈存在一样。
MySQL给数据库定义了几种不同的隔离级别
- read uncommitted 读未提交
- read committed 读已提交
- repeatable read 可重复读(MySQL默认的隔离级别)
- serializable 串行化
那么我们就需要去了解不同的隔离级别会带来的不同的问题。
如何去修改和查看数据库的隔离级别呢?
-- 查看数据库的隔离级别
select @@transaction_isolation;
select @@tx_isolation;
-- 修改数据库的隔离级别
set global transaction isolation level read uncommitted;
-- 注意,修改了之后,要重新去和数据库建立连接
4.1 读未提交
读未提交有如下问题:脏读、不可重复读、虚幻读
4.2 读已提交
读已提交没有脏读的问题,有不可重复读和虚幻读的问题。
4.3 可重复读
这个是MySQL默认的事务的隔离级别。
可重复读我们发现没有 脏读、不可重复读以及虚幻读的问题。
4.4 串行化
串行化是指把事务串行起来,一个一个执行。假如现在数据库中有三个事务,A、B、C,那么这三个事务肯定有一个执行的先后顺序的,针对数据库来说,在同一个时刻,只能执行一个事务,不能执行多个事务,不支持多个事务的并发执行。
从这个定义来看,串行化没有并发所带来的安全性的问题,但是有效率低的问题。
脏读 | 不可重复读 | 虚幻读 | |
---|---|---|---|
read uncommitted | √ | √ | √ |
read committed | X | √ | √ |
repeatable read | X | X | X |
serializable | X | X | X |
说明:在MySQL中,对于repeatable read这个隔离级别,其实是通过MySQL的存储引擎InnoDB解决了虚幻读的问题。
从效率上来说,read uncommitted 这个隔离级别效率最高,serializable这种隔离级别效率最低。
从安全性上来说,read uncommitted最不安全,serializable这种隔离级别最安全。