第4章--事务
事务原理与开发
事务Transaction:
什么是事务?
事务是并发控制的基本单位,指作为单个逻辑工作单元执行的一系列操作,且逻辑工作单元需满足ACID特性。
i.e. 银行转账:开始交易;张三账户扣除100元;李四账户增加100元;结束交易。
事务的特性:ACID
原子性 Atomicity:整个交易必须作为一个整体来执行。(要么全部执行,要么全部不执行)
一致性 Consistency:整个交易总体资金不变
隔离性 Isolation:
case1: 若张三给李四转账过程中,赵五给张三转账了200元。两个交易并发执行。
T1 T2
读取张三余额100;
读取张三余额100;
给李四转账100,
更新张三余额为0;
交易结束 赵五转入200,
更新张三余额为300
交易结束
case2: 脏读:张三给别人转账100之后张三存钱200,存钱后转账由于系统原因失败回滚。
读取一个事务未提交的更新
T1 T2
读取张三余额100
(转账) 更新张三余额0
读取张三余额0
T1 Rollback() (存钱) 更新张三余额200
T2结束(张三账户余额为200)
case3: 不可重复读:同一个事务,两次读取同一数值的结果不同,成为不可重复读。
T1张三读取自己余额为100;T2读取张三余额100;T2存钱更新为300;T1张三读取余额为300。T1中两次读取张三余额即为不可重复读。
case4: 幻读:两次读取的结果包含的行记录不一样。
T1读取所有用户(张三、李四);T2新增用户赵五;T1读取所有用户(3个);T1/T2结束。T1中两次读取的结果中行记录数不同,称为幻读。
需要避免上述cases的产生
隔离性:交易之间相互隔离,在一个交易完成之前,不能受到其他交易的影响
持久性 Durability:整个交易过程一旦结束,无论出现任何情况,交易都应该是永久生效的
使用JDBC进行事务控制:
Connection类中
.setAutoCommit():开启事务(若为false,则该Connection对象后续的sql都将作为事务来处理;若为true,则该Connection对象后续的所有sql都将作为单独的语句执行(默认为true))
.commit():事务被提交,即事务生效并结束
.rollback():回滚,回退到事务开始之前的状态
i.e.
ALTER TABLE user ADD Account int; UPDATE User SET Account = 100 WHERE id = 1; UPDATE User SET Account = 0 WHERE id > 1;
实现ZhangSi(1)给LiSan(2)转账的过程:
(非事务:)
public static void TransferNonTransaction() { Connection conn = null; PreparedStatement ptmt = null; try { conn = DriverManager.getConnection(DB_URL, USER_NAME, PASSWORD); String sql = "UPDATE User SET Account = ? WHERE userName = ? AND id = ?;"; // transfer 100 from ZhangSi(1) to LiSan(2) ptmt = conn.prepareStatement(sql); ptmt.setInt(1, 0); ptmt.setString(2, "ZhangSi"); ptmt.setInt(3, 1); ptmt.execute(); ptmt.setInt(1, 100); ptmt.setString(2, "LiSan"); ptmt.setInt(3, 2); ptmt.execute(); } catch (SQLException e) { e.printStackTrace(); } finally { try { if (conn != null) conn.close(); if (ptmt != null) ptmt.close(); } catch (SQLException e) { e.printStackTrace(); } } }
执行完第一个ptmt.execute()后,数据库中ZhangSi的Account=0, LiSan的Account=0;
出现了一个中间状态,对于整个业务逻辑的实现是不可接受的。如果此时程序崩溃了将不可挽回。
(事务:)
public static void TransferByTransaction() { Connection conn = null; PreparedStatement ptmt = null; try { conn = DriverManager.getConnection(DB_URL, USER_NAME, PASSWORD); // Using Transaction mechanism conn.setAutoCommit(false); String sql = "UPDATE User SET Account = ? WHERE userName = ? AND id = ?;"; ptmt = conn.prepareStatement(sql); ptmt.setInt(1, 0); ptmt.setString(2, "ZhangSi"); ptmt.setInt(3, 1); ptmt.execute(); ptmt.setInt(1, 100); ptmt.setString(2, "LiSan"); ptmt.setInt(3, 2); ptmt.execute(); // Commit the transaction conn.commit(); } catch (SQLException e) { // if something wrong happens, rolling back if(conn != null) { try { conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } } e.printStackTrace(); } finally { try { if (conn != null) conn.close(); if (ptmt != null) ptmt.close(); } catch (SQLException e) { e.printStackTrace(); } } }
若在第一个ptmt.execute()时断点,并查询数据库,结果为事务执行之前的状态,并不是中间状态。
直到conn.commit()方法执行完毕,事务中的所有操作在数据库中才有效。
Connection类中的检查点功能:
.setSavePoint():在执行过程中创建保存点,以便rollback()可以回滚到该保存点
.rollback(SavePoint savePoint):回滚到某个检查点
i.e.
public static void rollbackTest() { Connection conn = null; PreparedStatement ptmt = null; // save point Savepoint sp = null; try { conn = DriverManager.getConnection(DB_URL, USER_NAME, PASSWORD); conn.setAutoCommit(false); String sql = "UPDATE User SET Account = ? WHERE userName = ? AND id = ?;"; ptmt = conn.prepareStatement(sql); ptmt.setInt(1, 0); ptmt.setString(2, "ZhangSi"); ptmt.setInt(3, 1); ptmt.execute(); // create a save point sp = conn.setSavepoint(); ptmt.setInt(1, 100); ptmt.setString(2, "LiSan"); ptmt.setInt(3, 2); ptmt.execute(); // throw an exception manually for the purpose of testing throw new SQLException(); } catch (SQLException e) { // if something wrong happens, rolling back to the save point created before // and then transfer the money to Guoyi(3) if(conn != null) { try { conn.rollback(sp); System.out.println("Transfer from ZhangSi(1) to LiSan(2) failed;\n" + "Transfer to GuoYi(3) instead"); // other operations ptmt.setInt(1, 100); ptmt.setString(2, "GuoYi"); ptmt.setInt(3, 3); ptmt.executeQuery(); conn.commit(); } catch (SQLException e1) { e1.printStackTrace(); } } e.printStackTrace(); } finally { try { if (conn != null) conn.close(); if (ptmt != null) ptmt.close(); } catch (SQLException e) { e.printStackTrace(); } } }
事务的隔离级别:4个级别
读未提交(read uncommited):可能导致脏读
读提交(read commited):不可能脏读,但是会出现不可重复读
重复读(repeatable read):不会出现不可重复读,但是会出现幻读
串行化(serializable):最高隔离级别,不会出现幻读,但严格的并发控制、串行执行导致数据库性能差
N.B. 1. 事务隔离级别越高,数据库性能越差,但对于开发者而言编程难度越低。
2. MySQL默认事务隔离级别为重复读 repeatable read
JDBC设置隔离级别:
Connection对象中,
.getTransactionIsolation();
.setTransactionIsolation();