часть первая
Информационные системы и обработка транзакций
К современным информационным системам предъявляются жесткие требования надежности. Никакие отказы и сбои не должны порождать рассогласование данных информационной системы. Не менее важно предотвращать рассогласование данных, порождаемое параллельной работой нескольких пользователей с одними и теми же данными.
Одним из распространенных методов обеспечения отказоустойчивости систем является восстановление ближайшего по времени корректного состояния системы. Этот принцип ложится в основу обработки транзакций.
Все операции с данными можно представить в виде набора логических единиц работы, которые, возможно, пересекаются по времени выполнения. Считается, что изначально данные информационной системы согласованы и ее состояние корректно. Это означает, что:
- удовлетворяются все ограничения, накладываемые на наборы данных моделью системы (логическая целостность);
- состояние файлов хранилища данных корректно (физическая целостность), т. е. ни один из элементов хранилища не является испорченным с точки зрения СУБД и, возможно, других систем, если физическую целостность хранилища обеспечивает не только СУБД.
Очевидно, что требуется обеспечить согласованность данных информационной системы в течение всего времени ее работы.
Очевидно, что операции чтения данных, не изменяющие состояния базы данных, не могут нарушить целостность данных. Операции модификации данных информационной системы всегда должны довольно жестко контролироваться. На данный момент наиболее распространены механизмы контроля операций с данными информационной системы (как операций записи данных, так и операций чтения данных) посредством транзакций.
Приведем пример простой информационной системы: пусть в ней содержатся данные о клиентах банка и информация о текущем состоянии счета каждого клиента (например, для простоты, в одной валюте — в рублях). Пусть эта упрощенная информационная система обеспечивает единственную операцию — перевод денег с одного счета на другой. Очевидно, что такая операция перевода денег может быть разложена на две атомарные операции: (1) снятие суммы с одного счета и (2) добавление суммы к другому счету. Очевидно, что информационная система будет всегда находиться в корректном состоянии, если допускается выполнение только пар таких операций. Тогда не будут возникать суммы, которые уже сняты с одного счета, но ни на какой другой счет еще не поступили. Также очевидно, что если пользователь получил подтверждение перевода денег с одного счета на другой, то информация об этой операции ни в коем случае не должна быть утеряна, а сохранность этой информации не должна зависеть от возможных отказов информационной системы.
В случае отказа пользовательской части (например, в случае разрыва связи с информационной системой), который произошел в промежуток времени между выполнением действия (1) и действия (2), операция перевода денег аннулируется, так для сохранения согласованности данных требуется прохождение пары атомарных операций. В случае отказа системы непосредственно после того, как она послала уведомление пользователю, что перевод денег осуществлен успешно, информация об этой операции должна быть сохранена. Обе эти ситуации отказа должны быть обработаны. Если информационная система не предоставляет сервис такого уровня, то она ненадежна, а в современных условиях просто не пригодна к эксплуатации.
В данном примере продемонстрирована простейшая транзакция — перевод денег с одного счета на другой. Здесь она является логически завершенной последовательностью действий над данными информационной системы, не нарушающей ограничения целостности. Очевидно, что при работе нескольких пользователей с данной информационной системой согласованность данных должна быть обеспечена на том же уровне, что и при работе с информационной системой единственного пользователя. Пользователь не должен видеть, что кроме него с системой работают еще какие-то пользователи.
С одной стороны, информационная система должна автоматически обеспечивать согласованность данных при любых сбоях, а так же обеспечивать сохранность данных. С другой стороны, никакие действия пользовательских приложений не должны приводить к рассогласованию данных информационной системы. Так, в приведенном выше примере информационной системы, не допустимы приложения, выполняющие только действие (1) или выполняющие только действие (2). Это означает, что набор действий над данными информационной системы строго ограничивается правилами. Для упомянутой выше информационной системы таким правилом является разрешение выполнения только пар операций (1) и (2) и запрет любых других действий над данными информационной системы.
Современные информационные системы обеспечивают выполнение запросов нескольких пользователей одновременно. Трудно представить, например, банк, который в каждый момент времени обслуживает одного единственного клиента. У пользователя должна создаваться иллюзия монопольного доступа к системе; параллельная работа нескольких пользователей не должна приводить к рассогласованию данных информационной системы. Вопросы предотвращения такого рассогласования рассматриваются в разделе «Изолированность транзакций» второй части данной публикации (см. следующий выпуск журнала «Открытые системы»).
Неформальное определение транзакции
Приведем неформальные определения транзакции с точек зрения пользователя информационной системы, прикладного программиста и СУБД.
Пользователю информационной системы предоставляется достаточно жесткий интерфейс, позволяющий выполнять строго определенные действия с данными. Для пользователя существуют только логически завершенные операции с данными, которые могут быть выполнены только целиком.
Для пользователя обработка транзакций скрыта интерфейсом приложения. Пользователь может видеть лишь внешние проявления обработки транзакций.
- Данные сохраняются в информационной системе, если пользователь получил от системы уведомление об успешном завершении операций (например, сообщение о завершении перевода денег с его счета на счет магазина).
- У пользователя создается иллюзия монопольного доступа к информационной системе.
- Если по каким-то причинам приложение отказывает в тот момент, когда пользователь еще не завершил работу с информационной системой, то он может быть уверен, что данные не изменятся. Например, если пользователь уже указал номер своей кредитной карточки и сумму, которую он платит за товар в магазине, но еще не подтвердил перевод денег на счет магазина и в этот момент система отказала, то деньги со счета пользователя не переведут. Пользователь должен быть в этом уверен. Он может убедиться в том, что сумма на его счете не изменилась, запросив после восстановления системы состояние своего счета.
Для прикладного программиста СУБД предоставляет строго определенный интерфейс, позволяющий указывать действия над данными. Программисту доступен набор простых операций, которые могут выполняться как над единственным объектом данных (например, операция добавления одной записи), так и над множеством таких объектов (например, операция удаления всех записей в таблице). Каждая такая операция является атомарной — она выполняется над всем множеством целевых данных или не выполняется вообще. Например, если выполняется операции удаления набора записей из таблицы, и при удалении десятой по порядку записи возникает ошибка, то произойдет не только прерывание выполнения операции, но будет аннулировано и удаление девяти первых записей. В частности, каждый оператор SQL является атомарным. Кроме того, атомарные операции с данными могут быть объединены в блоки, так как достаточно большой класс действий над данными не может быть выражен одной такой операцией. Очевидно, что для обеспечения согласованности данных информационной системы прикладной программист должен иметь инструмент для определения атомарных блоков операций с данными. Такой блок операций выполняется целиком или не выполняется вовсе. Например, пусть при оформлении документов при продаже товара со склада выполняются следующие операции:
- занесение информации о документе (например, накладной) - номер документа, информация о покупателе, информация об отпуске товара (например, номер склада или иная информация);
- занесение информации о продаваемом товаре - какой товар продан, сколько, на какую сумму (для каждого из товаров, указанных в накладной);
- уменьшение количества товара на складе ровно на такое число единиц, сколько продано согласно данному документу.
Такое сложное логическое действие не может быть произведено одной атомарной операцией над данными, так как требует изменения информации о разных объектах. В то же время, для обеспечения согласованности данных информационной системы должны быть выполнены все три действия или не выполнено ни одно. Атомарный блок операций начинается оператором begin transaction и завершается оператором commit transaction. Говорят, что в этом случае транзакция фиксируется.
Очевидно, что прикладной программист должен иметь возможность обрабатывать возможные отклонения от правильной работы приложения. Пусть логическое действие над данными состоит из трех простейших операций, и предположим, что при выполнении второй операции возникла ошибка — отклонение от эталона (эталоном служит ситуация, когда все три операции выполняются без ошибок). В этом случае выполнение логического действия должно быть прервано и все операции аннулированы. Для этого прикладному программисту предоставляется интерфейс прерывания и отката логического действия — rollback transaction. Говорят, что в этом случае транзакция откатывается.
Операции commit transaction, rollback transaction являются точками синхронизации обработки транзакций. Данные сохраняются в информационной системе только после успешного выполнения операции commit transaction; только после этого они могут быть доступны другим приложениям. В дальнейшем такие данные будем называть зафиксированными. Функциональность сохранения фиксированных данных предоставляет прикладному программисту СУБД.
Кроме явного объявления начала транзакции begin transaction возможно также неявное объявление начала транзакции.
- Транзакцию может запустить операция установления соединения с сервером connect.
- Транзакцию может стартовать операция фиксации или отката транзакции - commit или rollback.
- В некоторых случаях допускается "сдвиг" старта транзакции на момент выполнения первого оператора данной транзакции (тот промежуток времени, когда приложение установило соединение с сервером или завершило предыдущую единицу работы, но еще не подало ни одного оператора можно считать чистым простоем приложения, и в некоторых случаях этот интервал времени простоя приложения можно игнорировать без потерь); такие сдвиги момента старта транзакции связаны с изолированностью транзакций; эти вопросы подробнее рассмотриваются ниже.
Кроме явного объявления завершения транзакции commit transaction или rollback transaction возможно неявное объявление завершения транзакции.
- Транзакцию может завершить операция закрытия соединения с сервером disconnect, что эквивалентно операции commit transaction и закрытию соединения с сервером.
- Транзакцию может завершить операция разрыва соединения с сервером kill соединения, что эквивалентно операции rollback transaction и закрытию соединения с сервером.
- Транзакцию может завершить операция некорректного завершения работы приложения (отказ приложения и его аварийное завершение), что эквивалентно операции rollback transaction и закрытию соединения с сервером; сервер или сетевые компоненты опознают факт аварийного завершения приложения; в этом случае все незавершенные транзакции приложения откатываются.
Последние два типа неявного завершения транзакции поддерживаются только на стороне СУБД. Пользовательское приложение, как правило, может опознавать собственные отказы и аварийные закрытия соединения с сервером (таймаут, обработка прерываний и т.п.), но контролировать их не может.
В случае отказа оборудования, операционной системы, СУБД все зафиксированные данные сохраняются. Это также обеспечивает СУБД.
Приложение должно иметь возможность считывать согласованные данные информационной системы и не должно порождать рассогласование данных информационной системы. Оба эти свойства обеспечиваются с одной стороны, СУБД (она должна предоставлять согласованные данные по корректному запросу), а с другой стороны, приложением (оно не должно выполнять действия, ведущие к рассогласованию данных, например, заведомо некорректную транзакцию). При работе нескольких приложений с СУБД, все получаемые приложениями данные должны быть согласованы — и эту функциональность обеспечивает СУБД. Приложения при своей последовательной работе не должны вызывать рассогласование данных (например, недопустима операция commit transaction в середине логического действия или при возникновении ошибки в процессе выполнения логического действия). Эту функциональность должно обеспечивать приложение, поскольку СУБД не обладает никакой информацией о том, какой операцией должно завершаться логическое действие и какие ошибки выполнения операций этого действия являются фатальными, а какие можно и игнорировать. Все это в состоянии определить только прикладной программист. СУБД же воспринимает логическое действие приложения только как поток операций, которые должны быть завершены операциями commit transaction или rollback transaction.
СУБД должна обеспечивать прикладному программисту интерфейс управления изолированностью приложения. Должна обеспечиваться поддержка такого поведения параллельно выполняющихся приложений, как если бы они выполнялись последовательно. Это свойство изолированности является сложно достижимым, поскольку очень трудно одновременно обеспечить высокую степень параллельности выполнения запросов различных приложений и высокий уровень изолированности каждого из параллельно выполняющихся приложений. Выполнение этой задачи в СУБД порождает достаточно высокие накладные расходы. Вопросы изолированности транзакций подробно рассматриваются в соответствующем разделе.
СУБД получает от прикладного приложения поток операций, разделенный на атомарные блоки (транзакции) операторами старта и завершения транзакций. СУБД также получает информацию о том, насколько изолирован данный атомарный блок операций от других блоков операций, обрабатываемых СУБД одновременно с данным. СУБД ничего не известно о логике работы приложения; она выполняет только набор передаваемых операций — операцию начала транзакции, операции с данными внутри транзакции, операцию завершения транзакции. С момента начала выполнения атомарного блока операций все модифицированные этими операциями элементы данных, а также, возможно, все или часть считанных данных сохраняются в локальном временном хранилище. Для обеспечения согласованности постоянного хранилища данных незафиксированные данные в него бесконтрольно не попадают. Только после выполнения операции фиксации транзакции данные из временного хранилища попадают в постоянное хранилище и становятся доступными другим транзакциям.
Для СУБД операции с данными делятся на следующие категории:
- операции чтения данных (select);
- операции записи данных (insert, delete, update);
- операции определения объектов данных (create/alter/drop user, role, table, view, synonym, stored procedure, trigger и т.п.);
- операции синхронизации (connect, disconnect, begin transaction, commit/rollback transaction).
В некоторых реализациях операции определения объектов данных выполняются вне контекста транзакции (не требуют явной фиксации). В эту категорию входят операции, которые модифицируют системный каталог. Каждая из этих операций является атомарной модификацией системного каталога и не ведет к его рассогласованию. Они достаточно жестко контролируются СУБД. Поэтому в некоторых реализациях допускают выведение операций определения объектов данных за контекст транзакций, а изменения системного каталога видны всем другим транзакциям сразу же после выполнения соответствующей операции. Откат таких операций невозможен. Допустимо только удаление или изменение определенных объектов данных соответствующими операторами drop/alter. Имеются реализации, где операции определения объектов данных приравниваются к операциям выборки и модификации данных (операции над системным каталогом приравниваются к операциям над объектами данных пользователей) — они выполняются в контексте транзакции. В этом случае операцию создания, модификации, удаления объекта данных можно зафиксировать или откатить точно так же, как операции выборки или модификации данных.
Единственный вид операций над данными, которые нельзя прервать или аннулировать, составляют операции синхронизации. В большинстве реализаций такие операции являются непрерываемыми — запрещается прерывание такой операции любыми другими операциями в СУБД.
В СУБД имеются механизмы выявления некорректного завершения приложений; все незавершенные транзакции таких приложений откатываются. «Живучесть» пользовательских приложений, как правило, отслеживается сетевыми компонентами СУБД; методы слежения — передача контрольного сообщения и ожидание ответа, опрос списка активных приложений.
В СУБД имеются механизмы, обеспечивающие восстановление системы в случае сбоя — как правило, их полностью обеспечивает журнал транзакций. Отслеживается как физическая согласованность данных (согласованность хранилищ данных, которые контролируются только СУБД), так и логическая согласованность данных — на уровне транзакций. На журнал транзакций ложится не только функция контроля логической целостности данных, но и физической, так как физическое рассогласование данных практически всегда фатально для СУБД. При рестарте СУБД после сбоя результаты всех операций незавершенных транзакций аннулируются — происходит откат транзакций по журналу. Результаты всех операций зафиксированных транзакций переносятся в постоянное хранилище — транзакции докатываются по журналу.
Формальное определение транзакции
Упорядоченное множество операций над объектами данных называют транзакцией, если это множество операций удовлетворяет свойствам ACID (Atomicity — атомарность, Consistency — непротиворечивость, Isolation — изолированность, Durability — сохранность данных).
- Atomicity - транзакция рассматривается как единая логическая единица, все ее изменения или сохраняются целиком, или полностью откатываются.
- Consistency - транзакция переводит базу данных из одного непротиворечивого состояния (на момент старта транзакции) в другое непротиворечивое состояние (на момент завершения транзакции). Непротиворечивым считается состояние базы, когда выполняются все ограничения физической и логической целостности базы данных. Считается, что на момент старта базы данных она находится в корректном состоянии, допускается нарушение ограничений целостности в течение транзакции, но на момент завершения все ограничения целостности, как физические, так и логические, должны быть соблюдены.
- Isolation - поведение транзакции Т при параллельном выполнении с другими транзакциями должно быть таким, как если бы для последовательного выполнения Т было выделено отдельное время, в течение которого не выполнялись бы операции других транзакций.
- Durability - если операция фиксации транзакции прошла успешно (commit), то все ее изменения должны быть сохранены; если операция фиксации прошла неуспешно (rollback), то все ее изменения должны быть удалены. Эти условия должны соблюдаться вне зависимости от сбоев (отказа сервера, операционной системы, аппаратуры и т.п.).
Транзакцию, удовлетворяющую условиям ACID, называют еще плоской транзакцией (flat transaction). Плоской она называется в силу атомарности. Структура такой транзакции очень проста:
Начало транзакции -> Действие 1 -> ... Действие N -> Конец транзакции
Для такой транзакции нет никаких промежуточных точек фиксации работы, которую она выполняет между Началом транзакции и Концом транзакции.
Говорят, что все действия от момента начала транзакции до момента ее завершения выполняются в контексте данной транзакции.
Менеджер транзакций, журнал транзакций
Плоская транзакция воспринимается как неделимая логическая единица работы. Управлением обработкой транзакций занимается менеджер транзакций. В его компетенцию входят взаимодействия параллельных транзакций, разрешение возможных конфликтов транзакций, когда несколько транзакций пытаются обратиться к одним и тем же данным, контроль моментов старта и завершения транзакций. Физическую целостность данных и протоколирование операций, которые выполняются в контексте транзакций, обеспечивает журнал транзакций (подсистема сервера, где фиксируются все действия, произведенные каждой транзакцией). Он взаимодействует с менеджером транзакций, получая от него информацию о начале и завершении транзакций.
Как только приложение пользователя устанавливает соединение с сервером, менеджер транзакций этого сервера получает уведомление о новом соединении и инициализирует все параметры соединения, которые связаны с обработкой транзакций. В число таких параметров входит уровень изолированности транзакции. Это указание серверу, которое регулирует поведение данной транзакции по отношению к другим, параллельно с ней выполняющимся транзакциям, и поведение других транзакций, выполняющихся сервером, по отношению к данной. В этот же момент может ставиться временная метка, которая определяет время начала транзакции. Момент старта транзакции отмечается в журнале транзакций.
В некоторых реализациях момент старта транзакции совпадает с моментом приема сервером запроса на соединение. В этом случае разница времени T установки соединения и времени T1 приема первой операции через данное соединение игнорируется, так как в этот промежуток времени приложение не производило никаких действий с данными.
С момента начала транзакции все ее действия протоколируются в журнале. В случае чтения данных в журнале могут храниться версии считанных записей или страниц данных, на которых эти записи находились в момент чтения. Такой механизм обеспечивает, в частности, считывание в точности тех же данных при повторном выполнении запроса. Следует отметить, что подобное поведение сервера при обработке операций чтения наблюдается в тех реализациях, в которых для обеспечения изолированности транзакций используется многоверсионность страниц данных или записей.
В случае изменения записи в журнале сохраняются две версии этой записи: старая — значение записи до модификации, и новая — модифицированное значение записи. Например, при изменении значения записи v с (1,1) на (2,2) в журнале транзакций будут сохранены старое значение (1,1) и новое (2,2). В случае удаления записи в журнале транзакций сохраняется текущее значение записи и пометка о том, что запись удалена. В случае вставки записи в журнале сохраняется новое значение записи и пометка о том, что выполнялась операция вставки записи.
Кроме того, в журнале транзакций отражается создание временных таблиц, требуемых для обработки запросов с group by, having, distinct. Протоколируется создание и других временных объектов, например, локальных и глобальных временных таблиц (в случае, если работа с ними реализована в данной версии сервера), временных индексов и т.п.
Проверка корректности операций над данными, выполняемыми в контексте данной транзакции, осуществляется ядром СУБД. Если при выполнении запроса фиксируется ошибка, то все действия над данными, связанные с выполнением данного запроса, также откатываются по журналу транзакций. Тем самым журнал транзакций обеспечивает и атомарность каждого запроса.
Протоколирование действий транзакции ведется до тех пор, пока сервер не получит информации о завершении транзакции от пользовательского приложения (операции commit/rollback) или от менеджера коммуникаций или сетевого компонента в результате:
- обработки операции закрытия соединения с сервером приложением пользователя;
- обнаружения факта сбоя приложения.
В этих случаях выполняются операции завершения транзакции.
При выполнении операции фиксации транзакции, во-первых, происходит проверка корректности действий транзакции, включая обработку конфликтов транзакций (если только она не ведется постоянно в течение всей жизни транзакции). Если фаза проверки корректности транзакции пройдена успешно, то на второй фазе фиксации данные из журнала транзакций переносятся в постоянное хранилище. После успешного прохождения обеих фаз, приложению пользователя посылается сообщение-ответ об успешном завершении транзакции. В журнале же освобождаются те объекты, которые обслуживали обработку только что зафиксированной транзакции. В дальнейшем эти объекты могут быть использованы для обработки других транзакций. Как правило, журналы имеют страничную структуру. Это означает, что после фиксации транзакции страницы, которые были заняты в процессе ее обработки, просто метятся как свободные. Следует также отметить, что операции переноса данных из журнала в постоянное хранилище могут быть асинхронными.
Если выполняется операция отката транзакции, то фаза проверки корректности транзакции опускается. Происходит уничтожение всех временных объектов, созданных транзакцией, и восстановление всех данных, которые модифицировала транзакция, — откат всех модификаций. Данные приводятся к тому состоянию, в котором они находились на момент начала транзакции.
Если модификации транзакции отражаются только в журнале, и транзакция не производит никаких модификаций постоянного хранилища, то откат всех модификаций может выглядеть как простое освобождение страниц журнала. Если же реализация сервера такова, что в процессе выполнения транзакции какие-то ее операции модификации данных отражаются на состоянии постоянного хранилища данных, то происходит замена новых значений данных в постоянном хранилище на старые значения согласно журналу (это возможно, так как в журнале хранятся и старые, и новые значения).
Журнал обеспечивает не только обработку транзакций, но и восстановление системы в случае сбоя. Как известно, для ускорения обработки данных сервер базы данных может сильно кэшировать данные постоянного хранилища. Для журнала это запрещено, хотя имеются реализации с небольшим кэшем журнала в несколько страниц и частым его выталкиванием во внешнюю память. Однако, как правило, журналы не кэшируются, что позволяет в случае отказа сервера восстановить по журналу корректное состояние данных.
Предположим, что произошел отказ сервера. В момент отказа происходила работа приложений пользователей. Их транзакции оказались не завершены, некоторые транзакции могли быть завершены, но данные в постоянное хранилище еще не попали. При рестарте сервера доступна только информация журнала, и только по нему может быть восстановлено корректное состояние данных — незавершенные транзакции откатываются, данные завершенных транзакций переносятся в постоянное хранилище — эти транзакции докатываются.
Продолжение в следующем выпуске журнала «Открытые системы».