> Приходилось ли работать с базами данных? (Go)

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

Компании: Axel Pro, Wildberries, VK

Стек: Go

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

Короткий ответ

Да, работал с реляционными базами данных (PostgreSQL, MySQL) и с Redis как кэшем. В основном использовал SQL через database/sql и pgx, писал миграции, проектировал схемы, оптимизировал запросы через EXPLAIN ANALYZE. Также сталкивался с транзакциями, конкурентным доступом и проблемами блокировок. В Go работал с пулами соединений, контекстами и обработкой ошибок при работе с БД.

Подробное объяснение

В повседневной работе с БД в Go выделяю несколько ключевых аспектов:

  • Драйверы и библиотеки: для PostgreSQL чаще использую pgx (включая pgxpool для пула соединений), для MySQL - go-sql-driver/mysql. Стандартный database/sql - хорошая абстракция, но при сложных запросах или необходимости в prepared statements удобнее работать с pgx напрямую.
  • Миграции: использую golang-migrate или goose, храню миграции в репозитории, применяю при деплое.
  • Проектирование схемы: нормализация до третьей формы, индексы под частые запросы, внешние ключи для целостности.
  • Транзакции: оборачиваю операции в tx, слежу за commit/rollback, использую defer для отката при ошибке.
  • Конкурентность: пул соединений ограничиваю через MaxConns, учитываю, что каждая горутина получает отдельное соединение из пула.
  • Оптимизация: смотрю EXPLAIN ANALYZE, добавляю индексы, избегаю N+1 запросов, использую JOIN и агрегации на стороне БД.

На практике

Типичный сценарий - REST API с CRUD-операциями. Схема: таблица users с полями id, email, created_at. Для поиска по email добавляю уникальный индекс. При создании пользователя проверяю уникальность через ON CONFLICT DO NOTHING или ловлю ошибку дубликата.

Для списка с пагинацией использую LIMIT/OFFSET или cursor-based пагинацию через WHERE id > $1 ORDER BY id LIMIT $2. Второй вариант предпочтительнее при больших объёмах данных.

При работе с Redis - кэширование часто запрашиваемых данных, инвалидация по ключу, использование TTL. Для атомарных операций - INCR, SETNX для реализации распределённых блокировок.

Пример кода

GO
type User struct {
ID int64
Email string
}
func CreateUser(ctx context.Context, pool *pgxpool.Pool, email string) (*User, error) {
var user User
err := pool.QueryRow(ctx, `
INSERT INTO users (email)
VALUES ($1)
ON CONFLICT (email) DO NOTHING
RETURNING id, email
`, email).Scan(&user.ID, &user.Email)
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrDuplicateEmail
}
return &user, err
}
func GetUsers(ctx context.Context, pool *pgxpool.Pool, limit, offset int) ([]User, error) {
rows, err := pool.Query(ctx, `
SELECT id, email FROM users
ORDER BY id
LIMIT $1 OFFSET $2
`, limit, offset)
if err != nil {
return nil, err
}
defer rows.Close()
users := make([]User, 0)
for rows.Next() {
var u User
if err := rows.Scan(&u.ID, &u.Email); err != nil {
return nil, err
}
users = append(users, u)
}
return users, rows.Err()
}

Как отвечать на собеседовании

Начни с конкретики: какие БД использовал, в каких проектах, какой стек. Затем переходи к деталям - как проектировал схему, какие проблемы решал (медленные запросы, блокировки, конкурентный доступ). Обязательно упомяни, как работаешь с пулом соединений и контекстами в Go. Если спрашивают про оптимизацию - расскажи про индексы и EXPLAIN. Не бойся признаться, что не знаешь какую-то специфическую фичу, но покажи, что понимаешь общие принципы.

Что проверяет интервьюер

  • Понимание основ SQL: JOIN, GROUP BY, индексы, транзакции.
  • Умение работать с database/sql и драйверами в Go.
  • Знание пула соединений, контекстов, обработки ошибок.
  • Способность проектировать схему под нагрузку.
  • Практический опыт оптимизации запросов.
  • Понимание trade-off между нормализацией и производительностью.

Типичные ошибки

  • Не упоминать про context.Context при работе с БД - это критично для Go.
  • Говорить только про ORM (например, GORM) без понимания SQL - интервьюер может спросить про конкретный запрос.
  • Игнорировать пул соединений и его настройку.
  • Не знать разницу между database/sql и драйверами.
  • Забывать про rows.Close() и rows.Err() после итерации.
  • Предлагать SELECT * без необходимости - это плохая практика.
  • Не упоминать про миграции и управление схемой.

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

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