// table goods: // ID | cnt_view // ----------- // 1 | 100 // 2 пользователя одновременно начали смотреть товар с id = 1 // и у нас в базе запустилось 2 параллельные транзакции // нужно объяснить какой будет результат? postgress 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
В приведённом коде две параллельные транзакции одновременно читают значение cnt_view для товара с id=1, увеличивают его на 1 и записывают обратно.
В PostgreSQL по умолчанию уровень изоляции транзакций — Read Committed. Это значит, что каждая транзакция видит только зафиксированные данные на момент начала запроса.
Сценарий:
- Обе транзакции читают cnt_view = 100.
- Обе увеличивают cnt_view до 101.
- Обе пытаются записать 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)
Это гарантирует последовательное обновление счётчика просмотров без потери данных.