Golang语言特点与技巧(五) | 青训营笔记

166 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第5篇笔记

在本篇中,我会总结在开发青训营项目抖声过程中遇到的一些问题。

一、数据库事务

一开始,我们写数据库的增删改相关操作时没有添加事务,造成了如果操作中途服务器出现一些问题,数据库中会产生错误数据。于是,后续我们增加了事务操作。

数据库的事务(Transaction)是一种机制、一个操作序列,包含了一组数据库操作命令。事务把所有的命令作为一个整体一起向系统提交或撤销操作请求,即这一组数据库命令要么都执行,要么都不执行,因此事务是一个不可分割的工作逻辑单元。

在我们的一些数据库函数中,涉及多个增删补改的操作的,我们都增加了事务机制。 例如在./cmd/user/dal/db/user.go中的UpdateUser函数操作,当一个用户想要关注另一个用户,需要涉及User表以及Follower的操作。User表中follow_countfollower_count需要修改,并且Follower表中需要增删字段。这样就需要我们添加事务,以保证原子性、一致性、隔离性和持久性。

(1)开启事务

gorm默认是开启事务的,但是如果我们不想要使用事务,可以关闭,这可以提高约30%的运行效率,如下:

&gorm.Config{
    PrepareStmt:            true,
    SkipDefaultTransaction: true, //这里我们跳过了默认事务
		},
(2)gorm的事务操作

gorm如果想要实现事务操作,我们只需要把原来的函数稍微修改一下就行,如下所示:

db.Transaction(func(tx *gorm.DB) error {
  // 我们可以把我们原来的事务加到这里的匿名函数中
  // 注意,后面的所有db都需要换成我们上面定义的tx
  if err := tx.Create(&User{Name: "LeafCCC1"}).Error; err != nil {
    return err
  }

  if err := tx.Create(&User{Name: "LeafCCC2"}).Error; err != nil {
    return err//如果发生错误 则会回滚
  }
    
  return nil//如果到了这一步返回了nil 则最终提交事务
})

我们以UpdateUser函数为例子,具体实现如下:

func UpdateUser(ctx context.Context, req *user.UpdateUserRequest) error {
	if req.UserId == constants.NotLogin {
		return nil
	} //查询用户是否存在

	//如果要关注 查询是否已经是关注状态
	//如果要取关 查询是否已经是取关状态
	var cnt int64 = 0
	if err := DB.WithContext(ctx).Model(&Follower{}).Where("user_id = ? and follower_id = ?", req.ToUserId, req.UserId).Count(&cnt).Error; err != nil {
		return err
	}

	if req.ActionType == constants.RelationAdd {
		if cnt > 0 {
			return nil
		}
	} else if req.ActionType == constants.RelationDel {
		if cnt == 0 {
			return nil
		}
	}

	//查询两个用户是否存在
	var user1 User
	var user2 User

	if err := DB.WithContext(ctx).Where("id = ?", req.UserId).First(&user1).Error; err != nil {
		return err
	}

	if err := DB.WithContext(ctx).Where("id = ?", req.ToUserId).First(&user2).Error; err != nil {
		return err
	}

	//使用事务封装
	return DB.Transaction(func(tx *gorm.DB) error {
		//先在Follow表中更改关注的关系
		//再在User表中更改follow_count与follower_count
		if req.ActionType == constants.RelationAdd {
			if err := tx.WithContext(ctx).Create(&Follower{UserID: req.ToUserId, FollowerID: req.UserId}).Error; err != nil {
				return err
			}
			user1.FollowCount += 1
			user2.FollowerCount += 1
		} else if req.ActionType == constants.RelationDel {
			if err := tx.WithContext(ctx).Where("user_id = ? and follower_id = ?", req.ToUserId, req.UserId).Delete(&Follower{}).Error; err != nil {
				return err
			}
			user1.FollowCount -= 1
			user2.FollowerCount -= 1
		}
		if err := tx.WithContext(ctx).Model(&user1).Select("follow_count").Updates(user1).Error; err != nil {
			return err
		}

		if err := tx.WithContext(ctx).Model(&user2).Select("follower_count").Updates(user2).Error; err != nil {
			return err
		}
		return nil //没有错误则提交事务
	})
}

二、数据库软删除的小bug

一般来说,gorm中删除都采取软删除的方式,但是当我们嵌套查询时,却查询出了已经被删除过的字段:

DB.WithContext(ctx).Table("favorites").Select("video_id").Where("user_id = ? ", userId).Find(&ids)).Find(&videos)

之后,我们查询条件额外查询是否已经删除即可,加上AND deleted_at is null:

DB.WithContext(ctx).Table("favorites").Select("video_id").Where("user_id = ? AND deleted_at is null", userId).Find(&ids)).Find(&videos)