事务是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元)。
1、事务的四大特性(ACID)
1.原子性(atomicity):事务中所有操作是不可再分割的原子单位。事务中所有操作要么全么执行成功,要么全部执行失败。
2.一致性(consistency):事务执行后,数据库状态与其他业务规则保持一致。例如转账业务,无论事务执行成功与否,参与转账的两个账号余额之和不变。
3.隔离性(isolation):隔离性是指在并发操作中,不同的事务应该隔离开来,是每个并发的事务之间不会相互干扰。
4.持久性(durability):一旦事务提交成功,事务的所有数据库操作都必须被持久化到数据库中,即使提交事务后数据库马上崩溃,也必须能保证通过某种机制恢复数据。
2、MySQL开启和关闭事务
在默认情况下,MySQL每执行一条SQL语句,都是一个单独的事务。如果需要在一个事务中包含多条SQL语句,那么需要开启事务和结束事务。
开启事务:start transaction
结束事务:commit 或 rollback
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> update account set balance=900 where name='zs';
Query OK, 1 row affected (0.02 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> update account set balance=1100 where name='ls';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from account;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | zs | 900 |
| 2 | ls | 1100 |
| 3 | ww | 1000 |
+----+------+---------+
3 rows in set (0.00 sec)
mysql> rollback;
Query OK, 0 rows affected (0.01 sec)
mysql> select * from account;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | zs | 1000 |
| 2 | ls | 1000 |
| 3 | ww | 1000 |
+----+------+---------+
3 rows in set (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> update account set balance=900 where name='zs';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> update account set balance=1100 where name='ls';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from account;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | zs | 900 |
| 2 | ls | 1100 |
| 3 | ww | 1000 |
+----+------+---------+
3 rows in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | zs | 900 |
| 2 | ls | 1100 |
| 3 | ww | 1000 |
+----+------+---------+
3 rows in set (0.00 sec)
【注】commit后再rollback将不会回滚事务。
3、JDBC完成事务处理(通过Connection完成事务处理)
同一事务中的所有操作,都必须使用同一个Connection对象!
Connection的三个方法与事务相关:
1.setAutoCommit(boolean):设置是否自动提交事务,true表示自动提交即每条SQL语句都是一个单独的事务,false表示开启了事务。
【注】 conn.setAutoCommit(false) :表示开启事务
2.commit():提交结束事务
【注】 conn.commit() :表示提交事务
3.rollback():回滚结束事务
【注】 conn.rollback():表示回滚事务
JDBC处理事务的代码格式:
try{
conn.setAtuoCommit(false);开启事务
... ...
conn.commit; 提交事务
}catch(){
conn.rollback(); 若"..."代码出现异常则不会提交事务,而是回滚事务
}
简要代码实例:
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;
public class JdbcTransaction {
/**
* 模拟dao层操作数据库
*/
public static void updateBalance(Connection conn,String name,int balance) throws SQLException, IOException, ClassNotFoundException {
try{
/**
* 1.抒写SQL模板,并完成创建pstmt
*/
String sql = "update account set balance=balance+? where name=?";
PreparedStatement pstmt = conn.prepareStatement(sql);
/**
* 2.对参数赋值
*/
pstmt.setDouble(1,balance);
pstmt.setString(2,name);
/**
* 3.执行
*/
pstmt.executeUpdate();
}catch(Exception e) {
throw new RuntimeException(e);
}
}
/**
* 转账操作
* @param args
*/
public static void main(String[] args) throws SQLException, IOException, ClassNotFoundException {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入转账人的姓名:");
String name1 = scanner.next();
System.out.println("请输入转账金额:");
int balance = scanner.nextInt();
System.out.println("请输入收人的姓名:");
String name2 = scanner.next();
Connection conn = JdbcUtils.getConnection();
try{
//开启事务
conn.setAutoCommit(false);
//调用updateBalance方法
updateBalance(conn,name1,-balance); //转账人
updateBalance(conn,name2,+balance);//收涨人
//提交事务
conn.commit();
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
【注】因为同一个事务的操作要用同一个Connection,因此需要把conn当作参数前去调用方法。
4、事务的隔离级别
事务是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作。
这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元)。
4.1、事务的并发读问题
;
脏读: 一个事物读取到另一个事务还未提交的数据(脏数据),形成脏读。
不可重复读: 一个事务两次读取同一行数据,结果得到不同状态结果,如中间正好另一个事务更新了该数据,两次结果相异,不可信任。
幻读(虚读): 一个事务执行两次查询,第二次查询比第一次多出或少一些数据,造成两次结果不一致。只是另一个事务在这两次查询中间插入或者删除了数据造成的。
【案例】
【脏读】
事务1:张三给李四转账100元
事务2:李四查看自己的账户
t1:事务1:开始事务
t2:事务1:张三给李四转账100元
t3:事务2:开始事务
t4:事务2:李四查看自己的账户,看到账户多出100元(脏读)
t5:事务2:提交事务
t6:事务1:回滚事务,回到转账之前的状态
【不可重复读】
事务1:酒店查看两次1048号房间状态
事务2:预订1048号房间
t1:事务1:开始事务
t2:事务1:查看1048号房间状态为空闲
t3:事务2:开始事务
t4:事务2:预定1048号房间
t5:事务2:提交事务
t6:事务1:再次查看1048号房间状态为使用
t7:事务1:提交事务
对同一记录的两次查询结果不一致!
【幻读】
事务1:对酒店房间预订记录两次统计
事务2:添加一条预订房间记录
t1:事务1:开始事务
t2:事务1:统计预订记录100条
t3:事务2:开始事务
t4:事务2:添加一条预订房间记录
t5:事务2:提交事务
t6:事务1:再次统计预订记录为101记录
t7:事务1:提交
对同一表的两次查询不一致!
4.2、四大隔离级别
4个等级的事务隔离级别,再相同数据的环境下,使用相同的输入执行相同的工作,根据不同的隔离级别,可以导致不同的结果。不同事物隔离级别能够解决的数据并发问题是不同的:
为了解决数据库事务并发运行时的各种问题数据库系统提供四种事务隔离级别:
1. Serializable 串行化 (没人用)
不会出现任何并发问题,因为对同一数据的访问是串行的,并非并发访问的
性能最差(容易死锁)
2. Repeatable Read 可重复读 (MySQL 默认隔离级别)
防止 "脏读"、"不可重复读",不能防止 "幻读"
性能比 "串行化" 好
3. Read Commited 读已提交 (Oracle 默认隔离级别)
防止 "脏读",不能解决 "不可重复读",不能防止 "幻读"
性能比 "串行化"、"可重复读"好
4. Read Uncommited 读未提交 (没人用)
可能出现任何事务并发问题
性能 最好
4.3、MySQL 隔离级别
可通过命令进行查看隔离级别:
select @@tx_isolation;
也可以通过命令设置当前连接的隔离级别:
set transaction isolationlevel [4选1]
4.4、JDBC 设置隔离级别
conn.setTransactionIsolation(int level)
参数可选:
Connection.TRANSACTION_READ_UNCOMMITTED
Connection.TRANSACTION_READ_COMMITTED
Connection.TRANSACTION_REPEATABLE_READ
Connection.TRANSACTION_SERIALIZABLE