Модели и смыслы данных в Cache и Oracle

Транзакции в базах данных

Разбить на страницы
Показывать лекцию целиком

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

Изучим свойства транзакций и языковые средства для их оформления.

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

Дадим классификацию ограничений целостности.

Введем понятие ссылочного ограничения целостности и покажем как оно поддерживается с помощью триггера.

Опишем феномены — проблемы, возникающие при параллельной работе—и на их основе определим уровни изолированности пользователей.

Изучим проблемы блокирования ресурсов и возникновения тупиков. Бегло рассмотрим многоверсионные данные.

В заключительных разделах рассмотрим роль транзакций в организации откатов и восстановлении данных после сбоев.

6.1 Зачем нужны транзакции? Что мы ждем от них? Свойства транзакций

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

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

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

Транзакции обеспечивают:

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

    Пусть при выполнении двух последовательно выполняющихся действий после успешного выполнения первого из них возник сбой (рисунок 6.1).

    (рис 6.1) Что может произойти при сбое сервера

    В приведенном примере это означает, что сумма, снятая с первого счета не попала на второй счет. Хуже того, сам факт исчезновения денег никак не зафиксирован. Если вас этот неприятный факт не расстроил, и вы считаете, что можно повторить все с начала, то тут сказывается одна нехорошая привычка, вырабатываемая ориентированностью наших студентов, фактически, на научные вычисления. Скажем, складывал я две матрицы, а они у меня не сложились. Ну, бог с ними — я их сложу еще раз. Но, если вы написали систему, которая отправила миллиард долларов, и он никуда не пришел, то, скорее всего, усовершенствовать ее будет уже другой человек, а вы будете искать новую работу. Вот, собственно, в этом разница в отношении к надежности вычислений в науке и бизнесе. Вообще говоря, бизнес-приложения должны работать всегда, независимо от того, исправен ли компьютер или нет, заболели ли вы или еще что-то случилось, ночь сейчас или день. Ведь есть организации, работающие круглые сутки. Хорошим тоном считается обеспечивать работоспособность в самом тяжелом режиме, который принято называть "24*7" (двадцать четыре на семь). Это означает 24 часа в сутки 7 дней в неделю. Это очень тяжелый режим, и без специальных мер он сам по себе не обеспечивается. Представьте, что в вашем домашнем компьютере нужно поменять или добавить жесткий диск. Как это сделать без останова системы?

    (рис 6.2) Возможные ошибки

    Для рассмотрения параллельной работы двух пользователей вспомним, что "сальдо" —это остаток на счете. Посчитать сальдо по группе счетов — это, попросту говоря, сложить их остатки. Обычный арифметический цикл, который известен всем школьникам. На рисунке 6.2 слева показана работа такого цикла. Сначала сальдо С по группе счетов равно 0, потом прибавляем к нему остаток первого счета, второго счета и т.д., счета тысячного. Как учили в школе? Написали цикл, проверили его на примере. Дальше работает всегда правильно. Но у нас то пользователь не один. Транзакция изображенная слева выполняет циклическое суммирование. Но после того, как счет 1 уже учтен, и до того, как учтен счет 1000, вторая транзакция, изображенная справа, снимает 200 руб. со счета 1, и зачисляет их (то есть прибавляет 200 руб.) на счет 1000. Теперь в подсчете сальдо по группе счетов появится ошибка в 200р. Почему? Потому что счет 1 мы обрабатывали в неизмененном состоянии, а счет 1000 в измененном. А что будет, если во времени вторая транзакция будет сдвигаться? Ну, очевидно, если она будет выполнена до первой (левой на рисунке) транзакции или после нее, то ничего страшного не случится, все будет посчитано правильно. А если транзакции начнут выполняться на одном промежутке времени то, с какого-то момента появится ошибка. Обратите внимание, довольно странная, неожиданная для начинающих ситуация — арифметический цикл, оказывается, может считать данные неправильно, если одновременно идет некоторый другой процесс, имеющий доступ к тем же данным. Транзактный механизм должен подобные казусы исключить.

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

    (рис 6.3) Ошибка при подсчете сальдо при параллельной работе без транзакций

    Работа с тысячью счетов в примере может затянуться, поэтому счетов у нас всего три штучки (Сч1, Сч2, Сч3). На них 500 руб., 700 руб. и 800 руб. соответственно. Сальдо С по группе этих трех счетов в начале вычисления равно нулю. Переменная t — это разметочная переменная, отмечающая моменты времени 0, 1, 2, 3, 4. Она определяет порядок действий. Сначала вы должны выполнить ту строчку в левом терминале, где есть присваивание t = 0. После него выполняете первую строчку второго терминала там, где t присваивается значение 1, и т.д. Если вам трудно следить за текстом, повторите все сами. Как и ожидалось, сальдо вычисляется с ошибкой.

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

    Вернемся к рассмотренной ранее операции — переводу денег с одного счета на другой. Эта операция включает две атомарные (элементарные, неделимые) операции:

  • снятие суммы с одного счета,
  • зачисление суммы на другой счет.
  • Если СУБД будет выполнять (или не выполнять) эти две операции только вместе, то база будет всегда находиться в согласованном состоянии и не будут возникать ситуации, при которых суммы, снятые с одного счета, ни на какой другой счет не поступают.

    Определение. Объединение нескольких операций в неделимое целое называют транзакцией.

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

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

    6.1.1 Определение транзакции

    Транзакция — это последовательность действий, выполняющаяся как единое целое и переводящая базу данных из одного согласованного состояния в другое согласованное состояние. Вы должны помнить, что ограничения целостности определенные в бизнесе, могут быть не прописаны явно. Так, в примере, приведенном на рисунке 6.1, ограничение целостности можно сформулировать так: деньги, снятые со счета, должны быть зачислены на другой счет. Может быть задействована некоторая цепочка счетов. Например, если вы отправляете деньги через свой банк клиенту другого банка, то деньги сначала поступают на ваш клиентский счет, с него — на корреспондентский счет вашего банка в другом банке, а уже оттуда на клиентский счет получателя. Всегда необходимо додумывать ситуацию до конца, тогда она будет прозрачна, понятна, и вы с ней справитесь. В транзакциях используют следующие инструкции:

  • чтения данных;
  • манипулирования данными;
  • блокирования и разблокирования ресурсов.
  • Транзакции обладают набором свойств (рисунок 6.4), характеризуемым аббревиатурой АСИД (ACID):

  • А - Атомарность. Транзакция выполняется как единое целое (либо все
  • выполняется, либо все не выполняется).
  • С - Согласованность. Транзакция переводит базу данных из одного согласованного состояния в другое согласованное состояние. Внутри транзакции согласованность базы данных может нарушаться.
  • И - Изолированность. Транзакции разных пользователей не должны мешать друг другу. Иначе говоря, транзакция должна работать только с непротиворечивыми данными, не имея доступа к промежуточным результатам.
  • Д - Долговечность. Результаты работы выполненной транзакции должны сохраниться в базе данных, даже если по завершении транзакции произойдет сбой системы.
  • (рис 6.4) Свойства транзакции

    Замечание. Соответствующая английская аббревиатура ACID образована первыми буквами терминов Atomicity, Consistency, Isolation, Durability.

    6.1.2 Замечание о свойствах АСИД

    Свойства АСИД транзакций, за исключением атомарности, не всегда выполняются в полном объеме.

  • Согласованность. Нарушается внутри транзакции. При неполной изоляции транзакций несогласованные данные могут быть доступны другим транзакциям.
  • Изоляция. Полной изоляции транзакций можно достигнуть, только если принудительно выстраивать транзакции в очередь и исполнять их строго одну после другой. При этом достаточно одной из транзакций зависнуть или выполняться слишком долго (сначала я пила кофе, потом меня вызвал начальник, а потом был перерыв), чтобы остановить всю работу. Поэтому вводится несколько уровней неполной изоляции.
  • Долговечность. Может нарушаться если, например, транзакция $$T_2$$, вызванная из транзакции $$T_1$$ будет завершена успешно, a $$T_1$$ откатится. Тогда откатятся данные транзакции $$T_2$$
  • 6.2 Языки управления транзакциями

    Будем рассматривать так называемые плоские транзакции, обладающие одним управляющим слоем. Именно этот тип широко распространен в современных СУБД.

    Существует два способа запуска транзакции:

  • Транзакция начинается автоматически с момента присоединения пользователя к СУБД или по завершении предыдущей транзакции (например, в Oracle).
  • Транзакция начинается специальной командой, например, BEGIN TRANSACTION или другой командой аналогичного назначения (например, в Cache).
  • Транзакция завершается, успешно или не успешно, специальными командами или по одному из событий:

  • Команда COMMIT [WORK] (зафиксировать транзакцию).
  • Команда ROLLBACK [WORK] (откатить транзакцию).
  • Событие "Отключение пользователя от СУБД".
  • Событие "Сбой системы".
  • Событие "Отключение системы".
  • В языке COS начало транзакции — команда TSTART, успешное завершение — TCOMMIT, неуспешное — TROLLBACK.

    В таблице 6.1 приведены команды управления данными для Cache.

    Команды управления данными для Cache
    Команда SQL Команда COS Описание
    %BEGTRANS TSTART Начало транзакции
    COMMIT [WORK] TCOMMIT Успешное завершение транзакции
    ROLLBACK [WORK] TROLLBACK Неуспешное завершение транзакции

    Замечание. В Oracle любые действия вкладываются в транзакции, в Cache транзакции используются по желанию пользователя. Пользователь Cache, работающий вне транзакции как бы "не замечает",

    чужих транзакций.

    Создадим и откомпилируем программу ^tr1 содержащую транзакцию:

    SET ^tt(0)=0, ^tt(1)=1 ;значения до транзакции
    TSTART ;начало транзакции
    SET ^tt(0)= "Значение 1 из транзакции"
    READ x ;приостанавливает исполнение
    SET ^tt(1)= "Значение 2 из транзакции"
    TCOMMIT /* конец транзакции - успешное завершение */
    WRITE "^tt(0)= ",^tt(0),!, "^tt(1)= ", ^tt(1)
    QUIT
    

    Вы помните, что в Cache данные бестиповые, тип данных определяется по контексту. Например запись -x приведет к интерпретации x как числа. В программе ^tr1 назначаем глобалам ^tt(0) и ^tt(1) цифровые значения. А в транзакции присваиваем им текстовые значения. READ x используется для остановки исполнения программы за счет ожидания ввода данных человеком. В конце печатается текст ^tt(0) =.. ^tt(1) =.. и сами значения этих глобалов. Если транзакция откатывалась, то глобалам ^tt(0) и ^tt(1) возвращаются цифровые значения.

    Запустим два терминала.

    Просмотрим два варианта исполнения транзакции:

  • С вводом пустого значения в переменную x (левый терминал) — см. рисунок ниже. Сначала в левом терминале исполняется программа ^tr1 , затем в правом читается значение глобала. Транзакция завершилась успешно, поэтому возвращены текстовые значения.
  • С выключением левого терминала (для выключения нажмите на кнопку с крестиком -см. рисунок ниже. Правый терминал читает ^tt(0) первый раз до выключения левого терминала, а второй раз — после его выключения. Вывод: выключение терминала откатывает транзакцию.
  • 6.3 Транзакции и ограничения целостности

    Переходим к первой задаче, которую решают транзакции — соблюдение ограничений целостности. Что нужно для проверки любого ограничения? Прежде всего, база должна быть активной, то есть иметь способность совершать что-то, сверх того, о чем ее попросили. Пусть задано ограничение первичный ключ. Когда мы приказываем ввести строку, СУБД сама обнаруживает, что такое ограничение имеется и запускает процедуру, проверяющую, не повторяются ли значения в ключевых столбцах. И, наверное, она не позволит ввести строку с повторяющимся значением ключа. Хорошо бы еще получить какое-то сообщение о возможном нарушении ограничения целостности.

    Чаще всего источник активности — специальные процедуры, называемые триггерами. Но это не единственный вариант. Триггеры напоминают резидентные программы. Они начинают работать только при запуске некоторого процесса, называемого триггерным событием. В число таких событий всегда входят вставка, удаление и обновление строк (по-английски insert, update и delete). В современных базах данных триггеры могут реагировать и на другие события. Поскольку ограничения целостности определены для хранимых объектов базы, триггеры прикрепляются к этим объектам.

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

    6.3.1 Нарушения целостности

    Определение. База данных находится в согласованном (целостном) состоянии, если выполнены все заданные в ней ограничения целостности.

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

    Система управления базами данных обязана реагировать на любые попытки нарушения целостности.

    Два основных типа реакции СУБД:

  • Отказ выполнить "недопустимую" операцию.
  • Выполнение действий, компенсирующих нарушения ограничений целостности. Далее мы поясним этот тип на примере.
  • Классификация ограничений целостности проводится по следующим основаниям:

  • По способам реализации.
  • По времени проверки.
  • По области действия.
  • 6.3.2 Классификация по способам реализации

    Различают

  • декларативную поддержку ограничений целостности;
  • процедурную поддержку ограничений целостности.
  • Декларативная поддержка ограничений целостности задается средствами языка определения данных (ЯОД, он же DDL — Data Definition Language).

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

    CREATE TABLE person (persid INTEGER PRIMARY KEY, persname CHAR(30) NOT NULL);

    Если прочитать ее на русском языке, все станет понятно. Итак: "СОЗДАТЬ ТАБЛИЦУ с названием person, содержащую два столбца: persid типа ЦЕЛОЕ с ограничением ПЕРВИЧНЫЙ КЛЮЧ и persname типа СИМВОЛЬНОЕ с длиной 30 символов и ограничением НЕЛЬЗЯ НЕ ЗАПОЛНЯТЬ".

    Процедурная поддержка ограничений целостности

    Процедурные ограничения целостности реализуются использованием триггеров и хранимых процедур.

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

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

    Триггеры

    Триггеры — это процедуры специального вида, прикрепленные к объекту базы, например, таблице, и срабатывающие при наступлении событий из некоторого набора событий. Наиболее известны триггеры, реализуемые в языке SQL.

    В набор событий обычно входят вставка записи (инструкция INSERT в SQL), удаление записи (инструкция DELETE) и обновление записи (инструкция UPDATE).

    Триггер может выполняться до отработки события (триггер BEFORE) и после его отработки (триггер AFTER).

    Выделяют также триггеры уровня строки (записи), срабатывающие один раз на каждую выбранную строку, и триггеры уровня выражения, срабатывающие один раз при обработке набора записей (например, при входе в таблицу или выходе из нее). Итого 3 * 2 * 2 = 12 типов триггеров на один набор записей (таблицу).

    Для проверки ограничения на возраст принимаемого на работу создают триггер на команду INSERT, срабатывающий до ее выполнения (нельзя допускать запись о неправильном приеме — значит выбираемый тип — BEFORE). В теле триггера вычисляют возраст, проверяют условие $$21\le возраст \le 45$$. Если условие не выполнено, запись о приеме аннулируется (рисунок 6.5).

    (рис 6.5)

    Ссылочные ограничения целостности

    Рассмотрим пример ссылочного ограничения целостности.

    Пусть задана схема, состоящая из двух таблиц dept (отделы) и person (сотрудники) (таблицы 6.2 и 6.3).

    Столбцы таблицы dept (deptid, deptname, deptquan): deptid — идентификатор подразделения, deptname —имя подразделения, deptquan — количество сотрудников в подразделении.

    Столбцы таблицы person(persid, persname,deptid): persid — идентификатор сотрудника, persname —имя сотрудника, deptid —идентификатор подразделения, в котором работает сотрудник.

    Содержимое таблицы dept
    DEPTID DEPTNAME DEPTQUAN
    1 Цех разлива 3
    2 Транспортный цех 2
    Содержимое таблицы person
    PERSID PERSNAME DEPTID
    1 Иванов 1
    2 Петров 2
    3 Сидоров 1
    4 Злобис 2
    5 Вредис 1

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

    Если уволим Иванова, недостаточно просто его вычеркнуть, потому что при этом изменится численность первого отдела. Ее нужно пересчитать, вычтя единицу из значения в столбце deptquan. А если переведем Иванова в транспортный цех, то необходимо не только изменить строку с Ивановым в таблице dept, но еще и прибавить к численности транспортного цеха единицу, вычтя единицу из численности цеха розлива. Иначе таблица "Отделы" окажется неправильно заполненной.

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

    Например, прием нового сотрудника не может быть выполнен одной операцией. Необходимо вставить запись в таблицу person и одновременно увеличить значение поля deptquan на 1. Выполняем два шага, которые, конечно же, необходимо включить в транзакцию:

  • Вставить запись о сотруднике в таблицу person

    INSERT INTO person VALUES (6, 'Петросян', 1).

    (ВСТАВИТЬ в person строку (6, "Петросян", 1).)

  • Увеличить значение поля deptquan

    UPDATE dept
    SET depquan=deptquan+l WHERE deptid=l. 
    

    (ОБНОВИТЬ dept, УСТАНОВИВ ЗНАЧЕНИЕ deptquan = deptquan+1 ДЛЯ СТРОКИ В КОТОРОЙ deptid= 1). Для увольнения и перемещения сотрудников необходимы свои транзакции.

  • Замечание. Пока мы не изучили инструкции SELECT, INSERT и UPDATE языка SQL, воспринимайте их просто как фразы, написанные на фрагменте естественного (как бы английского) языка, который понимает СУБД.

    Примеры ограничений целостности

    Пример 1. Возраст сотрудника не может быть меньше 21 и больше 45 лет (Декларативное ограничение типа check).

    Пример 2. Атрибут "Табельный номер" уникален. (Декларативное ограничение—уникальный или первичный ключ).

    Пример 3. Сотрудник обязан числиться в одном из отделов. (Декларативное ссылочное ограничение целостности).

    Пример 4. Сумма накладной равняется сумме произведений цен товаров на количество товаров для всех товаров, входящих в накладную. (Определение вычислимого столбца.).

    Замечание. Ограничение целостности может определять не только значения атрибутов, но и особенности удаления или обновления данных. Например, в схеме "Отдел" — "Сотрудник" удаление отдела может вызвать удаление его сотрудников или их перевод в другие отделы.

    Задание ограничения целостности приходит в базу из бизнес-модели. Для проверки правильности этого перехода полезно определить, какие требования предлагает бизнесу это ограничение, то есть выполнить обратный переход. Например, задана уникальность табельного номера. Что это означает для бизнеса? Какой номер присваивается работнику при повторном приеме? Существуют ли сотрудники, не имеющие номера?

    6.3.3 Классификация ограничений целостности по времени проверки

    По времени проверки выделяют два вида ограничений:

  • Немедленно проверяемые ограничения. Проверяются непосредственно в момент выполнения операции, могущей нарушить ограничение.
  • Ограничения с отложенной проверкой. Проверяются в момент фиксации транзакции оператором COMMIT.
  • Пример немедленно проверямого ограничения — первичный ключ. Пример ограничения с отложенной проверкой — ссылочные ограничения (таблицы 6.2 и 6.3).

    6.3.4 Классификация ограничений целостности по области действия

  • Ограничения домена (из-за отсутствия в СУБД поддержки доменов такие ограничения обычно переносятся на атрибуты).
  • Ограничения атрибута (немедленно проверяемые ограничения на допустимые значения атрибута). Реализуются почти всегда декларативно. Проверяют обычно попадание в диапазон или в список. Проверки на соответствие шаблону, например, в записи телефонного номера, в настоящее время реализуются с помощью регулярных выражений, то есть вне основной модели данных.
  • Ограничения кортежа. Это ограничения на соотношение атрибутов одного экземпляра сущности.
  • Ограничения отношения (или сущности или таблицы). Для проверки ограничения необходимо обработать все кортежи отношения. Пример: Только у двух человек в организации заработная плата может быть больше 100000.
  • Ограничения на допустимые связи.
  • Ограничения базы или схемы данных (межтабличные). Накладываются на свойства двух или более связанных между собой отношений. Пример: ссылочная целостность.
  • Итоговая классификация ограничений целостности приведена в разделе 6.6.

    Ограничения на допустимые связи

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

    В типичном случае имеется несколько взаимно исключающих связей. Например, сущность A может быть связана бинарной связью с B или C, но если существует связь A с B, то не может существовать связь A с C, и наоборот.

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

    Заметим, что реализация рассматриваемого класса ограничений достаточно сложна.

    Одна из возможных связей — наследование. Реализация ограничений на наследование будет рассмотрена позже при изучении объектных моделей.

    6.4 Транзакции и параллельная работа

    Любая система в бизнесе представляет множество действующих лиц — акторов — работающих одновременно и организованно. Актором может быть человек, программа или машина. Информационная система должна как-то отражать эту многоакторность. Конечно, в ней самой могут появиться параллельные процессы, не имеющие прямых аналогов в бизнесе.

    В этом разделе будут рассмотрены проблемы, возникающие при параллельной работе транзакций. В соответствии с традицией будем называть эти проблемы феноменами. На основе феноменов зададим уровни изолированности транзакций. В определениях изолированности будет использован стандарт версии SQL-92 языка SQL, но знание SQL и, тем более, стандарта для освоения материала не требуется.

    В заключительной части раздела рассмотрим блокировки ресурсов и эффекты, возникающие при блокировании.

    6.4.1 Феномены

    Феномен "Потерянные изменения"

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

    Пример (таблицы 6.4 и (6.5): На счет Acc с начальным сальдо 100, зачисляют первой транзакцией 20 руб., а второй 100 руб. Если первая транзакция записывает измененные данные в базу и завершается после того, как вторая транзакция их прочла, но до того, как она их записала, то изменения, внесенные первой транзакцией, теряются.

    Пример феномена Феномен "Потерянные изменения". Транзакции
    Транзакция 1 Время Транзакция 2
    Начало Тр1
    Чтение X=Acc to
    Начало Тр2
    ti Чтение X=Acc
    t2 Зачисление X=X+100
    Зачисление X=X+20 t3
    Запись в базу Acc=X t4
    COMMIT
    Конец Тр1
    t5 Запись в базу Acc=X COMMIT
    Конец Тр2
    Пример феномена "Потерянные изменения".
      USER>TStart 
      USER>Set Х=^Асс, t=Q 
      USER>Set Х=Х+20,   t = 3 
      USER>Set ^Асс=Х,  t = 4 
      USER>TCommit 
      USER>
      USER>TStart
      USER>Set Х=^Асс, t=1
      USER>Set X=X+100, t=2
      USER>Set ^Acc=X,   t = 5
      U5ER>TComoit
      USER>Write ^Acc 2 00
      USER>|

    Начальное значение ^Acc=100. TSTART — начало транзакции, TCOMMIT — ее успешное завершение. Переменная t, как и раньше, использована вместо комментария для задания отметки очередности исполнения команд, соответствующей моментам времени $$t = 0, t = 1, \dots$$.

    Зависимость от незафиксированных результатов (чтение "грязных" данных)

    Феномен возникает, когда читаются данные незавершенной транзакции, которые впоследствии откатываются. Начальное значение Acc в примере равно 100 (таблицы 6.6 и 6.7).

    Пример феномена "Чтение грязных данных" . Транзакции
    Транзакция 1 Время Транзакция 2
    Начало Тр1
    Изменение Acc=Acc+20 t1
    Начало Тр2
    t2 Чтение и изменение
    X=Acc+50
    ROLLBACK —возврат к Acc=100
    Конец Тр1
    t4 Acc=X
    Конец Тр2. Значение Acc не 150, а 170
    Пример феномена "Чтение грязных данных"
      USER>TStart
      USER>Set ^Acc=^Acc+20, t=1
      USER>TRollback
      USER>Sett=3 Rollback Write ^Acc
      100
      USER>
      
      USER>TStart
      USER>S X=^Acc+50, t=2
      USER>Set ^Acc=X ,t=4
      USER>TCommit
      USER>Set t=5
      USER>Write ^Acc
      170

    Феномен "Неповторяющееся чтение"

    Разберите сами приведенный ниже пример (таблицы 6.8 и 6.9).

    Пример феномена "Неповторяющееся чтение" . Транзакции
    Транзакция 1 Время Транзакция 2
    Начало Тр2
    t1 Чтение Acc (Acc=100)
    Начало Тр1
    Изменение Acc=300 t2 Проверка Acc<200 — Да
    t3 Чтение Acc (Acc=300) Проверка Acc<200 — Нет
    Пример феномена "Неповторяющееся чтение"
      USER>TStart
      U5ER>Set t=2,  ^Acc=300
      USER>
      USER>TStart 
      USER<Set t=l
      USER>If ^Acc<200 Write "Да"Да
      USER>Set t=3U
      SER>If ^Acc<200 Write "Да"
      USER>|
      

    Феномен появления записей "фантомов"

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

    Пусть транзакция Тр1 выбирает группу строк из таблицы Т, удовлетворяющих условию У1. Затем транзакция Тр2 вставляет в Т еще одну строку, удовлетворяющую условию У1. При повторной выборке строк транзакцией Тр1, к полученному в предыдущей выборке набору добавляется еще одна строка, называемая фантомом, привидением.

    6.4.2 Уровни изолированности пользователей

    Стандарт SQL-92, основываясь на перечисленных феноменах, определяет четыре уровня изолированности:

  • Read uncommitted. Чтение незафиксированных изменений. Самый низкий уровень изоляции. Воспринимаются как окончательные, так и промежуточные результаты других транзакций. СУБД предотвращает только проблему потерянных изменений.
  • Read committed. Чтение зафиксированных изменений. Уровень изоляции выше, чем у read uncommited. Транзакция не имеет доступа к промежуточным результатам других транзакций. Но окончательные результаты других транзакций, завершившихся во время исполнения нашей транзакции, могут быть доступны. Возможно получение
  • разных результатов при повторе запроса до и после фиксации другой транзакции, изменяющей данные. Возможны фантомы.
  • Repeatable read. Повторяемое чтение. Уровень изоляции выше, чем у read commited. Транзакция не имеет доступа к промежуточным или окончательным результатам других транзакций. Вставки записей приводят к появлению фантомов.
  • Serializable. Сериализуемость. Самый высокий уровень изоляции. Результат параллельного выполнения транзакций будет точно таким же как при последовательном их выполнении. Все перечисленные выше феномены отсутствуют, но параллельное выполнение транзакций, работающих с одними ресурсами невозможно.
  • Данные по уровням изолированности сведены в таблицу 6.10. Знак "+" в ней означает наличие феномена, а знак "-" — его отсутствие.

    Уровни изолированности и отсутствие феноменов
    Уровень Потерянные "Грязное" Неповторяющееся Фантомы
    изоляции изменения чтение чтение
    Serializable + + + +
    Repeatable read + + + -
    Read commited + + - -
    Read uncommited + - - -

    Так зачем нужны "плохие" виды транзакций, и особенно, Read uncommited?

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

    Пример: Библиотекари регулярно работают с каталогом, открывая транзакции для сверки и изменения каталога. Эти работы могут длиться часами. Что Вы предпочитаете, ждать появления исправленного каталога, или просматривать его в любой момент времени, может быть, иногда получая неправильные данные?

    Замечание. Правильнее было бы дать библиотекарям работать с копией каталога, а когда они закончат работу быстро обновить каталог, запретив остальным пользователям доступ на несколько минут или секунд.

    Как обеспечить правильную работу транзакций, обращающихся к одним и тем же ресурсам?

    Два основных способа:

  • блокирование "общих" ресурсов;
  • предоставление транзакциям, конкурирующим за ресурсы, разных экземпляров данных.
  • 6.4.3 Блокирование

    По степени разделяемости блокируемых ресурсов различают:

  • Монопольные блокировки (eXclusive locks или X-locks), называемые еще блокировками записи. Они не разрешают другим транзакциям доступ к блокированному ресурсу.
  • Разделяемые блокировки (Shared locks или S-locks) иначе блокировки чтения. Разрешают совместный доступ к блокированному ресурсу.
  • Виды блокировок

    Блокировки различают еще по размерам блокируемого ресурса: поле записи, запись, отношение, страница (блок базы), группа отношений, вся база. Забегая вперед, отметим, что в современных СУБД используется два способа хранения таблиц — строчный и столбцовый. Чаще данные хранятся строками. В этом случае минимальный объем блокировки — строка. В столбцовых базах —поле. Выполняется свойство иерархичности. Если объект верхнего уровня обладает блокировкой, то такой же блокировкой обладает его подобъект нижнего уровня.

    Взаимодействие блокировок:

  • Имеется X-блокировка. Запросы на любые блокировки от других транзакций будут отменены.
  • Имеется S-блокировка. Запрос на X-блокировку отвергается. Запрос на S-блокировку принимается.
  • Рассмотрим доступ по чтению и записи. Возможный протокол доступа к данным по чтению и записи с блокировками определяется следующими правилами:

  • Перед чтением объекта базы транзакция должна наложить на него S-блокировку.
  • Перед записью объекта базы транзакция должна наложить на него X-блокировку. Если перед этим транзакция выполняла S-блокировку этого объекта, то она заменяется X-блокировкой.
  • Если объект уже заблокирован X-блокировкой другой транзакции, то любая другая блокировка отвергается и транзакция переводится в состояние ожидания до тех пор, пока мешающая блокировка не снимется.
  • Если объект уже заблокирован S-блокировкой другой транзакции, то другая S-блокировка принимается, а X-блокировка отвергается.
  • X-блокировки сохраняются до конца транзакции. S-блокировка снимается по завершении чтения.
  • Если транзакция B начинается позже транзакции A, то успешность блокирования объекта базы транзакцией B определяется следующей матрицей совместимости блокировок (таблица 6.11):

    Матрица совместимости блокировок
    Транзакция A наложила блокировку Транзакция B пытается наложить блокировку
    S-блокировку X-блокировку
    S-блокировку + - (конфликт R-W)
    X-блокировку - (конфликт W-R) - (конфликт W-W)
    Транзакция-читатель мешает транзакции-писателю

    Сделаем важный вывод: при использовании разграничения доступа с помощью S- и X-блокировок транзакции-читатели могут мешать транзакциям-писателям. Транзакции-писатели всегда мешают другим транзакциям-писателям и транзакциям-читателям.

    Как выполняются блокировки в транзакциях Cache ObjectScript? С помощью команды LOCK имеющей формат:

    L[OCK] [+|-] список_имен_блокируемых_ресурсов

    Пример: LOCK ^a(1) или, сокращенно, L ^а(1) блокирует узел ^а(1).

    Форматы команды LOCK
    Формат Действие
    LOCK Отмена всех блокировок
    LOCK список_имен Снимает все блокировки, затем блокирует по
    одному имени из списка
    LOCK +имя Инкрементальная блокировка - добавление
    блокировки к уже существующим блокировкам
    LOCK -имя Удаляет блокировку

    Команда LOCK ^а(1), ^b(3) эквивалентна LOCK ^а(1) LOCK +^b(3). Использование времени ожидания (для инкрементальных блокировок рекомендуется всегда): LOCK ^а(1):5

    Попытка блокировать прерывается, если прошло 5 секунд, а блокировка не завершилась. При этом устанавливается значение $TEST=0. Просмотреть текущие блокировки можно двумя способами:

  • Программой ^LOCKTAB, запускаемой из терминала. Пример:

    USER>LOCK ^a(0)
    USER>ZN "%SYS"
    %SYS>DO ^LOCKTAB
    
  • С помощью портала управления системой - рисунок 6.6

    (рис 6.6) Список блокировок в портале управления системой
  • Результат выполнения последней команды:

    Node Name: WS111
    LOCK table entries at 11:10PM 11/28/2012 
    1176464 bytes usable,  1178528 bytes available.
     
    Entry  Process  X#  S#  Flg  W#  Item Locked
    1)  1188  1  ^[  "^^c:\intersystems\trycache\mgr\"]%SYS("CSP","Daemon")
    2)  3400  1  ^[  "^^c:\intersystems\trycache\mgr\"]TASKMGR
    3)  2640  1  ^[  "^^c:\intersystems\trycache\mgr\user"]a
    Command=>
    
    

    Любая СУБД блокирует как минимум свой словарь, поэтому вместо одной установленной нами блокировки на ресурс ^а(0) мы видим еще и другие блокировки.

    Справку по ^LOCKTAB можно получить командой HELP, сокращенно H. Выход из программы осуществляет команда Q[UIT].

    Блокировки распространяются в дереве на всех потомков и предков.

    Пример:

    Создадим глобал S ^a(1)=1, ^a(1,1)=11, ^a(2)=2, ^a(2,1)=21

    Команда L ^a(1) блокирует не только ^а(1), но и ^а,^а(1,1). Обратите внимание на то, что блокируются и локалы и глобалы. При этом LOCK не проверяет существование узлов. Поэтому блокировка создается и для несуществующих узлов.

    Рекомендация: Для удобства тестирования желательно, чтобы количество инкрементальных блокировок узла совпадало с количеством инкрементальных деблокировок.

    Пример: Если выполнена команда L +(^a,^b,^c), а затем L ^b, то останется заблокированным только ^b.

    6.4.4 Тупики (Clinch, Deadlock)

    При использовании транзакций возникают побочные и очень неприятные явления. Это тупики. Возникают они, когда две или более транзакций пытаются блокировать монопольно одни и те же ресурсы. Не получая доступа к ресурсам, конфликтующие транзакции могут зависнуть на неопределенное время. В СУБД этого допускать нельзя. Нужно как-то разрешить конфликт. Например, через некоторое время после возникновения тупика конфликтующие транзакции откатывают, но затем их вновь запускают в случайно выбранные моменты времени, совсем как при разрешении конфликтов в сети Ethernet.

    В транзакции могут исполняться следующие виды операций:

  • БРi — блокирование ресурса i;
  • РРi — разблокирование ресурса i;
  • действие над ресурсом i. (Чтение или манипулирование данными).
  • Ситуация тупика возникает при попытке двух транзакций блокировать одни и те же ресурсы:

    (рис 6.7) Ситуация тупика

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

    Использование блокировок позволяет решать проблемы параллельного доступа. Пример использования блокировок для устранения чтения "грязных" данных — в таблице 6.13

    Использование блокировок для устранения чтения "грязных" данных
    Транзакция 1 Время Транзакция 2
    S-блокировка Acc успешна t1
    Чтение Acc t2
    t3 S-блокировка Acc успешна
    t4 Чтение Acc
    Попытка записи Acc (X- t5
    блокировка отвергается)
    Ожидание t6 Попытка записи Acc (X-
    блокировка отвергается)
    Ожидание t7 Ожидание

    X-блокировки отвергаются, так как уже установлены две S-блокировки. Обе транзакции ожидают завершения другой транзакции. Возникает тупик, зато феномен чтения "грязных" данных отсутствует. Если же S-блокировка транзакции 2 будет выполнена после успешной X-блокировки транзакции 1, то транзакция 2 будет ждать завершения транзакции 1.

    Многоверсионные данные

    Мы видели, что в СУБД с единственной версией данных транзакции-читатели могут мешать транзакциям-писателям (конфликты R-W).

    В многоверсионных СУБД транзакциям-читателям предоставляются свои версии данных, получаемые откатом части схемы базы до последнего согласованного состояния. Такие транзакции не блокируют данных и потому не мешают транзакциям-писателям.

    Например, многоверсионная СУБД Oracle создает и поддерживает номер изменения системы SCN (system change number). Каждая завершенная транзакция увеличивает его. Поэтому можно считать SCN уникальным идентификатором завершенной транзакции.

    В заголовок блока данных записывается SCN завершившейся транзакции, которая изменяла блок последней.

    При чтении результирующее множество запроса формируется следующим образом:

  • Анализируется текущий SCN. Будем его называть SCN запроса.
  • При считывании блока данных Oracle сравнивает SCN запроса с SCN из заголовка этого блока, чтобы определить, читать ли сам блок или воспользоваться сегментом отката, в который заносились все читаемые блоки, так что вносимые изменения в них не отражаются.
  • Возможны два варианта

  • если SCN блока меньше или равен SCN запроса, то последняя изменявшая блок транзакция завершилась до начала чтения; в этом случае читается сам блок;
  • если же SCN блока больше SCN запроса, то изменения блока завершились после начала чтения; в этом случае из сегмента отката читается нужный блок, сохраненный в исходном состоянии до начала чтения.
  • Если не используется многоверсионность и не применяются блокировки, возможно получение несогласованных данных.

    6.5 Транзакции, откаты и восстановление после сбоев

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

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

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

    Выход из положения — использование дополнительных кольцевых последовательных буферов отката и файлов журналов, имеющих последовательный доступ. Такие файлы быстрее файлов данных. Избыточность, образующаяся при введении буферов отката и файлов журналов, позволяет хранить в базе и измененные данные, и их версии, существовавшие до внесения изменений. Рассмотрим работу буферных структур, изображенных на рисунке 6.8.

    (рис 6.8) Буферные структуры СУБД

    Кэш буферов базы — это раздел памяти (вспоминаем, что термин "память" означает "оперативная память" или "первичная память"), разделенный на блоки, по размеру совпадающие с блоками базы. Блок, который прочитали с диска и не успели изменить, называется чистым. И он же, но с внесенными изменениями, называется грязным. Заметим, что повышение быстродействия при использовании буферов базы происходит, только если вместе с необходимыми данными из диска извлекаются те данные, которые будут в ближайшее время использованы. Вспомните, что показатель HitRatio, определяемый как отношение числа обращений в буфер к общему числу обращений, очень близок к единице.

    На самом деле чистые блоки пишутся одновременно и в кэш буферов базы, и в буфер журнала. Из-за высокого быстродействия первичной памяти это не слишком увеличивает время записи.

    Кольцевой буфер отката состоит из таких же по размеру блоков. Из рисунка 6.8 видно, как взаимодействуют транзакции при квазипараллельной работе. Транзакция, например Т1, занимает чистыми блоками последовательно расположенные блоки буфера отката. Если в это время другая транзакция Т2 читает свой блок, то он будет размещен сразу за блоками, занятыми Т1, и далее блоки разных транзакций могут чередоваться. Если необходимо восстановить исходные данные, достаточно, используя инвертированные списки блоков, вытащить блоки откатываемой транзакции из кольцевого буфера. Движение по ссылкам занимает очень мало времени. Поэтому откат выполняется быстро.

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

    6.5.1 Восстановление данных

    Основные ситуации, в которых требуется восстановление данных:

  • Откат транзакции по команде ROLLBACK.
  • Мягкий сбой системы (утрата части или всей первичной памяти).
  • Жесткий сбой системы (отказ аппаратуры, чаще повреждение вторичной памяти).
  • Далее будет рассмотрена упрощенная система организации откатов и восстановлений после сбоев.

    Необходимо удовлетворить два противоречивых требования. С одной стороны, из-за недостатка оперативной памяти и из-за возможности сбоев необходимо выталкивать "грязные" блоки как можно раньше, но это замедлит работу. Для ускорения работы необходимо записывать на диск как можно реже, но это увеличивает риск рассогласования базы.

    6.5.2 Принцип "Write Ahead Log"

    Измененные объекты базы данных должны попадать в файл журнала раньше, чем грязные блоки кэша буферов базы попадут в файлы данных. Пиши сначала в журнальный файл! Write Ahead Log!

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

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

    При этом выполняются следующие два действия:

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

    6.5.3 Откат транзакции

    Завершенные по COMMIT транзакции нельзя откатить. Для выполнения отката незавершенной транзакции необходимо:

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

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

  • Транзакция успешно завершена до контрольной точки, все ее данные сохранены на диске. В восстановлении не нуждается.
  • Транзакция успешно завершена. Блоки журнала вытолкнуты полностью, а блоки буферов базы частично. Необходимо завершить операции, не отображенные в блоках данных.
  • Транзакция начата до последней контрольной точки и не завершилась до сбоя. Часть блоков данных и журнала переписана на диск по событию контрольной точки. Результатов остальных изменений нет. Необходимо откатить транзакцию.
  • Транзакция начата после последней контрольной точки и завершилась до сбоя. Записи журнала вытолкнуты на диск. Изменения в блоках базы на диске не производились. Необходимо повторить все действия (накатить транзакцию).
  • Транзакция начата после последней контрольной точки и не успела завершиться до сбоя. В журнале нет сведений о ней, изменения блоков базы были только в оперативной памяти. Делать ничего не нужно. Транзакция должна быть повторена.
  • 6.5.5 Восстановление после жесткого сбоя

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

    Порядок действий по восстановлению базы:

  • По архивным файлам восстановить базу данных. Желательно иметь возможность восстановить базу по состоянию на предыдущий день.
  • Если журнал сохранился, то по журналу повторяются все последние успешно завершившиеся транзакции. (При этом не нужно откатывать транзакции, прерванные в результате сбоя).
  • Если журнал погиб, восстановление последних транзакций невозможно и база создается по состоянию на момент последней архивации.
  • Замечание. Комплекс работ по сохранению и восстановлению данных называется backuprecovery.

    Хороший сервер это сложная система. В нем необходимо иметь источники питания и диски с горячей заменой. Источник бесперебойного питания рассчитывается так, чтобы он обеспечил работу во время запуска и выхода на режим дизель-генератора, обеспечивающего автономное питание практически на любое время. В серверном помещении должна быть создана эффективная система пожаротушения.

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

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

    6.6 Основные понятия главы

    (рис 6.9) Свойства транзакции (рис 6.10) Синтаксис транзакции (рис 6.11) Ограничения целостности (рис 6.12) Уровни изоляции пользователей (рис 6.13) Согласованность (рис 6.14) Блокировки
    Страницы:

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

    Изучим свойства транзакций и языковые средства для их оформления.

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

    Дадим классификацию ограничений целостности.

    Введем понятие ссылочного ограничения целостности и покажем как оно поддерживается с помощью триггера.

    Опишем феномены — проблемы, возникающие при параллельной работе—и на их основе определим уровни изолированности пользователей.

    Изучим проблемы блокирования ресурсов и возникновения тупиков. Бегло рассмотрим многоверсионные данные.

    В заключительных разделах рассмотрим роль транзакций в организации откатов и восстановлении данных после сбоев.

    6.1 Зачем нужны транзакции? Что мы ждем от них? Свойства транзакций

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

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

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

    Транзакции обеспечивают:

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

    Пусть при выполнении двух последовательно выполняющихся действий после успешного выполнения первого из них возник сбой (рисунок 6.1).

    (рис 6.1) Что может произойти при сбое сервера

    В приведенном примере это означает, что сумма, снятая с первого счета не попала на второй счет. Хуже того, сам факт исчезновения денег никак не зафиксирован. Если вас этот неприятный факт не расстроил, и вы считаете, что можно повторить все с начала, то тут сказывается одна нехорошая привычка, вырабатываемая ориентированностью наших студентов, фактически, на научные вычисления. Скажем, складывал я две матрицы, а они у меня не сложились. Ну, бог с ними — я их сложу еще раз. Но, если вы написали систему, которая отправила миллиард долларов, и он никуда не пришел, то, скорее всего, усовершенствовать ее будет уже другой человек, а вы будете искать новую работу. Вот, собственно, в этом разница в отношении к надежности вычислений в науке и бизнесе. Вообще говоря, бизнес-приложения должны работать всегда, независимо от того, исправен ли компьютер или нет, заболели ли вы или еще что-то случилось, ночь сейчас или день. Ведь есть организации, работающие круглые сутки. Хорошим тоном считается обеспечивать работоспособность в самом тяжелом режиме, который принято называть "24*7" (двадцать четыре на семь). Это означает 24 часа в сутки 7 дней в неделю. Это очень тяжелый режим, и без специальных мер он сам по себе не обеспечивается. Представьте, что в вашем домашнем компьютере нужно поменять или добавить жесткий диск. Как это сделать без останова системы?

    (рис 6.2) Возможные ошибки

    Для рассмотрения параллельной работы двух пользователей вспомним, что "сальдо" —это остаток на счете. Посчитать сальдо по группе счетов — это, попросту говоря, сложить их остатки. Обычный арифметический цикл, который известен всем школьникам. На рисунке 6.2 слева показана работа такого цикла. Сначала сальдо С по группе счетов равно 0, потом прибавляем к нему остаток первого счета, второго счета и т.д., счета тысячного. Как учили в школе? Написали цикл, проверили его на примере. Дальше работает всегда правильно. Но у нас то пользователь не один. Транзакция изображенная слева выполняет циклическое суммирование. Но после того, как счет 1 уже учтен, и до того, как учтен счет 1000, вторая транзакция, изображенная справа, снимает 200 руб. со счета 1, и зачисляет их (то есть прибавляет 200 руб.) на счет 1000. Теперь в подсчете сальдо по группе счетов появится ошибка в 200р. Почему? Потому что счет 1 мы обрабатывали в неизмененном состоянии, а счет 1000 в измененном. А что будет, если во времени вторая транзакция будет сдвигаться? Ну, очевидно, если она будет выполнена до первой (левой на рисунке) транзакции или после нее, то ничего страшного не случится, все будет посчитано правильно. А если транзакции начнут выполняться на одном промежутке времени то, с какого-то момента появится ошибка. Обратите внимание, довольно странная, неожиданная для начинающих ситуация — арифметический цикл, оказывается, может считать данные неправильно, если одновременно идет некоторый другой процесс, имеющий доступ к тем же данным. Транзактный механизм должен подобные казусы исключить.

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

    (рис 6.3) Ошибка при подсчете сальдо при параллельной работе без транзакций

    Работа с тысячью счетов в примере может затянуться, поэтому счетов у нас всего три штучки (Сч1, Сч2, Сч3). На них 500 руб., 700 руб. и 800 руб. соответственно. Сальдо С по группе этих трех счетов в начале вычисления равно нулю. Переменная t — это разметочная переменная, отмечающая моменты времени 0, 1, 2, 3, 4. Она определяет порядок действий. Сначала вы должны выполнить ту строчку в левом терминале, где есть присваивание t = 0. После него выполняете первую строчку второго терминала там, где t присваивается значение 1, и т.д. Если вам трудно следить за текстом, повторите все сами. Как и ожидалось, сальдо вычисляется с ошибкой.

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

    Вернемся к рассмотренной ранее операции — переводу денег с одного счета на другой. Эта операция включает две атомарные (элементарные, неделимые) операции:

  • снятие суммы с одного счета,
  • зачисление суммы на другой счет.
  • Если СУБД будет выполнять (или не выполнять) эти две операции только вместе, то база будет всегда находиться в согласованном состоянии и не будут возникать ситуации, при которых суммы, снятые с одного счета, ни на какой другой счет не поступают.

    Определение. Объединение нескольких операций в неделимое целое называют транзакцией.

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

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

    6.1.1 Определение транзакции

    Транзакция — это последовательность действий, выполняющаяся как единое целое и переводящая базу данных из одного согласованного состояния в другое согласованное состояние. Вы должны помнить, что ограничения целостности определенные в бизнесе, могут быть не прописаны явно. Так, в примере, приведенном на рисунке 6.1, ограничение целостности можно сформулировать так: деньги, снятые со счета, должны быть зачислены на другой счет. Может быть задействована некоторая цепочка счетов. Например, если вы отправляете деньги через свой банк клиенту другого банка, то деньги сначала поступают на ваш клиентский счет, с него — на корреспондентский счет вашего банка в другом банке, а уже оттуда на клиентский счет получателя. Всегда необходимо додумывать ситуацию до конца, тогда она будет прозрачна, понятна, и вы с ней справитесь. В транзакциях используют следующие инструкции:

  • чтения данных;
  • манипулирования данными;
  • блокирования и разблокирования ресурсов.
  • Транзакции обладают набором свойств (рисунок 6.4), характеризуемым аббревиатурой АСИД (ACID):

  • А - Атомарность. Транзакция выполняется как единое целое (либо все
  • выполняется, либо все не выполняется).
  • С - Согласованность. Транзакция переводит базу данных из одного согласованного состояния в другое согласованное состояние. Внутри транзакции согласованность базы данных может нарушаться.
  • И - Изолированность. Транзакции разных пользователей не должны мешать друг другу. Иначе говоря, транзакция должна работать только с непротиворечивыми данными, не имея доступа к промежуточным результатам.
  • Д - Долговечность. Результаты работы выполненной транзакции должны сохраниться в базе данных, даже если по завершении транзакции произойдет сбой системы.
  • (рис 6.4) Свойства транзакции

    Замечание. Соответствующая английская аббревиатура ACID образована первыми буквами терминов Atomicity, Consistency, Isolation, Durability.

    6.1.2 Замечание о свойствах АСИД

    Свойства АСИД транзакций, за исключением атомарности, не всегда выполняются в полном объеме.

  • Согласованность. Нарушается внутри транзакции. При неполной изоляции транзакций несогласованные данные могут быть доступны другим транзакциям.
  • Изоляция. Полной изоляции транзакций можно достигнуть, только если принудительно выстраивать транзакции в очередь и исполнять их строго одну после другой. При этом достаточно одной из транзакций зависнуть или выполняться слишком долго (сначала я пила кофе, потом меня вызвал начальник, а потом был перерыв), чтобы остановить всю работу. Поэтому вводится несколько уровней неполной изоляции.
  • Долговечность. Может нарушаться если, например, транзакция $$T_2$$, вызванная из транзакции $$T_1$$ будет завершена успешно, a $$T_1$$ откатится. Тогда откатятся данные транзакции $$T_2$$
  • 6.2 Языки управления транзакциями

    Будем рассматривать так называемые плоские транзакции, обладающие одним управляющим слоем. Именно этот тип широко распространен в современных СУБД.

    Существует два способа запуска транзакции:

  • Транзакция начинается автоматически с момента присоединения пользователя к СУБД или по завершении предыдущей транзакции (например, в Oracle).
  • Транзакция начинается специальной командой, например, BEGIN TRANSACTION или другой командой аналогичного назначения (например, в Cache).
  • Транзакция завершается, успешно или не успешно, специальными командами или по одному из событий:

  • Команда COMMIT [WORK] (зафиксировать транзакцию).
  • Команда ROLLBACK [WORK] (откатить транзакцию).
  • Событие "Отключение пользователя от СУБД".
  • Событие "Сбой системы".
  • Событие "Отключение системы".
  • В языке COS начало транзакции — команда TSTART, успешное завершение — TCOMMIT, неуспешное — TROLLBACK.

    В таблице 6.1 приведены команды управления данными для Cache.

    Команды управления данными для Cache
    Команда SQL Команда COS Описание
    %BEGTRANS TSTART Начало транзакции
    COMMIT [WORK] TCOMMIT Успешное завершение транзакции
    ROLLBACK [WORK] TROLLBACK Неуспешное завершение транзакции

    Замечание. В Oracle любые действия вкладываются в транзакции, в Cache транзакции используются по желанию пользователя. Пользователь Cache, работающий вне транзакции как бы "не замечает",

    чужих транзакций.

    Создадим и откомпилируем программу ^tr1 содержащую транзакцию:

    SET ^tt(0)=0, ^tt(1)=1 ;значения до транзакции
    TSTART ;начало транзакции
    SET ^tt(0)= "Значение 1 из транзакции"
    READ x ;приостанавливает исполнение
    SET ^tt(1)= "Значение 2 из транзакции"
    TCOMMIT /* конец транзакции - успешное завершение */
    WRITE "^tt(0)= ",^tt(0),!, "^tt(1)= ", ^tt(1)
    QUIT
    

    Вы помните, что в Cache данные бестиповые, тип данных определяется по контексту. Например запись -x приведет к интерпретации x как числа. В программе ^tr1 назначаем глобалам ^tt(0) и ^tt(1) цифровые значения. А в транзакции присваиваем им текстовые значения. READ x используется для остановки исполнения программы за счет ожидания ввода данных человеком. В конце печатается текст ^tt(0) =.. ^tt(1) =.. и сами значения этих глобалов. Если транзакция откатывалась, то глобалам ^tt(0) и ^tt(1) возвращаются цифровые значения.

    Запустим два терминала.

    Просмотрим два варианта исполнения транзакции:

  • С вводом пустого значения в переменную x (левый терминал) — см. рисунок ниже. Сначала в левом терминале исполняется программа ^tr1 , затем в правом читается значение глобала. Транзакция завершилась успешно, поэтому возвращены текстовые значения.
  • С выключением левого терминала (для выключения нажмите на кнопку с крестиком -см. рисунок ниже. Правый терминал читает ^tt(0) первый раз до выключения левого терминала, а второй раз — после его выключения. Вывод: выключение терминала откатывает транзакцию.
  • 6.3 Транзакции и ограничения целостности

    Переходим к первой задаче, которую решают транзакции — соблюдение ограничений целостности. Что нужно для проверки любого ограничения? Прежде всего, база должна быть активной, то есть иметь способность совершать что-то, сверх того, о чем ее попросили. Пусть задано ограничение первичный ключ. Когда мы приказываем ввести строку, СУБД сама обнаруживает, что такое ограничение имеется и запускает процедуру, проверяющую, не повторяются ли значения в ключевых столбцах. И, наверное, она не позволит ввести строку с повторяющимся значением ключа. Хорошо бы еще получить какое-то сообщение о возможном нарушении ограничения целостности.

    Чаще всего источник активности — специальные процедуры, называемые триггерами. Но это не единственный вариант. Триггеры напоминают резидентные программы. Они начинают работать только при запуске некоторого процесса, называемого триггерным событием. В число таких событий всегда входят вставка, удаление и обновление строк (по-английски insert, update и delete). В современных базах данных триггеры могут реагировать и на другие события. Поскольку ограничения целостности определены для хранимых объектов базы, триггеры прикрепляются к этим объектам.

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

    6.3.1 Нарушения целостности

    Определение. База данных находится в согласованном (целостном) состоянии, если выполнены все заданные в ней ограничения целостности.

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

    Система управления базами данных обязана реагировать на любые попытки нарушения целостности.

    Два основных типа реакции СУБД:

  • Отказ выполнить "недопустимую" операцию.
  • Выполнение действий, компенсирующих нарушения ограничений целостности. Далее мы поясним этот тип на примере.
  • Классификация ограничений целостности проводится по следующим основаниям:

  • По способам реализации.
  • По времени проверки.
  • По области действия.
  • 6.3.2 Классификация по способам реализации

    Различают

  • декларативную поддержку ограничений целостности;
  • процедурную поддержку ограничений целостности.
  • Декларативная поддержка ограничений целостности задается средствами языка определения данных (ЯОД, он же DDL — Data Definition Language).

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

    CREATE TABLE person (persid INTEGER PRIMARY KEY, persname CHAR(30) NOT NULL);

    Если прочитать ее на русском языке, все станет понятно. Итак: "СОЗДАТЬ ТАБЛИЦУ с названием person, содержащую два столбца: persid типа ЦЕЛОЕ с ограничением ПЕРВИЧНЫЙ КЛЮЧ и persname типа СИМВОЛЬНОЕ с длиной 30 символов и ограничением НЕЛЬЗЯ НЕ ЗАПОЛНЯТЬ".

    Процедурная поддержка ограничений целостности

    Процедурные ограничения целостности реализуются использованием триггеров и хранимых процедур.

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

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

    Триггеры

    Триггеры — это процедуры специального вида, прикрепленные к объекту базы, например, таблице, и срабатывающие при наступлении событий из некоторого набора событий. Наиболее известны триггеры, реализуемые в языке SQL.

    В набор событий обычно входят вставка записи (инструкция INSERT в SQL), удаление записи (инструкция DELETE) и обновление записи (инструкция UPDATE).

    Триггер может выполняться до отработки события (триггер BEFORE) и после его отработки (триггер AFTER).

    Выделяют также триггеры уровня строки (записи), срабатывающие один раз на каждую выбранную строку, и триггеры уровня выражения, срабатывающие один раз при обработке набора записей (например, при входе в таблицу или выходе из нее). Итого 3 * 2 * 2 = 12 типов триггеров на один набор записей (таблицу).

    Для проверки ограничения на возраст принимаемого на работу создают триггер на команду INSERT, срабатывающий до ее выполнения (нельзя допускать запись о неправильном приеме — значит выбираемый тип — BEFORE). В теле триггера вычисляют возраст, проверяют условие $$21\le возраст \le 45$$. Если условие не выполнено, запись о приеме аннулируется (рисунок 6.5).

    (рис 6.5)

    Ссылочные ограничения целостности

    Рассмотрим пример ссылочного ограничения целостности.

    Пусть задана схема, состоящая из двух таблиц dept (отделы) и person (сотрудники) (таблицы 6.2 и 6.3).

    Столбцы таблицы dept (deptid, deptname, deptquan): deptid — идентификатор подразделения, deptname —имя подразделения, deptquan — количество сотрудников в подразделении.

    Столбцы таблицы person(persid, persname,deptid): persid — идентификатор сотрудника, persname —имя сотрудника, deptid —идентификатор подразделения, в котором работает сотрудник.

    Содержимое таблицы dept
    DEPTID DEPTNAME DEPTQUAN
    1 Цех разлива 3
    2 Транспортный цех 2
    Содержимое таблицы person
    PERSID PERSNAME DEPTID
    1 Иванов 1
    2 Петров 2
    3 Сидоров 1
    4 Злобис 2
    5 Вредис 1

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

    Если уволим Иванова, недостаточно просто его вычеркнуть, потому что при этом изменится численность первого отдела. Ее нужно пересчитать, вычтя единицу из значения в столбце deptquan. А если переведем Иванова в транспортный цех, то необходимо не только изменить строку с Ивановым в таблице dept, но еще и прибавить к численности транспортного цеха единицу, вычтя единицу из численности цеха розлива. Иначе таблица "Отделы" окажется неправильно заполненной.

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

    Например, прием нового сотрудника не может быть выполнен одной операцией. Необходимо вставить запись в таблицу person и одновременно увеличить значение поля deptquan на 1. Выполняем два шага, которые, конечно же, необходимо включить в транзакцию:

  • Вставить запись о сотруднике в таблицу person

    INSERT INTO person VALUES (6, 'Петросян', 1).

    (ВСТАВИТЬ в person строку (6, "Петросян", 1).)

  • Увеличить значение поля deptquan

    UPDATE dept
    SET depquan=deptquan+l WHERE deptid=l. 
    

    (ОБНОВИТЬ dept, УСТАНОВИВ ЗНАЧЕНИЕ deptquan = deptquan+1 ДЛЯ СТРОКИ В КОТОРОЙ deptid= 1). Для увольнения и перемещения сотрудников необходимы свои транзакции.

  • Замечание. Пока мы не изучили инструкции SELECT, INSERT и UPDATE языка SQL, воспринимайте их просто как фразы, написанные на фрагменте естественного (как бы английского) языка, который понимает СУБД.

    Примеры ограничений целостности

    Пример 1. Возраст сотрудника не может быть меньше 21 и больше 45 лет (Декларативное ограничение типа check).

    Пример 2. Атрибут "Табельный номер" уникален. (Декларативное ограничение—уникальный или первичный ключ).

    Пример 3. Сотрудник обязан числиться в одном из отделов. (Декларативное ссылочное ограничение целостности).

    Пример 4. Сумма накладной равняется сумме произведений цен товаров на количество товаров для всех товаров, входящих в накладную. (Определение вычислимого столбца.).

    Замечание. Ограничение целостности может определять не только значения атрибутов, но и особенности удаления или обновления данных. Например, в схеме "Отдел" — "Сотрудник" удаление отдела может вызвать удаление его сотрудников или их перевод в другие отделы.

    Задание ограничения целостности приходит в базу из бизнес-модели. Для проверки правильности этого перехода полезно определить, какие требования предлагает бизнесу это ограничение, то есть выполнить обратный переход. Например, задана уникальность табельного номера. Что это означает для бизнеса? Какой номер присваивается работнику при повторном приеме? Существуют ли сотрудники, не имеющие номера?

    6.3.3 Классификация ограничений целостности по времени проверки

    По времени проверки выделяют два вида ограничений:

  • Немедленно проверяемые ограничения. Проверяются непосредственно в момент выполнения операции, могущей нарушить ограничение.
  • Ограничения с отложенной проверкой. Проверяются в момент фиксации транзакции оператором COMMIT.
  • Пример немедленно проверямого ограничения — первичный ключ. Пример ограничения с отложенной проверкой — ссылочные ограничения (таблицы 6.2 и 6.3).

    6.3.4 Классификация ограничений целостности по области действия

  • Ограничения домена (из-за отсутствия в СУБД поддержки доменов такие ограничения обычно переносятся на атрибуты).
  • Ограничения атрибута (немедленно проверяемые ограничения на допустимые значения атрибута). Реализуются почти всегда декларативно. Проверяют обычно попадание в диапазон или в список. Проверки на соответствие шаблону, например, в записи телефонного номера, в настоящее время реализуются с помощью регулярных выражений, то есть вне основной модели данных.
  • Ограничения кортежа. Это ограничения на соотношение атрибутов одного экземпляра сущности.
  • Ограничения отношения (или сущности или таблицы). Для проверки ограничения необходимо обработать все кортежи отношения. Пример: Только у двух человек в организации заработная плата может быть больше 100000.
  • Ограничения на допустимые связи.
  • Ограничения базы или схемы данных (межтабличные). Накладываются на свойства двух или более связанных между собой отношений. Пример: ссылочная целостность.
  • Итоговая классификация ограничений целостности приведена в разделе 6.6.

    Ограничения на допустимые связи

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

    В типичном случае имеется несколько взаимно исключающих связей. Например, сущность A может быть связана бинарной связью с B или C, но если существует связь A с B, то не может существовать связь A с C, и наоборот.

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

    Заметим, что реализация рассматриваемого класса ограничений достаточно сложна.

    Одна из возможных связей — наследование. Реализация ограничений на наследование будет рассмотрена позже при изучении объектных моделей.

    6.4 Транзакции и параллельная работа

    Любая система в бизнесе представляет множество действующих лиц — акторов — работающих одновременно и организованно. Актором может быть человек, программа или машина. Информационная система должна как-то отражать эту многоакторность. Конечно, в ней самой могут появиться параллельные процессы, не имеющие прямых аналогов в бизнесе.

    В этом разделе будут рассмотрены проблемы, возникающие при параллельной работе транзакций. В соответствии с традицией будем называть эти проблемы феноменами. На основе феноменов зададим уровни изолированности транзакций. В определениях изолированности будет использован стандарт версии SQL-92 языка SQL, но знание SQL и, тем более, стандарта для освоения материала не требуется.

    В заключительной части раздела рассмотрим блокировки ресурсов и эффекты, возникающие при блокировании.

    6.4.1 Феномены

    Феномен "Потерянные изменения"

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

    Пример (таблицы 6.4 и (6.5): На счет Acc с начальным сальдо 100, зачисляют первой транзакцией 20 руб., а второй 100 руб. Если первая транзакция записывает измененные данные в базу и завершается после того, как вторая транзакция их прочла, но до того, как она их записала, то изменения, внесенные первой транзакцией, теряются.

    Пример феномена Феномен "Потерянные изменения". Транзакции
    Транзакция 1 Время Транзакция 2
    Начало Тр1
    Чтение X=Acc to
    Начало Тр2
    ti Чтение X=Acc
    t2 Зачисление X=X+100
    Зачисление X=X+20 t3
    Запись в базу Acc=X t4
    COMMIT
    Конец Тр1
    t5 Запись в базу Acc=X COMMIT
    Конец Тр2
    Пример феномена "Потерянные изменения".
      USER>TStart 
      USER>Set Х=^Асс, t=Q 
      USER>Set Х=Х+20,   t = 3 
      USER>Set ^Асс=Х,  t = 4 
      USER>TCommit 
      USER>
      USER>TStart
      USER>Set Х=^Асс, t=1
      USER>Set X=X+100, t=2
      USER>Set ^Acc=X,   t = 5
      U5ER>TComoit
      USER>Write ^Acc 2 00
      USER>|

    Начальное значение ^Acc=100. TSTART — начало транзакции, TCOMMIT — ее успешное завершение. Переменная t, как и раньше, использована вместо комментария для задания отметки очередности исполнения команд, соответствующей моментам времени $$t = 0, t = 1, \dots$$.

    Зависимость от незафиксированных результатов (чтение "грязных" данных)

    Феномен возникает, когда читаются данные незавершенной транзакции, которые впоследствии откатываются. Начальное значение Acc в примере равно 100 (таблицы 6.6 и 6.7).

    Пример феномена "Чтение грязных данных" . Транзакции
    Транзакция 1 Время Транзакция 2
    Начало Тр1
    Изменение Acc=Acc+20 t1
    Начало Тр2
    t2 Чтение и изменение
    X=Acc+50
    ROLLBACK —возврат к Acc=100
    Конец Тр1
    t4 Acc=X
    Конец Тр2. Значение Acc не 150, а 170
    Пример феномена "Чтение грязных данных"
      USER>TStart
      USER>Set ^Acc=^Acc+20, t=1
      USER>TRollback
      USER>Sett=3 Rollback Write ^Acc
      100
      USER>
      
      USER>TStart
      USER>S X=^Acc+50, t=2
      USER>Set ^Acc=X ,t=4
      USER>TCommit
      USER>Set t=5
      USER>Write ^Acc
      170

    Феномен "Неповторяющееся чтение"

    Разберите сами приведенный ниже пример (таблицы 6.8 и 6.9).

    Пример феномена "Неповторяющееся чтение" . Транзакции
    Транзакция 1 Время Транзакция 2
    Начало Тр2
    t1 Чтение Acc (Acc=100)
    Начало Тр1
    Изменение Acc=300 t2 Проверка Acc<200 — Да
    t3 Чтение Acc (Acc=300) Проверка Acc<200 — Нет
    Пример феномена "Неповторяющееся чтение"
      USER>TStart
      U5ER>Set t=2,  ^Acc=300
      USER>
      USER>TStart 
      USER<Set t=l
      USER>If ^Acc<200 Write "Да"Да
      USER>Set t=3U
      SER>If ^Acc<200 Write "Да"
      USER>|
      

    Феномен появления записей "фантомов"

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

    Пусть транзакция Тр1 выбирает группу строк из таблицы Т, удовлетворяющих условию У1. Затем транзакция Тр2 вставляет в Т еще одну строку, удовлетворяющую условию У1. При повторной выборке строк транзакцией Тр1, к полученному в предыдущей выборке набору добавляется еще одна строка, называемая фантомом, привидением.

    6.4.2 Уровни изолированности пользователей

    Стандарт SQL-92, основываясь на перечисленных феноменах, определяет четыре уровня изолированности:

  • Read uncommitted. Чтение незафиксированных изменений. Самый низкий уровень изоляции. Воспринимаются как окончательные, так и промежуточные результаты других транзакций. СУБД предотвращает только проблему потерянных изменений.
  • Read committed. Чтение зафиксированных изменений. Уровень изоляции выше, чем у read uncommited. Транзакция не имеет доступа к промежуточным результатам других транзакций. Но окончательные результаты других транзакций, завершившихся во время исполнения нашей транзакции, могут быть доступны. Возможно получение
  • разных результатов при повторе запроса до и после фиксации другой транзакции, изменяющей данные. Возможны фантомы.
  • Repeatable read. Повторяемое чтение. Уровень изоляции выше, чем у read commited. Транзакция не имеет доступа к промежуточным или окончательным результатам других транзакций. Вставки записей приводят к появлению фантомов.
  • Serializable. Сериализуемость. Самый высокий уровень изоляции. Результат параллельного выполнения транзакций будет точно таким же как при последовательном их выполнении. Все перечисленные выше феномены отсутствуют, но параллельное выполнение транзакций, работающих с одними ресурсами невозможно.
  • Данные по уровням изолированности сведены в таблицу 6.10. Знак "+" в ней означает наличие феномена, а знак "-" — его отсутствие.

    Уровни изолированности и отсутствие феноменов
    Уровень Потерянные "Грязное" Неповторяющееся Фантомы
    изоляции изменения чтение чтение
    Serializable + + + +
    Repeatable read + + + -
    Read commited + + - -
    Read uncommited + - - -

    Так зачем нужны "плохие" виды транзакций, и особенно, Read uncommited?

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

    Пример: Библиотекари регулярно работают с каталогом, открывая транзакции для сверки и изменения каталога. Эти работы могут длиться часами. Что Вы предпочитаете, ждать появления исправленного каталога, или просматривать его в любой момент времени, может быть, иногда получая неправильные данные?

    Замечание. Правильнее было бы дать библиотекарям работать с копией каталога, а когда они закончат работу быстро обновить каталог, запретив остальным пользователям доступ на несколько минут или секунд.

    Как обеспечить правильную работу транзакций, обращающихся к одним и тем же ресурсам?

    Два основных способа:

  • блокирование "общих" ресурсов;
  • предоставление транзакциям, конкурирующим за ресурсы, разных экземпляров данных.
  • 6.4.3 Блокирование

    По степени разделяемости блокируемых ресурсов различают:

  • Монопольные блокировки (eXclusive locks или X-locks), называемые еще блокировками записи. Они не разрешают другим транзакциям доступ к блокированному ресурсу.
  • Разделяемые блокировки (Shared locks или S-locks) иначе блокировки чтения. Разрешают совместный доступ к блокированному ресурсу.
  • Виды блокировок

    Блокировки различают еще по размерам блокируемого ресурса: поле записи, запись, отношение, страница (блок базы), группа отношений, вся база. Забегая вперед, отметим, что в современных СУБД используется два способа хранения таблиц — строчный и столбцовый. Чаще данные хранятся строками. В этом случае минимальный объем блокировки — строка. В столбцовых базах —поле. Выполняется свойство иерархичности. Если объект верхнего уровня обладает блокировкой, то такой же блокировкой обладает его подобъект нижнего уровня.

    Взаимодействие блокировок:

  • Имеется X-блокировка. Запросы на любые блокировки от других транзакций будут отменены.
  • Имеется S-блокировка. Запрос на X-блокировку отвергается. Запрос на S-блокировку принимается.
  • Рассмотрим доступ по чтению и записи. Возможный протокол доступа к данным по чтению и записи с блокировками определяется следующими правилами:

  • Перед чтением объекта базы транзакция должна наложить на него S-блокировку.
  • Перед записью объекта базы транзакция должна наложить на него X-блокировку. Если перед этим транзакция выполняла S-блокировку этого объекта, то она заменяется X-блокировкой.
  • Если объект уже заблокирован X-блокировкой другой транзакции, то любая другая блокировка отвергается и транзакция переводится в состояние ожидания до тех пор, пока мешающая блокировка не снимется.
  • Если объект уже заблокирован S-блокировкой другой транзакции, то другая S-блокировка принимается, а X-блокировка отвергается.
  • X-блокировки сохраняются до конца транзакции. S-блокировка снимается по завершении чтения.
  • Если транзакция B начинается позже транзакции A, то успешность блокирования объекта базы транзакцией B определяется следующей матрицей совместимости блокировок (таблица 6.11):

    Матрица совместимости блокировок
    Транзакция A наложила блокировку Транзакция B пытается наложить блокировку
    S-блокировку X-блокировку
    S-блокировку + - (конфликт R-W)
    X-блокировку - (конфликт W-R) - (конфликт W-W)
    Транзакция-читатель мешает транзакции-писателю

    Сделаем важный вывод: при использовании разграничения доступа с помощью S- и X-блокировок транзакции-читатели могут мешать транзакциям-писателям. Транзакции-писатели всегда мешают другим транзакциям-писателям и транзакциям-читателям.

    Как выполняются блокировки в транзакциях Cache ObjectScript? С помощью команды LOCK имеющей формат:

    L[OCK] [+|-] список_имен_блокируемых_ресурсов

    Пример: LOCK ^a(1) или, сокращенно, L ^а(1) блокирует узел ^а(1).

    Форматы команды LOCK
    Формат Действие
    LOCK Отмена всех блокировок
    LOCK список_имен Снимает все блокировки, затем блокирует по
    одному имени из списка
    LOCK +имя Инкрементальная блокировка - добавление
    блокировки к уже существующим блокировкам
    LOCK -имя Удаляет блокировку

    Команда LOCK ^а(1), ^b(3) эквивалентна LOCK ^а(1) LOCK +^b(3). Использование времени ожидания (для инкрементальных блокировок рекомендуется всегда): LOCK ^а(1):5

    Попытка блокировать прерывается, если прошло 5 секунд, а блокировка не завершилась. При этом устанавливается значение $TEST=0. Просмотреть текущие блокировки можно двумя способами:

  • Программой ^LOCKTAB, запускаемой из терминала. Пример:

    USER>LOCK ^a(0)
    USER>ZN "%SYS"
    %SYS>DO ^LOCKTAB
    
  • С помощью портала управления системой - рисунок 6.6

    (рис 6.6) Список блокировок в портале управления системой
  • Результат выполнения последней команды:

    Node Name: WS111
    LOCK table entries at 11:10PM 11/28/2012 
    1176464 bytes usable,  1178528 bytes available.
     
    Entry  Process  X#  S#  Flg  W#  Item Locked
    1)  1188  1  ^[  "^^c:\intersystems\trycache\mgr\"]%SYS("CSP","Daemon")
    2)  3400  1  ^[  "^^c:\intersystems\trycache\mgr\"]TASKMGR
    3)  2640  1  ^[  "^^c:\intersystems\trycache\mgr\user"]a
    Command=>
    
    

    Любая СУБД блокирует как минимум свой словарь, поэтому вместо одной установленной нами блокировки на ресурс ^а(0) мы видим еще и другие блокировки.

    Справку по ^LOCKTAB можно получить командой HELP, сокращенно H. Выход из программы осуществляет команда Q[UIT].

    Блокировки распространяются в дереве на всех потомков и предков.

    Пример:

    Создадим глобал S ^a(1)=1, ^a(1,1)=11, ^a(2)=2, ^a(2,1)=21

    Команда L ^a(1) блокирует не только ^а(1), но и ^а,^а(1,1). Обратите внимание на то, что блокируются и локалы и глобалы. При этом LOCK не проверяет существование узлов. Поэтому блокировка создается и для несуществующих узлов.

    Рекомендация: Для удобства тестирования желательно, чтобы количество инкрементальных блокировок узла совпадало с количеством инкрементальных деблокировок.

    Пример: Если выполнена команда L +(^a,^b,^c), а затем L ^b, то останется заблокированным только ^b.

    6.4.4 Тупики (Clinch, Deadlock)

    При использовании транзакций возникают побочные и очень неприятные явления. Это тупики. Возникают они, когда две или более транзакций пытаются блокировать монопольно одни и те же ресурсы. Не получая доступа к ресурсам, конфликтующие транзакции могут зависнуть на неопределенное время. В СУБД этого допускать нельзя. Нужно как-то разрешить конфликт. Например, через некоторое время после возникновения тупика конфликтующие транзакции откатывают, но затем их вновь запускают в случайно выбранные моменты времени, совсем как при разрешении конфликтов в сети Ethernet.

    В транзакции могут исполняться следующие виды операций:

  • БРi — блокирование ресурса i;
  • РРi — разблокирование ресурса i;
  • действие над ресурсом i. (Чтение или манипулирование данными).
  • Ситуация тупика возникает при попытке двух транзакций блокировать одни и те же ресурсы:

    (рис 6.7) Ситуация тупика

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

    Использование блокировок позволяет решать проблемы параллельного доступа. Пример использования блокировок для устранения чтения "грязных" данных — в таблице 6.13

    Использование блокировок для устранения чтения "грязных" данных
    Транзакция 1 Время Транзакция 2
    S-блокировка Acc успешна t1
    Чтение Acc t2
    t3 S-блокировка Acc успешна
    t4 Чтение Acc
    Попытка записи Acc (X- t5
    блокировка отвергается)
    Ожидание t6 Попытка записи Acc (X-
    блокировка отвергается)
    Ожидание t7 Ожидание

    X-блокировки отвергаются, так как уже установлены две S-блокировки. Обе транзакции ожидают завершения другой транзакции. Возникает тупик, зато феномен чтения "грязных" данных отсутствует. Если же S-блокировка транзакции 2 будет выполнена после успешной X-блокировки транзакции 1, то транзакция 2 будет ждать завершения транзакции 1.

    Многоверсионные данные

    Мы видели, что в СУБД с единственной версией данных транзакции-читатели могут мешать транзакциям-писателям (конфликты R-W).

    В многоверсионных СУБД транзакциям-читателям предоставляются свои версии данных, получаемые откатом части схемы базы до последнего согласованного состояния. Такие транзакции не блокируют данных и потому не мешают транзакциям-писателям.

    Например, многоверсионная СУБД Oracle создает и поддерживает номер изменения системы SCN (system change number). Каждая завершенная транзакция увеличивает его. Поэтому можно считать SCN уникальным идентификатором завершенной транзакции.

    В заголовок блока данных записывается SCN завершившейся транзакции, которая изменяла блок последней.

    При чтении результирующее множество запроса формируется следующим образом:

  • Анализируется текущий SCN. Будем его называть SCN запроса.
  • При считывании блока данных Oracle сравнивает SCN запроса с SCN из заголовка этого блока, чтобы определить, читать ли сам блок или воспользоваться сегментом отката, в который заносились все читаемые блоки, так что вносимые изменения в них не отражаются.
  • Возможны два варианта

  • если SCN блока меньше или равен SCN запроса, то последняя изменявшая блок транзакция завершилась до начала чтения; в этом случае читается сам блок;
  • если же SCN блока больше SCN запроса, то изменения блока завершились после начала чтения; в этом случае из сегмента отката читается нужный блок, сохраненный в исходном состоянии до начала чтения.
  • Если не используется многоверсионность и не применяются блокировки, возможно получение несогласованных данных.

    6.5 Транзакции, откаты и восстановление после сбоев

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

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

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

    Выход из положения — использование дополнительных кольцевых последовательных буферов отката и файлов журналов, имеющих последовательный доступ. Такие файлы быстрее файлов данных. Избыточность, образующаяся при введении буферов отката и файлов журналов, позволяет хранить в базе и измененные данные, и их версии, существовавшие до внесения изменений. Рассмотрим работу буферных структур, изображенных на рисунке 6.8.

    (рис 6.8) Буферные структуры СУБД

    Кэш буферов базы — это раздел памяти (вспоминаем, что термин "память" означает "оперативная память" или "первичная память"), разделенный на блоки, по размеру совпадающие с блоками базы. Блок, который прочитали с диска и не успели изменить, называется чистым. И он же, но с внесенными изменениями, называется грязным. Заметим, что повышение быстродействия при использовании буферов базы происходит, только если вместе с необходимыми данными из диска извлекаются те данные, которые будут в ближайшее время использованы. Вспомните, что показатель HitRatio, определяемый как отношение числа обращений в буфер к общему числу обращений, очень близок к единице.

    На самом деле чистые блоки пишутся одновременно и в кэш буферов базы, и в буфер журнала. Из-за высокого быстродействия первичной памяти это не слишком увеличивает время записи.

    Кольцевой буфер отката состоит из таких же по размеру блоков. Из рисунка 6.8 видно, как взаимодействуют транзакции при квазипараллельной работе. Транзакция, например Т1, занимает чистыми блоками последовательно расположенные блоки буфера отката. Если в это время другая транзакция Т2 читает свой блок, то он будет размещен сразу за блоками, занятыми Т1, и далее блоки разных транзакций могут чередоваться. Если необходимо восстановить исходные данные, достаточно, используя инвертированные списки блоков, вытащить блоки откатываемой транзакции из кольцевого буфера. Движение по ссылкам занимает очень мало времени. Поэтому откат выполняется быстро.

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

    6.5.1 Восстановление данных

    Основные ситуации, в которых требуется восстановление данных:

  • Откат транзакции по команде ROLLBACK.
  • Мягкий сбой системы (утрата части или всей первичной памяти).
  • Жесткий сбой системы (отказ аппаратуры, чаще повреждение вторичной памяти).
  • Далее будет рассмотрена упрощенная система организации откатов и восстановлений после сбоев.

    Необходимо удовлетворить два противоречивых требования. С одной стороны, из-за недостатка оперативной памяти и из-за возможности сбоев необходимо выталкивать "грязные" блоки как можно раньше, но это замедлит работу. Для ускорения работы необходимо записывать на диск как можно реже, но это увеличивает риск рассогласования базы.

    6.5.2 Принцип "Write Ahead Log"

    Измененные объекты базы данных должны попадать в файл журнала раньше, чем грязные блоки кэша буферов базы попадут в файлы данных. Пиши сначала в журнальный файл! Write Ahead Log!

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

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

    При этом выполняются следующие два действия:

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

    6.5.3 Откат транзакции

    Завершенные по COMMIT транзакции нельзя откатить. Для выполнения отката незавершенной транзакции необходимо:

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

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

  • Транзакция успешно завершена до контрольной точки, все ее данные сохранены на диске. В восстановлении не нуждается.
  • Транзакция успешно завершена. Блоки журнала вытолкнуты полностью, а блоки буферов базы частично. Необходимо завершить операции, не отображенные в блоках данных.
  • Транзакция начата до последней контрольной точки и не завершилась до сбоя. Часть блоков данных и журнала переписана на диск по событию контрольной точки. Результатов остальных изменений нет. Необходимо откатить транзакцию.
  • Транзакция начата после последней контрольной точки и завершилась до сбоя. Записи журнала вытолкнуты на диск. Изменения в блоках базы на диске не производились. Необходимо повторить все действия (накатить транзакцию).
  • Транзакция начата после последней контрольной точки и не успела завершиться до сбоя. В журнале нет сведений о ней, изменения блоков базы были только в оперативной памяти. Делать ничего не нужно. Транзакция должна быть повторена.
  • 6.5.5 Восстановление после жесткого сбоя

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

    Порядок действий по восстановлению базы:

  • По архивным файлам восстановить базу данных. Желательно иметь возможность восстановить базу по состоянию на предыдущий день.
  • Если журнал сохранился, то по журналу повторяются все последние успешно завершившиеся транзакции. (При этом не нужно откатывать транзакции, прерванные в результате сбоя).
  • Если журнал погиб, восстановление последних транзакций невозможно и база создается по состоянию на момент последней архивации.
  • Замечание. Комплекс работ по сохранению и восстановлению данных называется backuprecovery.

    Хороший сервер это сложная система. В нем необходимо иметь источники питания и диски с горячей заменой. Источник бесперебойного питания рассчитывается так, чтобы он обеспечил работу во время запуска и выхода на режим дизель-генератора, обеспечивающего автономное питание практически на любое время. В серверном помещении должна быть создана эффективная система пожаротушения.

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

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

    6.6 Основные понятия главы

    (рис 6.9) Свойства транзакции (рис 6.10) Синтаксис транзакции (рис 6.11) Ограничения целостности (рис 6.12) Уровни изоляции пользователей (рис 6.13) Согласованность (рис 6.14) Блокировки
    Вернуться к учебному плану