使用go实现通过乐观锁对数据库操作 | 青训营
作者:LoHhhha
时间:2023.8.15
背景
在进行青训营大作业的编写过程中,我们需要维护用户user的follow_count、follower_count、work_count、favorite_count、tolal_favorited以及video、comment的一些属性参数,这些都需要我们对数据库行进行更新修改。如果我们并不使用任何处理,只在需要更新的时候,给数据库一条指令UPDATE users SET favorite_count=? WHERE id=?,在并发场景及其可能导致数据修改错误。
所以我们需要对我们的操作加“锁”。
介绍
悲观锁
悲观锁在操作数据时比较悲观,认为别人会同时修改数据。
在修改时悲观锁需要将一整行数据先锁定,不让别的进程进入修改,但这种处理方案会引入新的开销。
乐观锁
悲观锁在操作数据时比较乐观,认为别人不会同时修改数据。
在修改时悲观锁会干以下几件事情:
-
读取表中欲修改元素数据(或引入版本号概念)
-
修改是查看修改元素数据是否与之前读取的一致
- 一致:直接写入,完成操作
- 不一致:重新回到第一步
我们可以很清楚的看见一种可能:我们有可能一直卡在这个流程之中,无法完成修改,所以乐观锁需要引入最大失败次数来强制退出修改。
实现
增值更新
// __UpdateByOptimisticLockUseIncrement__
// @param: queryGet(string), queryUpdate(string), id(int64), increment(int64)
// @return: err(error)
// queryGet format as "select change_element from table_chose where id=?" values(id) -> pre
// queryUpdate format as "update set change_element=? from table_chose where id=? and change_element=?" values(pre+increment,id,pre)
func __UpdateByOptimisticLockUseIncrement__(queryGet string, queryUpdate string, id int64, increment int64) error {
for cnt := 0; cnt < settings.OptimisticLockMaxTryNumber; cnt++ {
var pre int64
err := Database.QueryRow(queryGet, id).Scan(&pre)
if err != nil {
return err
}
res, err := Database.Exec(queryUpdate, pre+increment, id, pre)
if err != nil {
return err
}
affected, err := res.RowsAffected()
if err != nil {
return err
}
if affected > 0 {
return nil
}
}
return fmt.Errorf("fail to update after use so much times")
}
新值更新
// __UpdateByOptimisticLock__
// @param: queryGet(string), queryUpdate(string), id(int64), newNumber(int64)
// @return: err(error)
// queryGet format as "select change_element from table_chose where id=?" values(id) -> pre
// queryUpdate format as "update set change_element=? from table_chose where id=? and change_element=?" values(newNumber,id,pre)
func __UpdateByOptimisticLock__(queryGet string, queryUpdate string, id int64, newNumber int64) error {
for cnt := 0; cnt < settings.OptimisticLockMaxTryNumber; cnt++ {
var pre int64
err := Database.QueryRow(queryGet, id).Scan(&pre)
if err != nil {
return err
}
if newNumber == pre {
return nil
}
res, err := Database.Exec(queryUpdate, newNumber, id, pre)
if err != nil {
return err
}
affected, err := res.RowsAffected()
if err != nil {
return err
}
if affected > 0 {
return nil
}
}
return fmt.Errorf("fail to update after use so much times")
}
使用
当我们需要对queryGet,queryUpdate进行定义。
比如以下对users表下的favorite_count修改的案例:
// UpdateUserFavoriteCountByIdUseIncrement
// @param: id(int64), increment(int64)
// @return: err(error)
/* Attention: Except that the user is really exist */
func UpdateUserFavoriteCountByIdUseIncrement(id int64, increment int64) error {
queryGet := "SELECT favorite_count FROM users WHERE id=?"
queryUpdate := "UPDATE users SET favorite_count=? WHERE id=? AND favorite_count=?"
return __UpdateByOptimisticLockUseIncrement__(queryGet, queryUpdate, id, increment)
}
// UpdateUserFavoriteCountById
// @param: id(int64), increment(int64)
// @return: err(error)
/* Attention: Except that the user is really exist */
func UpdateUserFavoriteCountById(id int64, newNumber int64) error {
queryGet := "SELECT favorite_count FROM users WHERE id=?"
queryUpdate := "UPDATE users SET favorite_count=? WHERE id=? AND favorite_count=?"
return __UpdateByOptimisticLock__(queryGet, queryUpdate, id, newNumber)
}