> Как реализовать выдачу книги одному читателю с учетом уникальности и предотвращения конфликтов (Go)

Уровень: senior · Роль: backend · Категория: Технические вопросы

Компании: Ozon

Стек: Go

> Пример ответа

Для реализации выдачи книги с гарантией уникальности и предотвращения конфликтов в Go используем транзакционный подход с оптимистичной блокировкой. Пример на PostgreSQL:

GO
type BookService struct {
db *sql.DB
}
func (s *BookService) IssueBook(ctx context.Context, bookID, readerID int) error {
tx, err := s.db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
if err != nil {
return err
}
defer tx.Rollback()
// Проверяем уникальность: книга не должна быть выдана другому
var status string
err = tx.QueryRowContext(ctx,
`SELECT status FROM books WHERE id = $1 FOR UPDATE`, bookID).Scan(&status)
if err != nil {
return err
}
if status != "available" {
return fmt.Errorf("книга уже выдана или недоступна")
}
// Обновляем статус и записываем выдачу
_, err = tx.ExecContext(ctx,
`UPDATE books SET status = 'issued', issued_to = $1 WHERE id = $2`,
readerID, bookID)
if err != nil {
return err
}
_, err = tx.ExecContext(ctx,
`INSERT INTO issues (book_id, reader_id, issued_at) VALUES ($1, $2, NOW())`,
bookID, readerID)
if err != nil {
return err
}
return tx.Commit()
}

Ключевые моменты:

  • Используем уровень изоляции Serializable для предотвращения race conditions.

  • SELECT ... FOR UPDATE блокирует строку книги до завершения транзакции.

  • Проверка статуса внутри транзакции гарантирует, что книга не будет выдана дважды.

  • При конкурентном доступе одна из транзакций получит ошибку сериализации - её нужно обработать с повторной попыткой (retry).

Для высоконагруженных систем можно добавить распределённую блокировку через Redis (SETNX) или использовать уникальный индекс на (book_id, status) с условием WHERE status = 'issued'.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью