前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go语言开发:Gorm使用当中的坑

Go语言开发:Gorm使用当中的坑

原创
作者头像
FrancoZhang
发布2021-12-06 17:20:16
1.7K1
发布2021-12-06 17:20:16
举报
文章被收录于专栏:Gopher
  • 问题

之前用户反映了一个Redis API V3的现网问题,用户在使用API V3接口去修改实例的自动备份配置(接口名:ModifyAutoBackupConfig)的时候,出现了设置不生效的问题,用户请求Request如下:

代码语言:javascript
复制
{
    "InstanceId":"crs-3hqwaohz",
    "WeekDays":[
        "Monday",
        "Tuesday",
        "Wednesday",
        "Thursday",
        "Friday",
        "Saturday",
        "Sunday"
    ],
    "TimePeriod":"00:00-01:00"
}

该接口功能是将实例crs-3hqwaohz的自动备份时间设置为每天晚上的00:00至01:00之间,用户得到如下Response:

代码语言:javascript
复制
{
    "Response":{
        "AutoBackupType":1,
        "WeekDays":[
            "Monday",
            "Tuesday",
            "Wednesday",
            "Thursday",
            "Friday",
            "Saturday",
            "Sunday"
        ],
        "TimePeriod":"00:00-01:00",
        "RequestId":"d2671fb8-08e5-432e-b7ca-79bee157f676"
    }
}

单纯从Response看,应该是成功设置了用户的备份时间,但是问题出在用户重新使用查询实例备份配置接口的时候,获取到的信息却是没有更新时间的,备份时间并没有更新为每天的0点到1点,而是依旧使用的老配置,6点到7点备份。查询结果如下显示:

代码语言:javascript
复制
{
    "Response":{
        "AutoBackupType":1,
        "WeekDays":[
            "Monday",
            "Tuesday",
            "Wednesday",
            "Thursday",
            "Friday",
            "Saturday",
            "Sunday"
        ],
        "TimePeriod":"06:00-07:00",
        "RequestId":"e6a43f93-a73a-44f2-b984-0f02ed1c5098"
    }
}
  • 分析

这里的问题简单概括为,修改接口使用成功,查询接口也没有报错;而且修改接口的Response数据是正确的,但是查询接口返回的数据内容则是有问题的;那么首先分析,用户两次操作之间多调用了一次修改接口,才出现了这个问题。然而很快通过分析日志发现并没有出现这种用户使用的问题,日志如下:

代码语言:javascript
复制
2019/05/08 15:44:25 service_tencentcloud_redis.go:611: [DEBUG]-1 api[ModifyAutoBackupConfig] , request body [{"InstanceId":"crs-3hqwaohz","WeekDays":["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],"TimePeriod":"00:00-01:00"}], response body[{"Response":{"AutoBackupType":1,"WeekDays":["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],"TimePeriod":"00:00-01:00","RequestId":"d2671fb8-08e5-432e-b7ca-79bee157f676"}}]

2019/05/08 15:44:25 service_tencentcloud_redis.go:632: [DEBUG]-2 api[DescribeAutoBackupConfig] , request body [{"InstanceId":"crs-3hqwaohz"}], response body[{"Response":{"AutoBackupType":1,"WeekDays":["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],"TimePeriod":"06:00-07:00","RequestId":"e6a43f93-a73a-44f2-b984-0f02ed1c5098"}}]

那么可以确定的是接口有Bug存在,尝试现网/测试环境进行复现,发现均不能复现用户问题,似乎不是一个稳定复现的问题,这条思路暂时断掉;那么接下来,另外一个思路,先摸清实例当前的具体情况,需要确认用户操作的实例备份配置后端真正设置到底是几点备份,通过查看后端数据库获取后端实例信息:

这里发现备份的开始时间有问题,开始时间没有被设置成应该的值:0,而是保留了实例原来的旧值:6;另一方面,备份的结束时间却是正确的1。那么问题来了,实例在设置开始时间的时候,这个字段没有成功而且也没有报错。那么到这里推测是SQL语句有问题,查看日志,找到对应的SQL语句:

代码语言:javascript
复制
UPDATE redis_oss.redis_autobackup_config SET endtime = 1 where instanceId = 60000687;

发现只更新了结束时间,没有更新开始时间,那么这里SQL有问题?为什么自己尝试复现的时候没有问题?于是查看设置成功的时候的SQL语句:

代码语言:javascript
复制
UPDATE redis_oss.redis_autobackup_config SET starttime = 6 , endtime = 7 where instanceId = 60000687;

发现这条语句是正常的,难道是更新字段的值不同导致的?于是重新尝试复现,和用户使用的时间一致,更新为0点到1点备份,结果发现,果然只有在设置成这个时间,SQL语句生成才是有问题的。

于是,既然能稳定复现这个问题了,基本也就确认了问题出现的位置,于是查看代码,找到这条SQL语句生成的地方,代码如下:

代码语言:javascript
复制
/**
记录redis自动备份周期与时间,没有设置就设置自动备份
*/
func CreateOrUpdateAutoBackupConfig(autoBackupConfig *db_model.RedisAutobackupConfig) (err error) {
	defer func() {
		if errs := recover(); errs != nil {
			log.Error("RecordAutoBackupConfig recover err, autoBackupConfig: ", autoBackupConfig, ", err:", errs)
			err = NewRuntimeError(GetErrMsg(errs))
			return
		}
	}()
	preAutoBackupConfig, err, isFound := GetAutoBackupConfigByInstanceId(autoBackupConfig.InstanceID)
	if !isFound { // 没有找到,则插入
		return GetTxManager().GetOrmDB().Save(autoBackupConfig).Error
	}
	if preAutoBackupConfig != nil {
		err = GetTxManager().GetOrmDB().Model(&db_model.RedisAutobackupConfig{}).Where(" instanceId=? and status=?",
			autoBackupConfig.InstanceID, redis_model.AUTO_BACKUP_VALID).Update(autoBackupConfig).Error
		if err != nil {
			return err
		}
	}
	return errors.New("CreateOrUpdateAutoBackupConfig error")
}

这里利用了Gorm的Update方法来更新数据库记录,使用上来说也非常方便,只是将对象传入Update方法,即可轻松的更新数据库记录,那么也只可能是这里出现了问题,于是分析,Gorm在更新数据库记录的实例,为什么生成的SQL语句的更新字段会不一致呢?很有可能是Gorm有自己的更新字段逻辑判断,依据某一种规则来判断是否需要更新某一个字段;于是,通过查询资料了解了Gorm的字段更新策略,发现Gorm使用上的一个坑,Gorm对于对象的更新策略:

由于用户设置了开始时间为0,而该字段在对象中表示为int类型,如下所示:

代码语言:javascript
复制
type RedisAutobackupConfig struct {
	AlarmTime      string         `gorm:"column:alarmTime"`
	AutoBackupType int            `gorm:"column:autobackuptype"`
	BackupOneHour  int64          `gorm:"column:backupOneHour"`
	CreateTime     string         `gorm:"column:createtime"`
	EndTime        int            `gorm:"column:endtime"`
	ID             int64          `gorm:"column:id;primary_key"`
	InstanceID     int64          `gorm:"column:instanceId"`
	Operator       string         `gorm:"column:operator"`
	StartTime      int            `gorm:"column:starttime"`
	Status         int            `gorm:"column:status"`
	SysConf        int            `gorm:"column:sysConf"`
	UpdateTime     string         `gorm:"column:updatetime"`
	UserType       int            `gorm:"column:usertype"`
	Weekdays       string         `gorm:"column:weekdays"`
}

// TableName sets the insert table name for this struct type
func (r *RedisAutobackupConfig) TableName() string {
	return "redis_autobackup_config"
}

Gorm发现StartTime字段类型为int,int默认值为0,而用户要设置的值也是0,这里Gorm认为默认值不需要更新,字段也就没有更新,完美解释上述情况和问题。

  • 解决方案

难道Gorm这么坑?不能设置默认值0?当然有启发解决方式,最直接的方法便是直接使用手写SQL,强制执行;但这样就失去了使用Gorm的意义。那么能否使用其他方式指定需要更新的字段呢?最终选择了另外一个方式:Map;即Update方法不再使用对象去更新,而是使用Map指定字段更新,代码修复如下:

代码语言:javascript
复制
updateMap := make(map[string]interface{}, 0)
updateMap["StartTime"] = autoBackupConfig.StartTime
updateMap["EndTime"] = autoBackupConfig.EndTime

GetTxManager().GetOrmDB().Model(&db_model.RedisAutobackupConfig{}).Where(" instanceId=? and status=?",autoBackupConfig.InstanceID,redis_model.AUTO_BACKUP_VALID).Update(updateMap).Error

这样便解决了问题。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 SQL Server
腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档