当前位置:网站首页>记一次gorm事务及调试解决mysql死锁

记一次gorm事务及调试解决mysql死锁

2022-08-02 02:12:00 不是住在隔壁的老王

前言

之前使用mysql和postgresql是通过sqlc包来使用,这一次使用gorm,还是遇到了很多问题,在此记录以下。

版本问题

gorm大版本更新之后,驱动的引入及实际的用法和老版本会有一些区别,特别是事务、锁的使用以及配置项的修改。
新版本gorm数据库链接方式以及包的名称和老版本相比均有改变,如下:

老版本:

import (
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
)
db, err := gorm.Open("mysql", "root:[email protected](127.0.0.1:3306)/db01?&parseTime=True&loc=Local")

而新版本:

import (
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"
)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    })

老版本的配置项通过db.xx的形式修改:

	db.LogMode(true)
	db.DB().SetMaxIdleConns(10)
	db.DB().SetMaxOpenConns(100)
	db.SingularTable(true)

而新版本均通过&gorm.Config{}字段进行修改:

db, err = gorm.Open(mysql.Open(dbLink), &gorm.Config{
    
		SkipDefaultTransaction: true,  //开启事务
		NamingStrategy: schema.NamingStrategy{
    
			SingularTable: true,
		},

forupdate的用法改变:
最开始依旧使用老版本的用法来使用,但是一直不成功,然后使用debug打印sql语句,发现生成的sql语句并没有for update相关操作,再去查找资料,发现老版本写法不生效,更换为新的写法。
老版本:

 result := tx.Debug().Set("gorm:query_option", "FOR UPDATE").First(&user1, arg.FromUserID)

新版本:

result = tx.Debug().Clauses(clause.Locking{
    Strength: "UPDATE"}).First(&user2, arg.ToUserID)

数据库死锁的排查解决:

在一个类似于转账的场景,为了保证转账的一致性,在相关的接口中使用了事务来保证转账成功,在最后测试同一个账户有人转出,有人转入时出现死锁;
根据网上查询以及一位大佬博文(http://t.csdn.cn/nAzEf)的思路下,在终端还原了死锁场景。
分别开启两个事务,按照顺序进行update操作:在执行第4步即事务2中的第二条操作时,出现死锁。

事务11 update user set collection_coins = collection_coins - 10 where id = 1537973755;
3 update user set collection_coins = collection_coins + 10 where id = 1338725819;   等待获取锁

事务22 update user set collection_coins = collection_coins - 10 where id = 1338725819;
4 update user set collection_coins = collection_coins + 10 where id = 1537973755;  

在这里插入图片描述

查询相关锁的表,能看到在发生死锁的上一步:

分别运行以下两个语句:

select * from information_schema.innodb_locks;
select * from information_schema.innodb_lock_waits;

在这里插入图片描述
运行:

SELECT * FROM information_schema.innodb_trx;

可以看到
在这里插入图片描述
可以看到,两个事务均获得了锁,进行了第3个操作时,事务1阻塞,等待事务2释放
锁;如果此时事务2不释放锁,反而执行第4个操作,则会造成事务2请求锁,但此时锁在事务1处,而事务1已经阻塞,无法释放锁,则事务2也会被阻塞,形成死锁。

解决办法则是:
调换4个语句的执行顺序;
如果按照1-4-3-2的顺序来进行则不会造成死锁。
因为在执行1后,事务1获得锁1,此时事务2执行4,事务2请求锁1,此时事务2阻塞;
事务1再执行3,获得锁2;此时事务1执行完毕,commit释放锁1和锁2,事务2获得锁1,语句4执行成功,再执行语句2成功,则不会发生死锁。

换到代码中则是对执行顺序进行排列,利用id为数字格式,可以比大小,则按照大小进行排列即可;

原网站

版权声明
本文为[不是住在隔壁的老王]所创,转载请带上原文链接,感谢
https://blog.csdn.net/m0_46351795/article/details/126022160