Sobes.tech
Middle+

// mahsulotlar jadvali: // ID | cnt_view // ----------- // 1 | 100 // ikki foydalanuvchi bir vaqtning o'zida id = 1 bo'lgan mahsulotni ko'rishni boshladilar // va bizning bazamizda 2 parallel tranzaksiya ishga tushirildi // natija nima bo'ladi? postgresql func IncrementView(ctx context.Context, db *sql.DB, id int64) error { tx, err := db.BeginTx(ctx, nil) if err != nil { return err } defer tx.Rollback() var cnt int err = tx.QueryRowContext(ctx, "SELECT cnt_view FROM goods WHERE id = $1", id, ).Scan(&cnt) if err != nil { return err } cnt++ _, err = tx.ExecContext(ctx, "UPDATE goods SET cnt_view = $1 WHERE id = $2", cnt, id, ) if err != nil { return err } return tx.Commit() }

sobes.tech AI

AIdan javob

В приведённом коде две параллельные транзакции одновременно читают значение cnt_view для товара с id=1, увеличивают его на 1 и записывают обратно.

В PostgreSQL по умолчанию уровень изоляции транзакций — Read Committed. Это значит, что каждая транзакция видит только зафиксированные данные на момент начала запроса.

Сценарий:

  1. Обе транзакции читают cnt_view = 100.
  2. Обе увеличивают cnt_view до 101.
  3. Обе пытаются записать 101.

Проблема — потеря обновления (lost update). Вторая транзакция при попытке коммита получит ошибку блокировки (serialization failure) или будет ждать, пока первая завершится.

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

Пример исправления:

err = tx.QueryRowContext(ctx, "SELECT cnt_view FROM goods WHERE id = $1 FOR UPDATE", id).Scan(&cnt)

Это гарантирует последовательное обновление счётчика просмотров без потери данных.