问题
最近使用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
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/99215.html