事务
一、事务
1.事务的概念:事务是指逻辑上的一组操作,这组操作要么同时完成要么同时不完成。参考转账操作。
2.使用命令去开启一个事务:
start transaction;--开启事务,这条语句之后的sql语句将处在一个事务当中,这些sql语句并不会立即执行
Commit--提交事务,一旦提交事务,事务中的所有sql语句才会执行。
Rollback -- 回滚事务,将之前所有的sql取消。
conn.setAutoCommit(false);
conn.commit();
conn.rollback();
conn.setSavePoint();
conn.rollback(sp);
3、事物的实现原理
默认情况下,我们向数据库发送的sql语句是会被自动提交的,开启事务就是相当于关闭自动提交功能,改为手动提交,我们只需要将提交事务的操作放在最后一个操作,这样一来,如果在提交事务之前出现异常,由于没有执行提交操作,事务中未提交的操作就会被回滚掉
4、事务的四大特性ACID
1、原子性(Atomicity):事务中所有操作是不可再分割的原子单位。事务中所有操作要么全部执行成功,要么全部执行失败。
2、一致性 (Consistency):事务执行后,数据库状态与其它业务规则保持一致。如转账业务,无论事务执行成功与否,参与转账的两个账号余额之和应该是不变的。
3、隔离性(Isolation):隔离性是指在并发操作中,不同事务之间应该隔离开来,使每个并发中的事务不会相互干扰。
4、持久性(Durability):一旦事务提交成功,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须能保证通过某种机制恢复数据。
事务代码体现
public class AccountDao { // 全局参数 private Connection con; private PreparedStatement pstmt; // 1. 转账,没有使用事务 public void trans1() { String sql_zs = "UPDATE account SET money=money-1000 WHERE accountName='张三';"; String sql_ls = "UPDATE account SET money=money+1000 WHERE accountName='李四';"; try { con = JdbcUtil.getConnection(); // 默认开启的隐士事务 con.setAutoCommit(true); /*** 第一次执行SQL ***/ pstmt = con.prepareStatement(sql_zs); pstmt.executeUpdate(); /*** 第二次执行SQL ***/ pstmt = con.prepareStatement(sql_ls); pstmt.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } finally { JdbcUtil.closeAll(null,con, pstmt ); } } // 2. 转账,使用事务 public void trans2() { String sql_zs = "UPDATE account SET money=money-1000 WHERE accountName='张三';"; String sql_ls = "UPDATE1 account SET money=money+1000 WHERE accountName='李四';"; try { con = JdbcUtil.getConnection(); // 默认开启的隐士事务 // 一、设置事务为手动提交 con.setAutoCommit(false); /*** 第一次执行SQL ***/ pstmt = con.prepareStatement(sql_zs); pstmt.executeUpdate(); /*** 第二次执行SQL ***/ pstmt = con.prepareStatement(sql_ls); pstmt.executeUpdate(); } catch (Exception e) { try { // 二、 出现异常,需要回滚事务 con.rollback(); } catch (SQLException e1) { } e.printStackTrace(); } finally { try { // 三、所有的操作执行成功, 提交事务 con.commit(); JdbcUtil.closeAll( null,con, pstmt); } catch (SQLException e) { } } } // 3. 转账,使用事务, 回滚到指定的代码段 public void trans() { // 定义个标记 Savepoint sp = null; // 第一次转账 String sql_zs1 = "UPDATE account SET money=money-1000 WHERE accountName='张三';"; String sql_ls1 = "UPDATE account SET money=money+1000 WHERE accountName='李四';"; // 第二次转账 String sql_zs2 = "UPDATE account SET money=money-500 WHERE accountName='张三';"; String sql_ls2 = "UPDATE1 account SET money=money+500 WHERE accountName='李四';"; try { con = JdbcUtil.getConnection(); // 默认开启的隐士事务 con.setAutoCommit(false); // 设置事务手动提交 /*** 第一次转账 ***/ pstmt = con.prepareStatement(sql_zs1); pstmt.executeUpdate(); pstmt = con.prepareStatement(sql_ls1); pstmt.executeUpdate(); // 回滚到这个位置? sp = con.setSavepoint(); /*** 第二次转账 ***/ pstmt = con.prepareStatement(sql_zs2); pstmt.executeUpdate(); pstmt = con.prepareStatement(sql_ls2); pstmt.executeUpdate(); } catch (Exception e) { try { // 回滚 (回滚到指定的代码段) con.rollback(sp); } catch (SQLException e1) { } e.printStackTrace(); } finally { try { // 提交 con.commit(); } catch (SQLException e) { } JdbcUtil.closeAll(null,con, pstmt ); } } } ========================================= public class App { @Test public void testname() throws Exception { // 转账 AccountDao accountDao = new AccountDao(); accountDao.trans(); } }
5、事物的隔离级别
1、事务的并发读问题
1、脏读 :读取到另一个事务未提交数据;
2、不可重复读:两次读取不一致;
3、幻读(虚读):读到另一事务已提交数据。
2、并发事务问题
因为并发事务导致的问题大致有5类,其中两类是更新问题,三类是读问题。
脏读(dirty read):读到另一个事务的未提交更新数据,即读取到了脏数据;
不可重复读(unrepeatable read):对同一记录的两次读取不一致,因为另一事务对该记录做了修改;
幻读(虚读)(phantom read):对同一张表的两次查询不一致,因为另一事务插入了一条记录;
脏读
事务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:提交
对同一表的两次查询不一致!
不可重复读和幻读的区别:
不可重复读是读取到了另一事务的更新;
幻读是读取到了另一事务的插入(MySQL中无法测试到幻读);
3、四大隔离级别
4个等级的事务隔离级别,在相同数据环境下,使用相同的输入,执行相同的工作,根据不同的隔离级别,可以导致不同的结果。不同事务隔离级别能够解决的数据并发问题的能力是不同的。
1 SERIALIZABLE(串行化)
不会出现任何并发问题,因为它是对同一数据的访问是串行的,非并发访问的;
性能最差;
2 REPEATABLE READ (可重复读)(MySQL默认级别)
防止脏读和不可重复读,不能处理幻读问题;
性能比SERIALIZABLE好
3 READ COMMITTED (读已提交数据)(Oracle默认级别)
防止脏读,没有处理不可重复读,也没有处理幻读;
性能比REPEATABLE READ好
4 READ UNCOMMITTED(读未提交数据)
可能出现任何事务并发问题
性能最好
MySQL的默认隔离级别为Repeatable read,可以通过下面语句查看:
select @@tx_isolation |
也可以通过下面语句来设置当前连接的隔离级别:
set transaction isolationlevel [4选1] |
4、JDBC设置隔离级别
con. setTransactionIsolation(int level)
参数可选值如下:
Connection.TRANSACTION_READ_UNCOMMITTED;
Connection.TRANSACTION_READ_COMMITTED;
Connection.TRANSACTION_REPEATABLE_READ;
Connection.TRANSACTION_SERIALIZABLE。
事务总结:
事务的特性:ACID;
事务开始边界与结束边界:开始边界(con.setAutoCommit(false)),结束边界(con.commit()或con.rollback());
事务的隔离级别: READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE。多个事务并发执行时才需要考虑并发事务。