事务与隔离级别笔记

一、数据库视角

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(下下策)

 

posted @ 2017-05-05 19:39  Matt_Cheng  阅读(322)  评论(0编辑  收藏  举报