这是我参与「第三届青训营 -后端场」笔记创作活动的第5篇笔记
在本篇中,我会总结在开发青训营项目抖声过程中遇到的一些问题。
一、数据库事务
一开始,我们写数据库的增删改相关操作时没有添加事务,造成了如果操作中途服务器出现一些问题,数据库中会产生错误数据。于是,后续我们增加了事务操作。
数据库的事务(Transaction)是一种机制、一个操作序列,包含了一组数据库操作命令。事务把所有的命令作为一个整体一起向系统提交或撤销操作请求,即这一组数据库命令要么都执行,要么都不执行,因此事务是一个不可分割的工作逻辑单元。
在我们的一些数据库函数中,涉及多个增删补改的操作的,我们都增加了事务机制。
例如在./cmd/user/dal/db/user.go中的UpdateUser函数操作,当一个用户想要关注另一个用户,需要涉及User表以及Follower的操作。User表中follow_count与follower_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)