zl程序教程

您现在的位置是:首页 >  数据库

当前栏目

mysql事务 mysql事务回滚 MySQL事务死锁 如何解除死锁 资金出入账

mysql事务 如何 死锁 回滚 解除 资金
2023-09-27 14:26:13 时间

问题

最近使用golang做资金账户,目前涉及到这两个问题

  • 资金入账时,可能存在提现【出账】
  • 资金提现时,可能存在资金入账

因而,为了保证资金的正确性,这里需要事务操作。

什么是事务

MySQL 事务主要用于处理操作量大,复杂度高的数据。比如说,在人员管理系统中,你删除一个人员,你既需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成一个事务!

为什么需要事务

以上面资金出账和入账为例子,写出如下没有事务的代码:

创建账户表

mysql> create table account (
    ->     id int(11) primary key not null auto_increment comment '账户自增主键id',
    ->     userId int(11) not null comment '用户id',
    ->     balance int(11) not null DEFAULT 0 comment '账户余额,默认为0'
    -> ) ENGINE = 'INNODB';
Query OK, 0 rows affected (0.06 sec)

Query OK, 0 rows affected (0.06 sec)可知,account表创建成功。

插入数据

mysql> insert into account(userId,balance) values(1223,444),(1224,666);
Query OK, 2 rows affected (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 0

Query OK, 2 rows affected (0.01 sec)可知,数据插入成功。

无事务资金出入账

  • 假设用户1223用户1224转账100元。
  • 若转账成功,用户1223的账户余额为344元,用户1224的账户余额为766元。
  • 如果转账失败,用户1223用户1224的账户余额不变。

按照这个想法,设计如下SQL语句,此时是没有事务的:

-- 执行用户1223的出账,用户1224的入账
mysql> update account set balance = balance -100 where userId = 1223;
	   update account set balance = balance + 100 where useId = 1224 ;

Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

ERROR 1054 (42S22): Unknown column 'useId' in 'where clause'

在两条SQL语句中,用户1223出账SQL语句正确,即其账户余额为344元;用户1224入账SQL语句错误,因为account表中没有useId这个字段,因而其账户余额不变,仍旧是666元,如下SQL所示:

mysql> select * from account;
+----+--------+---------+
| id | userId | balance |
+----+--------+---------+
|  1 |   1223 |     344 |
|  2 |   1224 |     666 |
+----+--------+---------+
2 rows in set (0.00 sec)

这就和我们的预期不同,此时,我们使用如下SQL恢复用户1223的账户余额:

mysql> update account set balance = balance + 100 where userId = 1223 ;

Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

然后使用事务,再次执行上述SQL。

有事务资金出入账

依旧是``用户1223用户1224```转账100元,如下所示:

-- 开启事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

-- 执行用户1223的出账,用户1224的入账
mysql> update account set balance = balance -100 where userId = 1223;
       update account set balance = balance + 100 where useId = 1224 ;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

ERROR 1054 (42S22): Unknown column 'useId' in 'where clause'

-- 出现错误语句,回滚金额
mysql> rollback;
Query OK, 0 rows affected (0.01 sec)

-- 查询回滚后的账户余额
mysql> select * from account;
+----+--------+---------+
| id | userId | balance |
+----+--------+---------+
|  1 |   1223 |     444 |
|  2 |   1224 |     666 |
+----+--------+---------+
2 rows in set (0.00 sec)
  • 首先,开启事务
  • 执行用户1223的出账,用户1224的入账
  • 用户1223的出账SQL正确,用户1224的入账SQL错误
  • 出现错误的SQL语句,事务回滚
  • 查询账户余额,发现账户余额不变,符合我们的预期。

因而,为了保证资金的正确性,我们必须使用事务。

事务死锁

死锁出现的原因

在事务的情况下,给某个账户加上行级锁。

我使用的是for update的悲观行级锁,但前置条件是,当前账户要存在,否则,行级锁就会变成表解锁,锁粒度就会增加。

比如,在提现时,需要判断当前账户是否绑卡,如果没有绑卡,就抛出您尚未绑卡的toast。一般情况下都绑了卡,但就怕特殊情况,真的是想啥来啥。恰巧遇到某用户没有绑卡,抛出您尚未绑卡的toast之后,再次访问就报出如下问题:

在这里插入图片描述

于是去排查问题,发现在抛出尚未绑卡的异常时,没有将事务回滚,于是,此处添加事务回滚tx.rollBack

如果不进行事务回滚,那么当前行就不释放锁,相同的请求SQL过来,就会不停地尝试连接,,如果连接不成功,则会抛出连接超时的问题。

当前行不释放锁,新的SQL请求加锁,便出现了死锁的情况。

解决事务死锁

查看表级锁

如果行级锁不存在,使用SQLshow OPEN TABLES where In_use > 0查看是否存在表级锁,如下SQL所示:

mysql> show OPEN TABLES where In_use > 0;
Empty set (0.00 sec)

查询表锁进程

其次,查询进程(如果您有SUPER权限,您可以看到所有线程。否则,您只能看到您自己的线程)show processlist,最后杀死【kill】进程id

查询行级锁

使用sql语句SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;查看当前存在锁的事务,如下图存在一个事务死锁:

在这里插入图片描述

杀死行锁进程

杀死锁进程,我们可以使用命令: kill trx_mysql_thread_id

比如杀死如上的行级死锁,找到trx_mysql_thread_id的数值,执行命令:kill 4314823