Rambler's Top100

(c)2009-2017 openinfotech.ru

СУБД HyTech

Документация
Транзакции

Поддержка транзакций

Общие принципы функционирования механизма транзакций

Для обеспечения логической непротиворечивости данных в процессе модификации таблиц, а также для повышения устойчивости к аппаратным сбоям, в СУБД HyTech поддерживается механизм транзакций.

Проиллюстрируем работу этого механизма на примере. Например, пусть необходимо перенести некоторую сумму с одного счета на другой. Очевидно, что операция должна состоять из следующих шагов:

  • снять сумму с одного счета (модификация записи 1);
  • добавить сумму на другой счет (модификация записи 2).

Заметим, что после выполнения первого шага таблица будет находится в противоречивом состоянии (нарушен баланс счетов на величину, снятую с первого счета). При параллельной работе нескольких пользователей это является ошибкой (другой пользователь видит таблицу в неверном состоянии). Кроме того, второй шаг может завершиться с ошибкой (например, из-за захвата записи 2 другими пользователями). Аппаратный сбой в момент между выполнением первого и второго шага, вообще приведет к утере контроля над целостностью таблицы, так как задача, "знающая" о снятии суммы с первого счета, завершилась аварийно. Попытка решить задачу с использованием блокировки всей таблицы на момент выполнения операции замедлит работу всех пользователей сети и не сможет обеспечить восстановление после сбоев. Для разрешения подобных противоречий и предназначен механизм транзакций. В данном случае перед выполнением операции СУБД уведомляется о начале транзакции на таблице счетов. СУБД возвращает задаче идентификатор начавшейся транзакции. После чего задача выполняет необходимые действия (шаги 1 и 2). В процессе их выполнения другие пользователи сети "видят" таблицу в первоначальном состоянии (как до выполнения шага 1). Пользователи не ограничены в операциях поиска над таблицами, задействованными в активной транзакции. Операции редактирования для таких таблиц "ждут" завершения транзакции. При удачном завершении операций модификации обоих записей, следует выполнить команду завершения транзакции. После этого все произведенные изменения станут "видимыми" для всех пользователей сети, а таблица(ы) станут доступными для редактирования. В случае невозможности выполнения очередного шага (например, из-за недоступности ресурса) транзакция должна быть завершена аварийно. В результате аварийного завершения все выполненные с момента начала транзакции изменения уничтожаются. Задействованные таблицы возвращаются в первоначальные состояния ("откатываются"). Аналогичный откат происходит и после аппаратного сбоя. Первое же обращение любого пользователя сети к таблице вызовет выполнение процедуры "отката".

Для предотвращения "подвисов" установлен контроль за временем выполнения транзакций. При превышении допустимого предела, транзакция помечается как подлежащая аварийному завершению вне зависимости от результатов выполнения промежуточных шагов.

Пользователь, начавший транзакцию, не сможет продолжать операций с таблицей (кроме операций освобождения от захватов записей, результатов или таблиц) и должен прервать выполнение транзакции, выдачей команды аварийного завершения транзакции. Другие пользователи сети "видят" таблицы транзакции в первоначальном состоянии и могут свободно ими распоряжаться (в том числе использовать их в транзакциях).

Для предотвращения "клинчей" при доступе к ресурсам используются следующие правила:

  • выполняется захват всех требуемых ресурсов сразу;
  • первая же неудача приводит к отказу от всей операции.

Описанный механизм транзакций распространяется и на несколько таблиц. Задача может начать выполнение нескольких транзакций одновременно. Одна и та же таблица может быть задействована только в одной транзакции одновременно.

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

Включение механизма транзакции

Для функционирования механизма отслеживания транзакций необходимо, чтобы каждая станция в сети имела уникальное имя и был задан путь на существующий каталог, в котором будет размещен журнал транзакций.

Включение системы отслеживания транзакций производится установкой флага TRS_ON в члене структуры INIT.nTrsMode при инициализации СУБД. Кроме флага "включения" в этот член структуры при помощи операций | (логического ИЛИ) можно добавить битовые флаги управления памятью (TRS_PREP) и режимами записи в журнал (TRS_FLUSH).

ВНИМАНИЕ!!. Для поддержания целостности и непротиворечивости данных следует обеспечить единство в методах модификации таблиц с разных станций сети. То есть, если механизм транзакций включен хотя бы на одной станции сети, он должен быть включен на всех прочих станциях. И наоборот, если не предполагается использование транзакций на данной станции, они также не должны использоваться и другими станциями (здесь имеется в виду только факт "включения" механизма транзакций, а не использование самих функций запуска и завершения транзакций). Это требование определяется порядком доступа к таблицам для модификации:

  • сначала выполняется захват журнала транзакций (если таковой доступен);
  • затем выполняется захват заголовка модифицируемой таблицы.

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

ВНИМАНИЕ!. Для правильного функционирования механизма транзакций в сетевой среде должно быть обеспечено единое время (то есть время, установленное в таймере каждой станции должно совпадать друг с другом). Чем больше расхождение времен на станцияих, тем больше погрешность при завершении транзакций по времени. Если, например, расхождение времен на станциях составляет 5 сек, то тайм-аут для транзакций должен быть установлен более 5 сек, иначе транзакция, не успев начаться, может быть завершена другой станцией.

При выполнении операций управления транзакциями требуется некоторое количество памяти (около 11 Кб). Предусматривается две стратегии управления памятью:

  • память выделяется один раз при инициализации СУБД и освобождается при завершении ее работы;
  • память выделяется каждый раз при выполнении функций и освобождается при выходе из них.

Очевидно, что первый вариант более надежен, так как память будет иметься всегда (иначе СУБД не стартует), но уменьшает размер свободной памяти для общего пользования. Второй вариант более экономен в плане использования памяти и установлен по умолчанию. Для выбора первого варианта стратегии выделения памяти следует добавить в член структуры INIT.nTrsMode флаг TRS_PREP.

При выполнении операций управления транзакциями производится запись данных в журнал транзакций. Для этих целей используются функции работы с файлами ДОС. Операция записи в файл не обязательно немедленно переносит данные на диск. Они могут какое-то время храниться во внутренних буферах операционной системы. Сбой по питанию в этот момент может привести к потере данных (они так и не попадут на диск).

С другой стороны постоянное "выталкивание" данных из буферов на диск замедляет работу СУБД.

В надежных системах, оборудованных специальными устройствами питания вероятность потери данных из-за сбоя по питанию чрезвычайно мала. По умолчанию задается более быстрый режим - без сброса буферов.

Для задания режима записи в журнал транзакции с немедленным сбросом данных на диск следует добавить в член структуры INIT.nTrsMode флаг TRS_FLUSH.

Заготовлены два варианта комбинаций флагов режима:

  • TRS_EASY - без предварительного выделения памяти и без немедленного сброса данных на диск
  • TRS_HARD - с предварительным выделением памяти и сбросом буферов на диск.

Примечание. Возможна работа механизма транзакций и на одной машине. Для этого следует добавить в член структуры INIT.nTrsMode флаг TRS_ALONE. При этом режим открытия журнала транзакций будет мопопольным.

Пример.

Выполнить перенос денег со счета на счет.

 typedef struct tagCOUNT { /* Структура записи таблицы счетов */
    char name[15];
    unsigned count;
 } COUNT;
 COUNT rec1, rec2;
 THANDLE hTable;
 TRID hRoll;
 INIT sInit;
 . . .
 memset( &sInit, sizeof(sInit), 0);
 sInit.fpTmpPath = "c:\\tmp";
 sInit.fpNetPath = "f:\\net";
 sInit.fpNetName = "user1";
 sInit.fpTrtPath = "g:\\roll";
 /* Режим отработки транзакций */
 sInit.nTrsMode = TRS_ON | TRS_FLUSH;
 sInit.nTransTimeOut = 20;
 /* Проверка кодов завершения опущена для простоты. */
 htInit( &sInit, sizeof(sInit) );
 htTableOpen( "count", NULL, NULL, TAB_SHARE, &hTable );
 . . .
 if( htHoldReadRecord( hTable, 1L, &rec1 ) == 0 ) {
 if( htHoldReadRecord( hTable, 2L, &rec2 ) < 0 )
	 htReleaseRecord( hTable, 1L );
 else { /* Перенос денег */
 /* Начать выполнение транзакции */
	 htStartTransaction( &hTable, 1, &hRoll );
	 rec1.count -= 1000;
	 rec2.count += 1000;
	 if( htRecordModify( hTable, 1L, &rec1 ) < 0 ) {
	 htReleaseRecord( hTable, 1L );
	 htReleaseRecord( hTable, 2L );
	 htRollBackTransaction( &hRoll );
	 }
	 else {
	 if( htRecordModify( hTable, 2L, &rec2 ) < 0 ) {
	 htReleaseRecord( hTable, 1L );
	 htReleaseRecord( hTable, 2L );
	 htRollBackTransaction( &hRoll );
	 }
	 else { /* Удачное завершение */
	 htReleaseRecord( hTable, 1L );
	 htReleaseRecord( hTable, 2L );
	 if( htCommitTransaction( &hRoll ) < 0 )
	 htRollBackTransaction( &hRoll );
	 }
	 }
 }
 }
 . . .