• 事务


    一、事务
    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 [41]

    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。多个事务并发执行时才需要考虑并发事务。


     

  • 相关阅读:
    Populating Next Right Pointers in Each Node I&&II ——II仍然需要认真看看
    MySQL源码分析以及目录结构
    mysql分表的三种方法
    Hadoop学习
    关系型数据库ACID
    九种基本数据类型和它们的封装类
    java中堆和栈的区别
    软件测试-----Graph Coverage作业
    Lab1--关于安装JUnit的简要描述
    动态导入(import)和静态导入(import)的区别
  • 原文地址:https://www.cnblogs.com/flei/p/6728044.html
Copyright © 2020-2023  润新知