Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Golang中decimal与Gorm MySQL的一个问题 #177

Open
AlexiaChen opened this issue Aug 15, 2023 · 1 comment
Open

Golang中decimal与Gorm MySQL的一个问题 #177

AlexiaChen opened this issue Aug 15, 2023 · 1 comment
Labels
golang go programming language 软件调试 调试技巧,思考

Comments

@AlexiaChen
Copy link
Owner

AlexiaChen commented Aug 15, 2023

最近在做一个金融相关账务的后台业务,主要是监听链上的合约emit出的Event,把Event存储库的表中,golang用的是Gorm来访问MySQL。但是发现用以下Gorm Update一个字段的时候,在及少数情况下,小数点会计算错误, 其中 Gorm的结构对象的两个字段是用decimal.Decimal来存储的金额,当然decimal。Decimal就是可以用来做账的。

// 可用余额
BalanceAvailable decimal.Decimal `gorm:"type:decimal(36,18);not null;default:'0';check:balance_available >= '0'" json:"balance_available"`
// 冻结余额
BalanceFrozen decimal.Decimal `gorm:"type:decimal(36,18);not null;default:'0';check:balance_frozen >= '0'" json:"balance_frozen"`

如果是一下更新Balance Available Balance Frozen的金额,在极端情况下会有错误,比如计算1 - 0.7的时候,会计算成0.30000000000000004, 我不知道在gorm.Expr里面发生了什么,或者是MySQL对decimal.Decimal支持的问题,版本原因,反正是错误了

func UpdateAccountBalanceAvailable(db *gorm.DB, accountId uint64, tokenID TokenId, changeValue decimal.Decimal) error {
	return db.Model(&Account{}).Where("account_id = ? AND token_id = ?", accountId, tokenID).
		Update("balance_available", gorm.Expr("balance_available + ?",  changeValue )).Error
}


func UpdateAccountBalanceFrozen(db *gorm.DB, accountId uint64, tokenID TokenId, changeValue decimal.Decimal) error {
	return db.Model(&Account{}).Where("account_id = ? AND token_id = ?", accountId, tokenID).
		Update("balance_frozen", gorm.Expr("balance_frozen+ ?",  changeValue )).Error
}

然后修改成以下代码,就不会错误了,就是把decimal.Decimal的计算,放到gorm.Expr外面来,计算完再更新进去,我写的单元测试就过了。也就是正确了,懒得深究就为什么了,不知道是gorm还是MySQL的原因,因为我本地的MySQL是5.x。第一次做金融账务相关的业务,也算是第一次真正用golang写后端代码。坑还是不少的。我以下面的写法应该就绕过这个坑了,也不需要管到底是Gorm的问题还是MySQL的问题了。

func UpdateAccountBalanceAvailable(db *gorm.DB, accountId uint64, tokenID TokenId, changeValue decimal.Decimal) error {
	account, err := GetAndLockAccount(db, accountId, tokenID)
	if err != nil {
		return fmt.Errorf("GetAndLockAccount When UpdatesAccountBalanceAvailable error: %v", err)
	}
	updatedValue := account.BalanceAvailable.Add(changeValue)
	return db.Model(&Account{}).Where("account_id = ? AND token_id = ?", accountId, tokenID).
		Update("balance_available", updatedValue).Error
}


func UpdateAccountBalanceFrozen(db *gorm.DB, accountId uint64, tokenID TokenId, changeValue decimal.Decimal) error {
	account, err := GetAndLockAccount(db, accountId, tokenID)
	if err != nil {
		return fmt.Errorf("GetAndLockUniGasAccount When UpdateUniGasAccountBalanceFrozen error: %v", err)
	}
	updatedValue := account.BalanceFrozen.Add(changeValue)
	return db.Model(&Account{}).Where("account_id = ? AND token_id = ?", accountId, tokenID).
		Update("balance_frozen", updatedValue).Error
}

以上代码,在单元测试中,就把1 - 0.7,计算成0.3

不过我可以猜测一下,原因就在于gorm.Expr中,decimal调用的+ 的实现,绝对就不是decimal.Decimal自带的那个Add方法实现。可能跟SQL的实现有关,MySQL的概率更大些,Gorm本身有问题的概率小一些。因为我记得SQL Update语句就是可以 Update var = var + XX 这样的表达。看来以后要有个最佳实践,就是凡是涉及数据库中字段的一些计算,最好放在业务侧处理,不要用任何SQL提供的这些操作。当然哈,这里不包括SQL 提供的 COUNT SUM什么的。

@AlexiaChen AlexiaChen added 软件调试 调试技巧,思考 golang go programming language labels Aug 15, 2023
@AlexiaChen
Copy link
Owner Author

后面简单验证了一下,确实就是MySQL版本的问题,MySQL 5.7.42 会出现此问题, MySQL 8就不会了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
golang go programming language 软件调试 调试技巧,思考
Projects
None yet
Development

No branches or pull requests

1 participant