Транзакции

Транзакции являются фундаментальной концепцией любых систем баз данных. Важным моментом транзакции является то, что она объединяет несколько последовательных операций в одну операцию «все или ничего». Промежуточные состояния между операциями не видны другим параллельным транзакциям, и если происходит какой-либо сбой, препятствующий завершению транзакции, то ни одна из операций никак не влияет на базу данных.

Например, рассмотрим банковскую базу данных, которая содержит остатки по различным счетам клиентов, а также общие остатки по депозитам для филиалов. Предположим, что мы хотим записать платеж в размере 100 долларов со счета Алисы на счет Боба. Сильно упрощая, SQL-команды для этого могут выглядеть так:

UPDATE accounts SET balance = balance - 100.00
    WHERE name = 'Alice';
UPDATE branches SET balance = balance - 100.00
    WHERE name = (SELECT branch_name FROM accounts WHERE name = 'Alice');
UPDATE accounts SET balance = balance + 100.00
    WHERE name = 'Bob';
UPDATE branches SET balance = balance + 100.00
    WHERE name = (SELECT branch_name FROM accounts WHERE name = 'Bob');

Детали этих команд здесь не важны; важно отметить, что для выполнения этой довольно простой операции требуется несколько отдельных обновлений. Сотрудники банка хотят быть уверены, что либо все эти обновления произойдут, либо ни одно из них не произойдет. Конечно, не годится, чтобы системный сбой привел к тому, что Боб получит 100 долларов, которые не были списаны со счёта Алисы. Также Алиса не останется счастливым клиентом, если у нее со счёта будет списана сумма без зачисления Бобу. Нам нужна гарантия того, что если во время операции что-то пойдет не так, то ни одна из выполненных до сих пор операций не вступит в силу. Группировка обновлений в транзакцию дает нам эту гарантию. Транзакция называется атомарной: с точки зрения других транзакций она либо происходит полностью, либо вообще не происходит.

Мы также хотим гарантировать, что после того, как транзакция будет завершена и подтверждена системой базы данных, она действительно будет записана в долговременное хранилище и не будет потеряна, даже если вскоре после этого произойдет сбой системы. Например, если мы записываем снятие наличных Бобом, мы не хотим иметь никаких шансов на то, что дебет его счета исчезнет в результате аварии сразу после того, как он выйдет из двери банка. Транзакционная база данных гарантирует, что все обновления, сделанные транзакцией, будут зарегистрированы в постоянном хранилище (то есть, на диске) до того, как будет сообщено о завершении транзакции.

Еще одно важное свойство транзакционных баз данных тесно связано с понятием атомарных обновлений: когда одновременно выполняется несколько транзакций, каждая из них не должна видеть неполные изменения, внесенные другими. Например, если одна транзакция занята суммированием всех остатков в филиалах, не годится, чтобы она включала дебет из филиала Алисы, но не включила кредит из филиала Боба, и наоборот. Таким образом, транзакции должны выполнять «все или ничего» не только с точки зрения их постоянного воздействия на базу данных, но также с точки зрения видимости во время их исполнения. Обновления, сделанные открытой транзакцией, невидимы для других транзакций, до тех пор, пока транзакция не завершится, после чего все обновления станут видимыми одновременно.

В QHB транзакция устанавливается путем окружения команд SQL транзакции командами BEGIN и COMMIT. Таким образом, наша банковская транзакция будет выглядеть так:

BEGIN;
UPDATE accounts SET balance = balance - 100.00
    WHERE name = 'Alice';
-- etc etc
COMMIT;

Если в ходе транзакции мы решили, что не хотим фиксировать (возможно, мы только что заметили, что баланс Алисы стал отрицательным), мы можем выполнить команду ROLLBACK вместо COMMIT, и все наши обновления будут отменены.

QHB фактически обрабатывает каждый оператор SQL как выполняемый в транзакции. Если вы не выполните команду BEGIN, то каждый отдельный оператор имеет неявный BEGIN и (в случае успеха) COMMIT обернутый вокруг него. Группу операторов, окруженную BEGIN и COMMIT, иногда называют блоком транзакции.

Заметка
Некоторые клиентские библиотеки выдают команды BEGIN и COMMIT автоматически, так что вы можете получить эффект блоков транзакций без запроса. Проверьте документацию для прикладного интерфейса, который вы используете.

Можно управлять транзакциями более детально, используя точки сохранения. Точки сохранения позволяют вам выборочно отбрасывать части транзакции, в то же время фиксируя остальные. После определения точки сохранения с помощью SAVEPOINT вы можете при необходимости откатиться до точки сохранения с помощью ROLLBACK TO. Все изменения базы данных транзакции между определением точки сохранения и откатом к ней отбрасываются, но изменения, сохраненные раньше, чем точка сохранения, сохраняются.

После отката к точке сохранения она продолжает оставаться определённой, поэтому вы можете откатиться к ней несколько раз. И наоборот, если вы уверены, что вам не нужно будет снова возвращаться к определенной точке сохранения, она может быть освобождена, для того что бы освободить некоторые ресурсы системы. Имейте в виду, что освобождение или откат к точке сохранения автоматически сбросит все точки сохранения, которые были определены после нее.

Все это происходит внутри блока транзакции, поэтому ни одно из тзменений не видно другим сеансам базы данных. Когда и если вы фиксируете блок транзакции, зафиксированные действия становятся видимыми как единое целое для других сеансов, тогда как отмененные действия никогда не становятся видимыми вообще.

Вспоминая банковскую базу данных, предположим, что мы списываем 100,00 долларов со счета Алисы и зачисляем на счет Боба, чтобы потом выяснить, что мы должны были зачислить счет Уолли. Мы могли бы сделать это, используя точки сохранения, например:

BEGIN;
UPDATE accounts SET balance = balance - 100.00
    WHERE name = 'Alice';
SAVEPOINT my_savepoint;
UPDATE accounts SET balance = balance + 100.00
    WHERE name = 'Bob';
-- oops ... forget that and use Wally's account
ROLLBACK TO my_savepoint;
UPDATE accounts SET balance = balance + 100.00
    WHERE name = 'Wally';
COMMIT;

Этот пример, конечно, упрощен, но в блоке транзакций возможен больший контроль с помощью точек сохранения. Более того, ROLLBACK TO — это единственный способ восстановить контроль над блоком транзакции, который был переведен системой в прерванное состояние из-за ошибки, если не считать полного отката и повторного запуска.