Как работает долговечность транзакции в базе данных при сбое питания
Что проверяют
Знаете ли вы, что именно гарантирует буква D в ACID и каким механизмом база её выполняет, а не просто повторяете слово «журнал». Следом часто спрашивают, что будет, если питание пропало в момент commit(), и что при этом видит Java-клиент.
Как отвечать
Долговечность обещает одно: транзакция, коммит которой подтверждён клиенту, переживёт падение процесса или сервера. Незафиксированное при сбое как раз должно пропасть.
Механизм почти везде один, Write-Ahead Log:
- сначала журнал: изменения пишутся в последовательный журнал, а страницы таблиц правятся в памяти без немедленного сброса на диск;
- fsync на коммите: база отвечает «закоммичено» только после того, как запись журнала сброшена на носитель. В PostgreSQL за это отвечает
synchronous_commit = on(значение по умолчанию), в InnoDBinnodb_flush_log_at_trx_commit = 1; - контрольные точки: страницы данных периодически сбрасываются на диск, и старый журнал для восстановления больше не нужен;
- восстановление: после рестарта база проигрывает журнал от последней контрольной точки (redo). Незавершённые транзакции в PostgreSQL остаются невидимыми, потому что их статус не «зафиксирована», а InnoDB откатывает их по undo-логу;
- рваные страницы: страница могла записаться наполовину, от этого защищают
full_page_writesв PostgreSQL и doublewrite buffer в InnoDB.
Отдельно скажите про железо: если диск держит запись в кэше без защиты от потери питания и рапортует о ней раньше времени, никакой WAL не спасёт.
Для Java: если соединение оборвалось во время commit(), клиент получает SQLException и не знает, прошла ли транзакция. Поэтому повторная попытка должна быть идемпотентной.
Где спотыкаются
Приплетают MVCC и потоковую репликацию. MVCC отвечает за изоляцию, синхронная реплика может усилить защиту, но базовая гарантия на одном узле держится на WAL и fsync.
Говорят, что сохраняются и изменения неудачной транзакции. Долговечно только то, что подтверждено коммитом.
Советуют ради скорости выключить fsync. Это риск испорченного кластера. synchronous_commit = off безопаснее: при сбое теряются последние транзакции, но база остаётся целостной.