Go Gorm Atomi c Обновление счетчика приращений - PullRequest
1 голос
/ 28 апреля 2020

У меня есть счетчик, который пользователи могут увеличивать.

Тем не менее, я хочу избежать состояния гонки двух пользователей, увеличивающих счетчик одновременно.

Есть ли способ атомарно увеличивать счетчик, используя Gorm, а не извлекать значение из базы данных, увеличивать и, наконец, обновлять базу данных?

Ответы [ 3 ]

2 голосов
/ 28 апреля 2020

Если вы хотите использовать базовые функции c ORM, вы можете использовать FOR UPDATE в качестве опции запроса при извлечении записи, база данных заблокирует запись для этого указанного c соединения до это соединение выдает запрос UPDATE для изменения этой записи.

Оба оператора SELECT и UPDATE должны произойти в одном соединении, что означает, что вам нужно обернуть их в транзакция (в противном случае Go может отправить второй запрос по другому соединению).

Обратите внимание, что этот сделает каждое другое соединение, которое хочет SELECT той же записи, подождет , пока вы сделали UPDATE. Это не проблема для большинства приложений, но если у вас либо очень высокий параллелизм, либо время между SELECT ... FOR UPDATE и UPDATE после этого, это может быть не для вас.

В дополнение к FOR UPDATE, опция FOR SHARE звучит так, как будто она также может работать для вас, с меньшим количеством блокировок (но я не знаю этого достаточно хорошо, чтобы сказать это наверняка).

Примечание: Это предполагает, что вы используете СУБД, которая поддерживает SELECT ... FOR UPDATE; если это не так, обновите вопрос, чтобы сообщить нам, какую СУБД вы используете.

Другой вариант - просто go вокруг ORM и сделать db.Exec("UPDATE counter_table SET counter = counter + 1 WHERE id = ?", 42) (хотя см. { ссылка } для некоторых ловушек).

0 голосов
/ 28 апреля 2020

Возможное решение - использовать транзакции GORM (https://gorm.io/docs/transactions.html).

    err := db.Transaction(func(tx *gorm.DB) error {
        // Get model if exist
        var feature models.Feature
        if err := tx.Where("id = ?", c.Param("id")).First(&feature).Error; err != nil {
            return err
        }

        // Increment Counter
        if err := tx.Model(&feature).Update("Counter", feature.Counter+1).Error; err != nil {
            return err
        }

        return nil
    })

    if err != nil {
        c.Status(http.StatusInternalServerError)
        return
    }

    c.Status(http.StatusOK)
0 голосов
/ 28 апреля 2020

Я не уверен насчет вашего варианта использования. Пожалуйста, пример вашего использования.

Одним из возможных решений для достижения этой цели является использование пакета github.com/uber-go/atomic, но вам придется вручную вызвать метод atom.Inc, чтобы увеличить счетчик и использовать его.

Пример этого страница:

// Uint32 is a thin wrapper around the primitive uint32 type.
var atom atomic.Uint32

// The wrapper ensures that all operations are atomic.
atom.Store(42)
fmt.Println(atom.Inc())
fmt.Println(atom.CAS(43, 0))
fmt.Println(atom.Load())

Подробности см. в документации: https://pkg.go.dev/go.uber.org/atomic?tab=doc

...