Py学习  »  DATABASE

MySQL 中的事务详解

东雎 • 4 年前 • 141 次点击  
阅读 7

MySQL 中的事务详解

什么是事务

  • 是以一种可靠、一致的方式,访问和操作数据库中数据的程序单元

原则

  • 原子性:一个事务要么全部成功,要么全部失败
  • 一致性:事务完成以后,状态改变是一致的,一致性一般是通过结果来呈现的
  • 隔离性:在不同事务试图去操作同一份数据的时候,事务之间的隔离性
  • 持久性:数据提交以后,事务操作的结果才会永久保存到数据库当中

范例 :a给b转账100元
一致性解读:不会出现a账户-100而b账户没有增加100的情况,整体的状态是一致的,总的状态不会平白无故地改变
隔离性解读:在a给b转账的过程中,b进行查余额的操作,那么b得到到结果将由数据库设定得数据库隔离级别来决定。

使用sql进行数据管理

事务1:

START TRANSACTION;
UPDATE user_transaction set amount = amount -100 WHERE username = 'user1';
UPDATE user_transaction set amount = amount + 100 WHERE `username` = 'user2';
COMMIT
复制代码

事务2

SELECT *  from user_transaction; 
复制代码

事务3

START TRANSACTION;
SELECT *  from user_transaction; 
SELECT * FROM user_transaction WHERE username = 'user1';
COMMIT
复制代码

结果展示

  1. 在不修改数据库默认隔离级别的情况下,只有当执行完commit之后,其它事务才能查询到数据库的数据库的更新。
  2. 可重复读的展示(可重复读:一个事务中读取到的数据和事务开启的时刻是一致的) 事务3开启事务以后,开启事务1并执行事务1的第一条更新语句执行第一条查询语句,得到结果如下
    执行事务1的第二条更新语句并提交事务,然后执行事务3的第2条查询语句
    我们可以看到在可重读的隔离级别下,一个事务内多次读取的数据结果和事务开始时的结果是一致的,哪怕其它事务已经对原有数据进行更新并提交。

查询数据库中的设置的隔离级别,可以发现mysql默认的数据库隔离级别是可重读

select @@GLOBAL.tx_isolation,@@tx_isolation;
复制代码

修改数据库当前事务隔离级别为脏读

set session transaction isolation level read uncommited
复制代码

结果

  1. 事务1开启事务并执行第一条更新语句,事务2可以看到事务1中执行的更新的数据,即使事务并没有提交,即可以读取到脏数据。

mysql数据库的四种隔离级别

  • 读未提交
  • 读提交
  • 可重读
  • 序列读

jdbc操作事务

事务1:开启事务->两次更新->提交事务

public class JdbcTransaction {

    public static void main(String args[]) throws SQLException {

        Connection connection = getConn();

        //关闭自动提交,相当于开启一个事务
        connection.setAutoCommit(false);

        //减少账户余额
        String sql1 = "UPDATE user_transaction set amount = amount -100 WHERE username = ? ";
        PreparedStatement ps1 = connection.prepareStatement(sql1);
        ps1.setString(1, "user1");
        ps1.executeUpdate();

        //若抛出异常,事务会回滚
        //throwException();

        //增加账号余额
        String sql2 = "UPDATE user_transaction set amount = amount + 100 WHERE `username` = ?";
        PreparedStatement ps2 = connection.prepareStatement(sql2);
        ps2.setString(1, "user2");
        ps2.executeUpdate();

        // 提交事务
        connection.commit();
        ps1.close();
        ps2.close();
    }

    private static Connection getConn() {
        String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/test";
        String username = "root";
        String password = "root";
        Connection conn = null;
        try {
            Class.forName(driver); //classLoader,加载对应驱动
            conn = (Connection) DriverManager.getConnection(url, username, password);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }

    private static void throwException() throws SQLException {
        throw new SQLException();
    }
复制代码

事务2: 开启事务->进行查询操作->根据查询的结果再进行更新->提交

public class JdbcTransaction2 {

    public  static  void main(String args[]) throws SQLException {

        Connection connection = getConn();

//        关闭自动提交,相当于开启一个事务
        connection.setAutoCommit(false);

//        减少账户余额,加悲观锁
        String query1 = "select * from user_transaction for update";
        PreparedStatement ps1 = connection.prepareStatement(query1);
        ResultSet resultSet = ps1.executeQuery();
        Integer myAmount = 0;
        while (resultSet.next()){
            String username = resultSet.getString(2);
            Integer amount = resultSet.getInt(3);
            System.out.println("username =" + username+ " amount = " + amount);
            if (username.equals("user1")){
                myAmount = amount;
            }
        }
        // 根据查询出来的结果去更新会有什么问题?
        /**
         * 1. 开启JdbcTransaction中的事务,执行更新操作但不commit
         * 2. 本事务的更新操作将卡在更新数据之前,直到上一个事务提交,交出该数据锁的权限
         * 3. 此时更新的数据将是根据前面查得的数据进行更新的,那么此时更新的依据将会是旧的数据
         *
         * 解决:开启事务,锁住查询出来的数据,若其它事务正在对本事务需要查询的数据进行操作,那么本事务等待直至
         * 其它事务commit
         */

//        如果有其它事务正在操作这条数据,那么此处将会等到其它事务提交以后才能继续往下执行
//        根据mysql的内部机制,更新同一条数据的两个事务不能同时执行,需要等待其中一个事务执行完
        String sql2 = "UPDATE user_transaction set amount = ? WHERE username = ? ";
        PreparedStatement ps2 = connection.prepareStatement(sql2);
        ps2.setString(1,String.valueOf((myAmount+100)));
        ps2.setString(2,"user1");
        ps2.executeUpdate();
        System.out.println("进行数据更新");

        // 提交事务
        connection.commit();
        ps1.close();
        ps2.close();
    }

    private static Connection getConn() {
        String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/test";
        String username = "root";
        String password = "root"


    
;
        Connection conn = null;
        try {
            Class.forName(driver); //classLoader,加载对应驱动
            conn = (Connection) DriverManager.getConnection(url, username, password);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }
}
复制代码

数据库:

样例示范:
执行事务1,但先不提交(断点停留在commit( ) 处),开启事务2,会发现事务2会停留在第二条sql(更新数据的sql)前,原因是事务1正在对同一份数据进行更新,事务2无法获取到数据的锁;这时提交事务1,事务2也随之提交。这时会出现的问题是事务2中是根据旧数据为依据进行更新的,这种情况在生产中是不允许出现的。 解决方式:

  1. 对事务2中查询出来的数据进行加锁,这里要注意的是加锁的数据一定要是我们需要查询的指定数据,所以这里一定要加上where条件,否则有可能导致锁全表,这样将给系统性能带来很大的影响。开启事务1以后,事务2直到事务1提交以后才拿到查询结果,因为要先获取该行数据的锁,这样则不会出现读旧数据去更新的情况。
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/32413
 
141 次点击