> Как избежать гонок и рейсов в базе данных (Go)
Уровень: middle · Роль: backend · Язык: Go · Категория: Технические вопросы
Компании: Wildberries
Стек: Go
> Пример ответа
Гонки (race conditions) и рейсы (deadlocks) в базах данных - частые проблемы при конкурентном доступе. В Go с типичным стеком (PostgreSQL, MySQL) основные методы предотвращения:
-
Транзакции с правильным уровнем изоляции
ИспользуйтеSERIALIZABLEдля критичных операций (например, бронирование билетов). Это гарантирует, что параллельные транзакции не приведут к несогласованности. Пример на Go сdatabase/sql:GOtx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})// выполнение запросовtx.Commit() -
Оптимистичные блокировки (optimistic locking)
Добавьте полеversionв таблицу. При обновлении проверяйте, что версия не изменилась:SQLUPDATE items SET value = $1, version = version + 1 WHERE id = $2 AND version = $3Если
RowsAffected == 0- конфликт, повторите операцию. -
Пессимистичные блокировки (SELECT FOR UPDATE)
Для предотвращения гонок при чтении-записи используйте блокировку строки:GOtx.ExecContext(ctx, "SELECT id FROM accounts WHERE id = $1 FOR UPDATE")// затем обновление -
Избегание deadlock’ов
- Всегда блокируйте ресурсы в одном и том же порядке (например, по возрастанию ID).
- Используйте таймауты для транзакций (
SET lock_timeout = '5s'в PostgreSQL). - В Go задавайте контекст с дедлайном:
ctx, cancel := context.WithTimeout(ctx, 3*time.Second).
-
Правильная обработка ошибок
При получении40001(serialization failure) или40P01(deadlock detected) - повторяйте транзакцию с экспоненциальной задержкой.
Пример комплексного подхода для Go:
GOfunc updateBalance(ctx context.Context, db *sql.DB, userID int, amount int) error {for retries := 0; retries < 3; retries++ {tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})if err != nil { return err }// Пессимистичная блокировка_, err = tx.ExecContext(ctx, "SELECT balance FROM users WHERE id = $1 FOR UPDATE", userID)if err != nil { tx.Rollback(); continue }_, err = tx.ExecContext(ctx, "UPDATE users SET balance = balance + $1 WHERE id = $2", amount, userID)if err != nil {tx.Rollback()if isDeadlockOrSerialization(err) { continue }return err}return tx.Commit()}return fmt.Errorf("failed after retries")}
Ключевое - тестировать под нагрузкой (например, с pgbench или go test -race) и выбирать стратегию исходя из частоты конфликтов: для редких - оптимистичные блокировки, для частых - пессимистичные.
> Похожие задачи по Go
Какие группы паттернов проектирования существуют
Для чего нужен брокер сообщений Kafka
Сколько букв нужно для поиска в индексе
Как индекс определяет, какую запись отдать при поиске по таблице
> Похожие задачи по backend
Какие группы паттернов проектирования существуют
Для чего нужен брокер сообщений Kafka
Сколько букв нужно для поиска в индексе
Как индекс определяет, какую запись отдать при поиске по таблице
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью