社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  DATABASE

MySQL 中的事务详解

东雎 • 6 年前 • 145 次点击  
阅读 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
 
145 次点击