事务实例--银行转账

首先,举个例子:
银行(bank)中有两个客户(name)张三和李四

我们需要将张三的1000元存款(sal)转到李四的账户上

我们需要怎要通过sql语句来实现这个过程
update bank set sal = sal - 1000 where name = '张三';
update bank set sal = sal + 1000 where name = '李四';

但是万一出现一些错误,比如将字段名称打错了,没有检查就执行了这个两个语句,比如
update bank set sal = sal - 1000 where name = '张三';
update bank set sale = sale + 1000 where name = '李四';
我们通过查询数据库会发现 张三依然减少了1000元但是李四却没有加钱

如果可以有一种方法使得sql语句要么都执行,要么里面有一句没有执行,就全部不执行,


这时候就要引入事务这个概念
事务是一个最小的、不可分割的工作单元,不论成功与否都作为一个整体进行工作。

事物具有哪些特性
事务都应该具备ACID特征。所谓ACID是Atomic(原子性),Consistent(一致性),Isolated(隔离性),Durable(持久性)四个词的首字母所写.

使用银行转账来解释一下每种特性的含义:
原子性: 组成事务处理的语句形成了一个逻辑单元,不能只执行其中的一部分。换句话说,事务是不可分割的最小单元。 所以:银行转帐过程中,必须同时从一个帐户减去转帐金额,并加到另一个帐户中,只改变一个帐户是不合理的。

一致性:在事务处理执行前后,MySQL数据库是一致的。也就是说,事务应该正确的转换系统状态。所以:银行转帐过程中,要么转帐金额从一个帐户转入另一个帐户(在不考虑转账费用的情况下,转账方减少的金额与收账方的增加的金额应该是相等),要么两个帐户都不变,没有其他的情况。

隔离性:一个事务处理对另一个事务处理没有影响。就是说任何事务都不可能看到一个处在不完整状态下的事务。比如说,银行转帐过程中,在转帐事务没有提交之前,另一个转帐事务只能处于等待状态。

持久性:事务处理的效果能够被永久保存下来。转账结果能够在无论发生什么情况下都能保存下来.

Mysql支持事务的存储引擎有:BDB、InnoDB,如果我需要使用存储引擎则数据库数据使用的存储引擎应该是以上两种。

mysql中如何使用事务


BEGIN 开始一个事务
ROLLBACK 回滚会结束用户的事务,并撤销正在进行的所有未提交的修改
COMMIT 提交事务,并使已对数据库进行的所有修改成为永久性的

模拟银行转账
public boolean transfrom(int fromId, int toId, double amount)
    {
        Connection con = JDBCUtil.getConnection();
        String sql1 = "update tb_account set balance = balance - ? where userId = ?;";
        String sql2 = "update tb_account set balance = balance + ? where userId = ?;";
        String sql3 = "select balance  from tb_account where userId = ?;";
        PreparedStatement ps1 = null;
        PreparedStatement ps2 = null;
        PreparedStatement ps3 = null;
        ResultSet rs = null;
        try
        {
            //开启事务
            con.setAutoCommit(false);
            //转账时涉及的两个账户以及各账户的金额变动
            ps1 = con.prepareStatement(sql1);
            ps1.setDouble(1, amount);
            ps1.setInt(2, fromId);
            ps1.executeUpdate();

            ps2 = con.prepareStatement(sql2);
            ps2.setDouble(1, amount);
            ps2.setInt(2, toId);
            ps2.executeUpdate();

            //检查转出方账户的余额是否足够支持此次转账金额;如果余额不足,则抛出“余额不足”异常,并回滚
            ps3 = con.prepareStatement(sql3);
            ps3.setInt(1, fromId);
            rs = ps3.executeQuery();
            Double balance = 0.0;
            if(rs.next())
            {
                balance = rs.getDouble("balance");
            }
            if(balance < 0)
            {
                throw new Exception("账户余额不足");
            }
            con.commit();
            return true;
        } catch (Exception e)
        {
            e.printStackTrace();
        }
        try
        {
            con.rollback();
        } catch (SQLException e)
        {
            e.printStackTrace();
        }finally{
            try
            {
                rs.close();
                ps1.close();
                ps2.close();
                ps3.close();
                con.close();
            } catch (SQLException e)
            {
                e.printStackTrace();
            }
        }
        return false;
    }



SET TRANSACTION:用来设置事务的隔离级别,主要用于解决sql中的并发问题.InnoDB存储引擎提供事务的隔离级别有READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ和SERIALIZABLE

具体介绍四个隔离级别
1.READ UNCOMMITTED(未提交读)。在RU的隔离级别下,事务A对数据做的修改,即使没有提交,对于事务B来说也是可见的,这种问题叫脏读。这是隔离程度较低的一种隔离级别,在实际运用中会引起很多问题,因此一般不常用。
2.READ COMMITTED(提交读),一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。这个级别有时候也叫做不可重复读,因为两次执行相同的查询,可能会得到不一样的结果。因为在这两次读之间可能有其他事务更改这个数据,每次读到的数据都是已经提交的。
3.REPEATABLE READ(可重复读),解决了脏读,也保证了在同一个事务中多次读取同样记录的结果是一致的。但是理论上,可重复读隔离级别还是无法解决另外一个幻读的问题,指的是当某个事务在读取某个范围内的记录时,另外一个事务也在该范围内插入了新的记录,当之前的事务再次读取该范围内的记录时,会产生幻行。
4.SERIALIZABLE(可串行化),它通过强制事务串行执行,避免了前面说的幻读的问题,但由于读取的每行数据都加锁,会导致大量的锁征用问题,因此性能也最差。
直观地理解参考https://www.cnblogs.com/huanongying/p/7021555.html.

猜你喜欢

转载自blog.csdn.net/hehanHH/article/details/80751858