数据库事务-银行转账问题

说明

在数据库事务中,有一个经典场景,银行转账,如下:

假设 马云 给 李雷 转账 1亿 人民币,想象一下,转账业务的操作流程:

  1. 从马云账户减少1亿
  2. 在李雷的账户上增加1亿

当 第1步 执行成功后,突然出现问题,导致 第2步 执行失败,此时会 发生巨大问题:钱丢了

代码演示

建表语句

CREATE TABLE `account` (
    `aid` INT NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(64) NOT NULL,
    `money` DECIMAL(10,2) NOT NULL DEFAULT '0',
    PRIMARY KEY (`aid`)
)
COLLATE='utf8mb4_unicode_ci'
;

初始化数据

INSERT INTO `account` (`aid`, `name`, `money`) VALUES (1, '马云', 10000.00);
INSERT INTO `account` (`aid`, `name`, `money`) VALUES (2, '李雷', 10.00);

java代码

Class.forName("com.mysql.jdbc.Driver");
String url="jdbc:mysql://127.0.0.1:3308/scott?useSSL=false&useUnicode=true&characterEncoding=utf-8";
Connection conn = DriverManager.getConnection(url,
        "root",//用户名
        "");//密码

//从马云账户减掉100
String sql="update account set money=money-? where aid=?";
PreparedStatement pstm=conn.prepareStatement(sql);

pstm.setInt(1, 100);
pstm.setInt(2,1);
pstm.executeUpdate();
pstm.close();

//======关键,演示报错代码==============
/*
 * 上面代码,成功将马云账户减掉100元
 * 运行该行代码时会报错,不再执行下面代码,也就不会将李雷账户增加100元
 * 也就造成丢失100元的情况
 */
int i=1/0;

//给李雷账户加上100
String sql2="update account set money=money+? where aid=?";
PreparedStatement pstm2=conn.prepareStatement(sql2);

pstm2.setInt(1, 100);
pstm2.setInt(2,2);
pstm2.executeUpdate();
pstm2.close();

conn.close();

总结

类似下面情况:

  • 从业务的角度讲:是 一个操作(可以理解为点一下按钮)
  • 这一个操作,要对数据库进行 多次 增、删、改 操作(不是必须包含增、删、改3个操作)

那么 多次 增、删、改 都要成功,有一个失败,数据 就要 恢复到初始值,否则就会出现问题,即:数据不一致

类似案例

如:电商平台的下订单:

  • 从业务角度讲:是 一个操作,即:下订单
  • 下订单这个操作,要对数据库进行多次 增、删、改 操作,如下:
    1. 生成流水号,并将订单流水号、总金额、卖家信息、买家信息 保存到订单表中
    2. 将购买的多个商品的信息 保存到 订单商品表中
    3. 库存数要减少相应的数量

这些操作 要么都执行成功,有一个失败,数据就恢复到之前的数值

解决

事务 就是为了 避免 这种情况,详见 数据库事务的4个特性(ACID)


原文出处:/show_1IX2idpROSDP.html