MySQL 中的事务详解

栏目: 数据库 · 发布时间: 5年前

内容简介:范例:a给b转账100元一致性解读:不会出现a账户-100而b账户没有增加100的情况,整体的状态是一致的,总的状态不会平白无故地改变隔离性解读:在a给b转账的过程中,b进行查余额的操作,那么b得到到结果将由数据库设定得数据库隔离级别来决定。
  • 原子性:一个事务要么全部成功,要么全部失败
  • 一致性:事务完成以后,状态改变是一致的,一致性一般是通过结果来呈现的
  • 隔离性:在不同事务试图去操作同一份数据的时候,事务之间的隔离性
  • 持久性:数据提交以后,事务操作的结果才会永久保存到数据库当中

范例:a给b转账100元

一致性解读:不会出现a账户-100而b账户没有增加100的情况,整体的状态是一致的,总的状态不会平白无故地改变

隔离性解读:在a给b转账的过程中,b进行查余额的操作,那么b得到到结果将由数据库设定得数据库隔离级别来决定。

MySQL 中的事务详解

使用 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的第一条更新语句执行第一条查询语句,得到结果如下
    MySQL 中的事务详解
    执行事务1的第二条更新语句并提交事务,然后执行事务3的第2条查询语句
    MySQL 中的事务详解
    我们可以看到在可重读的隔离级别下,一个事务内多次读取的数据结果和事务开始时的结果是一致的,哪怕其它事务已经对原有数据进行更新并提交。

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

select @@GLOBAL.tx_isolation,@@tx_isolation;
复制代码
MySQL 中的事务详解

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

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;
    }
}
复制代码

数据库:

MySQL 中的事务详解

样例示范:

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

  1. 对事务2中查询出来的数据进行加锁,这里要注意的是加锁的数据一定要是我们需要查询的指定数据,所以这里一定要加上where条件,否则有可能导致锁全表,这样将给系统性能带来很大的影响。开启事务1以后,事务2直到事务1提交以后才拿到查询结果,因为要先获取该行数据的锁,这样则不会出现读旧数据去更新的情况。

以上所述就是小编给大家介绍的《MySQL 中的事务详解》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

日赚500元

日赚500元

董俊峰 / 2008-5 / 20.00元

《日赚500元:揭开网络赚钱的秘密》是一本大学生网络创业必看的图书,一本想在网络上创业的人必看的图书。懂懂团队第一个操作Google FireFox下载项目,第一个操作“域名停靠”项目。第一个操作Google账号推介项目。首次提出“网赚”这个概念,并创造性地将“网赚”的过程分为3个阶段。《日赚500元:揭开网络赚钱的秘密》揭开了网络上一些行为的本质。一起来看看 《日赚500元》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器