zoukankan      html  css  js  c++  java
  • 事务与隔离级别笔记

    一、数据库视角

    SQL Server 2008 R2 事务与隔离级别实例讲解 笔记

    1、事务是数据库的工作单元,可视为一个原子操作,要么成功,要么什么也不曾发生

      事务操作的三种命令:

        a、BEGIN TRANSACTION

        b、COMMIT TRANSACTION

        c、ROLLBACK TRANSACTION

    2、隔离级别是针对事务,事务隔离级别规定了读操作(从而间接影响写操作),具体通过行级锁来实现(共享锁、排他锁)

      四种基本隔离级别:

        a、READ UNCOMMITTED        NOLOCK,脏读

        b、READ COMMITTED (Default)    HOLDLOCK,不可重复读    共享锁存在于语句级别,查询语句执行完会立即释放

        c、REPEATABLE READ         HOLDLOCK,幻读         共享锁存在于事务级别,一旦获取,只有事务结束后才会释放

        d、SERIALIZABLE           HOLDLOCK,不存在上述问题  共享锁存在于事务级别,针对查询范围内的记录,查询范围内insert会阻塞

      另外两种隔离级别

        e、SNAPSHOT            在整个事务中,读取快照,不会阻塞,不存在上述三种问题,但写操作会报错

        f、READ COMMITTED SNAPSHOT   在整个事务中,读取快照或者已提交的数据,不会阻塞,允许写操作,存在不可重复读、幻读问题

    3、锁的粒度默认为行,无论何种隔离级别,写操作获取的排他锁,在事务结束时才释放

    注:该笔记也参考a、 SQLserver锁和事务隔离级别     b、SQL Server 事务隔离级别详解

    注:mysql默认的隔离级别为REPEATABLE READ,可在 /etc/my.cnf 下添加配置 transaction_isolation=read-committed 更改默认配置

    二、Java视角

    java事务学习笔记总结 笔记

    1、Java对事物的支持体现在三方面

      a、自动提交模式(Auto-commit mode)

        1)当auto-commit为true时,当每个独立SQL操作的执行完毕,事务立即自动提交,也就是说每个SQL操作都是一个事务。

        2)当auto-commit为false时,每个事务都必须显示调用commit方法进行提交,或者显示调用rollback方法进行回滚。auto-commit默认为true。

      b、事务隔离级别(Transaction Isolation Levels)

        JDBC定义了五种事务隔离级别:

          1)TRANSACTION_NONE JDBC驱动不支持事务

          2)TRANSACTION_READ_UNCOMMITTED 允许脏读、不可重复读和幻读。

          3)TRANSACTION_READ_COMMITTED 禁止脏读,但允许不可重复读和幻读。

          4)TRANSACTION_REPEATABLE_READ 禁止脏读和不可重复读,单运行幻读。

          5)TRANSACTION_SERIALIZABLE 禁止脏读、不可重复读和幻读。

          经验证,sql server及mysql均不支持隔离级别(1)

      c、保存点(SavePoint)

        JDBC定义了SavePoint接口,提供在一个更细粒度的事务控制机制。当设置了一个保存点后,可以rollback到该保存点处的状态,而不是rollback整个事务。Connection接口的setSavepoint和releaseSavepoint方法可以设置和释放保存点。

    2、事务的分类

      a、显示事务:以begin transaction显示开始,以commit或rollback结束。

      b、隐式事务:数据库连接首次执行任一sql语句会自动启动一个事物,当提交或回滚时结束事物,隐式事务须显式以commit或rollback结束

      auto-commit为false时, Java事务采用隐式事务,每个事务须显式以commit或rollback结束,若省略,则自动添加rollback结束

    3、JDBC规范定义了事务的各种行为,但是每个数据库厂商对事务的支持程度可能各不相同,为此,JDBC提供了DatabaseMetaData接口,提供了一系列JDBC特性支持情况的获取方法,如:

      a、DatabaseMetaData.supportsTransactionIsolationLevel 判断对事务隔离级别的支持情况

      b、DatabaseMetaData.supportsSavepoints 判断对保存点的支持情况

    4、mysql在TRANSACTION_READ_COMMITTED 隔离级别上的行为与SQL Server存在差异

      当事务1对一个记录更新而尚未提交,之后,事务2读取同一条记录,此时,

        1)SQL Server会阻塞,等待事务1提交

        2)mysql正常运行,读取事务1更新之前的记录快照,但如果事务2继续更新同一条记录,则会阻断, 与SQL Server处在隔离级别READ COMMITTED SNAPSHOT 相同

      测试代码

      Task1

    package cn.matt.jdbc;
    
    import java.sql.*;
    
    public class Task1 implements Runnable {
    
        @Override
        public void run() {
            // String url =
            // "jdbc:sqlserver://10.102.16.63:1433;databaseName=Wind_test;user=sa;password=ABcd1234";
            String url = "jdbc:mysql://10.102.16.64:3306/test?useUnicode=true&characterEncoding=utf-8";
    
            Connection conn = null;
            PreparedStatement pstmt = null;
            try {
                // conn = DriverManager.getConnection(url);
                conn = DriverManager.getConnection(url, "sa", "abcd1234");
    
                conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
    
                conn.setAutoCommit(false);
    
                String sql = "update UserInfo set username = ? where id = ?";
                pstmt = conn.prepareStatement(sql);
    
                pstmt.setInt(2, 5);
                pstmt.setString(1, "Hellol13");
                pstmt.execute();
    
                conn.commit();
            } catch (SQLException e) {
                e.printStackTrace();
                try {
                    conn.rollback();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            } finally {
                if (conn != null) {
                    try {
                        conn.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    View Code

      Task2

    package cn.matt.jdbc;
    
    import java.sql.*;
    
    public class Task2 implements Runnable {
    
        @Override
        public void run() {
            // String url =
            // "jdbc:sqlserver://10.102.16.63:1433;databaseName=Wind_test;user=sa;password=ABcd1234";
            String url = "jdbc:mysql://10.102.16.64:3306/test?useUnicode=true&characterEncoding=utf-8";
    
            Connection conn = null;
            Statement stmt = null;
            PreparedStatement pstmt = null;
            ResultSet rs = null;
            try {
                // conn = DriverManager.getConnection(url);
                conn = DriverManager.getConnection(url, "sa", "abcd1234");
    
                conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
    
                conn.setAutoCommit(false);
    
                stmt = conn.createStatement();
                rs = stmt.executeQuery("select * from UserInfo");
    
                String sql = "update UserInfo set username = ? where id = ?";
                pstmt = conn.prepareStatement(sql);
    
                pstmt.setInt(2, 5);
                pstmt.setString(1, "Hellol133");
                pstmt.execute();
    
                rs = stmt.executeQuery("select * from UserInfo");
    
                conn.commit();
            } catch (SQLException e) {
                e.printStackTrace();
                try {
                    conn.rollback();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            } finally {
                if (conn != null) {
                    try {
                        conn.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    View Code

      TransTest

    package cn.matt.jdbc;
    
    public class TransTest {
    
        public static void main(String[] args) {
            new Thread(new Task1()).start();
            new Thread(new Task2()).start();
        }
        
    }
    View Code

     5、考虑一种场景:客户A的银行账户有1万元,某一天,A从ATM提取了6000元,与此同时,A的妻子在商场买了一款包,从同一个账户支付了6000元,此时账户余额为-2000;由于支付的操作是先查询账户余额,后插入交易流水,当两笔交易同时发生时,查到的余额均大于支付金额,校验成功,使得上述场景有可能发生。

      隔离级别TRANSACTION_READ_COMMITTED 模式下,解决上述并发问题的方法(乐观锁,将查询和插入操作作为一个事物处理):

      在查询账户余额时(第一次查询)保存交易流水的最新时间戳,在执行插入操作时,先查询流水的最新时间戳(第二次查询),并与第一次查询对比,若相等,则插入成功;对于更新操作,第二次查询为update中的where子句

      注意:第二次查询与之后的插入须为原子操作,实现方式:

        insert into A

          select a, b, c from dual

            where exists(select 1 from dual where '...' = (select max(...) from A)) 

    注意:TRANSACTION_READ_COMMITTED及TRANSACTION_REPEATABLE_READ隔离级别下,乐观锁逻辑上依然存在并发可能性,在SERIALIZABLE隔离级别下,无并发可能

    并发解决方案:

    1、 a、前端不可重复提交操作,b、后端做一个队列,串行对数据库的更改操作

    2、隔离级别设为SERIALIZABLE(下下策)

  • 相关阅读:
    python基础:多进程讲解
    vue 导出数据
    vue 导入excel数据组件
    vue 分页组件
    vue 点击空白区域隐藏div
    vue 类似淘宝选择地址组件
    js基础练习经典题(数组,质数,数组的遍历等等)
    js去重复
    理解CSS3里的Flex布局用法
    书写HTML5/CSS3的命名规则
  • 原文地址:https://www.cnblogs.com/MattCheng/p/6814660.html
Copyright © 2011-2022 走看看