Транзакции, ACID и конкурентный доступ
Транзакции и ACID спрашивают даже у аналитиков — потому что от ответа зависит, попадут ли в требования сценарии «а если упало посередине».
Транзакция — набор операций, который выполняется целиком или не выполняется вовсе. ACID — четыре свойства, которые СУБД обещает транзакции: атомарность, согласованность, изолированность, долговечность.
Вопрос 1. Что такое ACID? Объясните на примере
Что проверяют. Понимание, а не расшифровку аббревиатуры. Пример перевода денег между счетами — канонический, и его ждут.
- Atomicity (атомарность). Списание с одного счёта и зачисление на другой происходят вместе. Если сбой между операциями — откатывается всё, деньги не исчезают.
- Consistency (согласованность). После транзакции соблюдены все правила целостности: внешние ключи, ограничения, проверки. Баланс не станет отрицательным, если это запрещено ограничением.
- Isolation (изолированность). Параллельные транзакции не видят промежуточных состояний друг друга. Насколько строго — определяет уровень изоляции.
- Durability (долговечность). После подтверждения (commit) данные переживут отключение питания — они уже записаны в журнал на диск.
CREATE TABLE account (
id INTEGER PRIMARY KEY AUTOINCREMENT,
owner TEXT NOT NULL,
balance REAL NOT NULL CHECK (balance >= 0)
);
INSERT INTO account (owner, balance) VALUES ('Анна', 1000.0), ('Борис', 500.0);
-- перевод 300 рублей: обе операции в одной транзакции
BEGIN TRANSACTION;
UPDATE account SET balance = balance - 300 WHERE owner = 'Анна';
UPDATE account SET balance = balance + 300 WHERE owner = 'Борис';
COMMIT;
SELECT owner, balance FROM account ORDER BY owner;
Запустите блок: получится «Анна 700, Борис 800». Если бы между двумя UPDATE произошёл сбой, ROLLBACK вернул бы исходные 1000 и 500 — деньги не потерялись бы. Ограничение CHECK (balance >= 0) — иллюстрация согласованности: перевод 2000 рублей просто не пройдёт.
Вопрос 2. Какие бывают уровни изоляции и какие аномалии они закрывают?
Что проверяют. Средний и старший уровень. Достаточно знать четыре уровня и три аномалии и уметь связать их таблицей.
| Уровень | Грязное чтение | Неповторяющееся чтение | Фантомы |
| Read Uncommitted | возможно | возможно | возможны |
| Read Committed | нет | возможно | возможны |
| Repeatable Read | нет | нет | возможны* |
| Serializable | нет | нет | нет |
Что означают аномалии по-человечески:
- Грязное чтение — вы прочитали чужие неподтверждённые изменения, а их потом откатили.
- Неповторяющееся чтение — прочитали строку дважды внутри одной транзакции и получили разные значения, потому что кто-то её изменил и закоммитил.
- Фантомы — повторили один и тот же запрос по условию и получили новые строки, которых не было. Звёздочка в таблице: в PostgreSQL на уровне Repeatable Read фантомов не будет благодаря снимку данных, в стандарте SQL — допускаются.
Аналитику это нужно, чтобы формулировать требования к конкурентному доступу. Классический пример: два менеджера одновременно резервируют последнюю единицу товара. Требование должно явно описывать поведение: «резерв выполняется в транзакции с блокировкой строки остатка; второй запрос получает 409 и сообщение "товар закончился"».
Вопрос 3. Что такое оптимистичная и пессимистичная блокировка?
Что проверяют. Практический навык: как описать одновременное редактирование одной карточки двумя пользователями.
Пессимистичная блокировка: пользователь захватывает запись на редактирование, остальные видят «карточка занята Ивановым». Подходит для длинных редактирований и небольшого числа пользователей. Минус — забытые блокировки, нужен таймаут.
Оптимистичная блокировка: никто ничего не захватывает, но у записи есть версия. При сохранении версия сверяется; если она изменилась — сохранение отклоняется с предложением обновить данные. Дешевле и лучше масштабируется, поэтому в веб-системах используется чаще.
Оптимистичная блокировка в контракте API
GET /orders/1024 → { "id":1024, "status":"new", "version": 7 }
PATCH /orders/1024
If-Match: "7"
{ "status": "paid" }
200 OK → сохранено, version стал 8
409 Conflict → { "code":"VERSION_CONFLICT",
"detail":"Заказ изменён другим пользователем",
"currentVersion": 8 }
Требование при конфликте должно описывать поведение интерфейса: показать, кто и что изменил, предложить перезагрузить или объединить изменения — молча затирать чужую работу нельзя.
Ключи и связи: короткий блок, который любят спросить следом
- Первичный ключ (PK) — уникально идентифицирует строку, не может быть NULL.
- Естественный ключ — значение из предметной области (ИНН, номер паспорта). Плюс — осмысленность, минус — меняется и повторяется чаще, чем кажется.
- Суррогатный ключ — искусственный идентификатор (автоинкремент, UUID). Не несёт смысла и потому не меняется. Практика по умолчанию, а естественный ключ выносится в уникальный индекс.
- Внешний ключ (FK) — ссылка на первичный ключ другой таблицы, гарантирует ссылочную целостность.
- Каскадное удаление — вопрос к бизнесу, а не к разработчику: удаление клиента не должно молча уносить его заказы. Обычно вместо удаления применяют мягкое (пометкой) удаление.
Типичные ошибки кандидатов
- Расшифровывают ACID, но не могут привести пример, где нарушение атомарности видно бизнесу.
- Считают, что уровень изоляции по умолчанию — Serializable. В большинстве СУБД это Read Committed.
- Не описывают конкурентный доступ в требованиях, и «последний сохранивший затирает всех» обнаруживается на проде.
- Пытаются растянуть транзакцию на несколько сервисов. Между сервисами транзакции нет — там сага с компенсирующими операциями.
- Ставят каскадное удаление, не спросив бизнес, допустимо ли терять историю.
Как ответить кратко
Транзакция выполняется целиком или откатывается. ACID: атомарность — списание и зачисление вместе; согласованность — не нарушаются ограничения целостности; изолированность — параллельные транзакции не видят промежуточных состояний; долговечность — после commit данные переживут сбой. Уровни изоляции от Read Uncommitted до Serializable закрывают грязное чтение, неповторяющееся чтение и фантомы, по умолчанию обычно Read Committed. Как аналитик я обязан описать конкурентный доступ: для одновременного редактирования — оптимистичная блокировка по версии записи с ответом 409 и понятным сообщением, а не молчаливая перезапись. И помню, что между сервисами транзакции нет: там сага с компенсирующими операциями.