使用go实现通过乐观锁对数据库操作 | 青训营

299 阅读2分钟

使用go实现通过乐观锁对数据库操作 | 青训营

作者:LoHhhha 时间:2023.8.15

背景

在进行青训营大作业的编写过程中,我们需要维护用户userfollow_countfollower_countwork_countfavorite_counttolal_favorited以及videocomment的一些属性参数,这些都需要我们对数据库行进行更新修改。如果我们并不使用任何处理,只在需要更新的时候,给数据库一条指令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")
}

使用

当我们需要对queryGetqueryUpdate进行定义。

比如以下对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)
}