Какие типы локов существуют в базах данных
Что проверяют
Понимаете ли вы, как база разводит конкурентные транзакции, и умеете ли применить это из Go-кода. Обычно следом просят показать списание баланса без гонки и спрашивают, что будет при взаимной блокировке.
Как отвечать
Разложите локи по двум осям: режим и гранулярность. Опирайтесь на PostgreSQL, с ним Go-сервисы работают чаще всего:
- разделяемый и исключительный: shared совместим с другими shared, exclusive несовместим ни с чем. Остальная классификация строится на этой матрице совместимости;
- строчные:
SELECT ... FOR UPDATE,FOR NO KEY UPDATE,FOR SHARE,FOR KEY SHARE. ОбычныйSELECTблагодаря MVCC строчными локами не блокируется; - табличные: восемь режимов, от
ACCESS SHAREу любого чтения доACCESS EXCLUSIVEу большинстваALTER TABLE; - advisory:
pg_advisory_xact_lock(key), прикладная блокировка по числу, когда сериализовать нужно задачу, а не строку; - предикатные: их берёт уровень
SERIALIZABLE, чтобы поймать аномалию и откатить транзакцию с ошибкой сериализации.
Добавьте, что в MySQL InnoDB есть gap- и next-key-локи на диапазоны, а оптимистическая блокировка через колонку version и пессимистическая через FOR UPDATE это стратегии, а не типы локов. Для очередей задач назовите FOR UPDATE SKIP LOCKED.
В Go лок живёт внутри транзакции из db.BeginTx(ctx, nil) или pool.Begin(ctx) в pgx и держится до Commit или Rollback.
Где спотыкаются
Называют deadlock типом лока. Это ситуация, а не режим: PostgreSQL ищет цикл после deadlock_timeout (по умолчанию 1 секунда) и отменяет одну транзакцию с кодом 40P01, а приложение должно её повторить. Профилактика: брать строки в одном порядке.
Делают FOR UPDATE вне транзакции. В автокоммите лок снимается сразу после запроса и ничего не защищает.
Держат транзакцию открытой во время HTTP-вызова, и строки стоят заблокированными секундами. Рядом та же ошибка с пулом: сессионный pg_advisory_lock снимается только с того соединения, где его взяли, а пул отдаёт вам другое.