Бесконечные ожидания и тупики

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

Пример ситуации бесконечного ожидания приведен на рисунке 36.

Tранзакция Т2 время Tранзакция Т1 Tранзакция Т3 Tранзакция Т4
  т1 Блокиро—вание Д    
    Чтение Д    
Попытка блокиро—вания Д – отказ тк      
ожидание... тк+1 Разблоки—рование Д    
      Блокиро—вание Д (раньше, чем Т2)  
      Чтение Д Блокиро—вание Д – отказ
        ожидание…

Рисунок 36 – Пример ситуации бесконечного ожидания

Предположим, что транзакции T1, T2, Т3, Т4 исполнения программы, содержащей следующие действия: блокирование Д; чтение Д; изменение Д; подтверждение сохранения Д; разблокирование Д. Не исключена возможность того, что транзакция Т2 будет бесконечно находиться в состоянии ожидания, тогда как некоторые другие транзакции постоянно осуществляют блокировку Д, хотя и существует неограниченное число моментов, когда Т2 имеет шансы заблокировать Д. Состояние такого рода называется бесконечным ожиданием. Подобная проблема потенциально может возникнуть в любой обстановке, предусматривающей параллельное исполнение процессов (задача продажи билетов).

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

Пример ситуации тупика приведен на рисунке 37. Есть две транзакции: транзакция Т1, содержащая команды, работающие с элементами данных А и В: LOCK(A); LOCK (B); …; UNLOCK (A); UNLOCK (B) и транзакция Т2: LOCK(B); LOCK (A); …; UNLOCK (B); UNLOCK (A). При этом не имеет значения, для каких процессов требуются элементы данных А и В в транзакциях Т1 и Т2 (команда LOCK – заблокировать элемент данных, команда UNLOCK – разблокировать элемент данных).

Транзакция T1 время Транзакция T2
LOCK(A) Т1 LOCK(В)
     
LOCK(В) НЕТ! Т2 LOCK(A) НЕТ!
  Т3  
ожидание   ожидание
     

Рисунок 37 – Пример ситуации тупика

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

Способы предотвращения тупиков являются объектом исследования в области теории БД. Предлагаются различные методы разрешения тупиков.

1 Выполняется линейное упорядочивание элементов по какому—либо признаку (например, последовательно перечисляются все элементы, подлежащие блокированию) и вводится системное требование на составление запросов (программ): все запросы должны выполнять блокировки в этом порядке.

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

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

Уровни изоляции транзакций

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

1 «Грязное чтение» (dirty reads). Одна транзакция изменяет некоторые данные, но еще не завершается. Другая транзакция читает эти же данные (с изменениями, внесенными первой транзакцией) и принимает на их основе какие—то решения. Первая транзакция выполняет откат. В результате решение, принятое второй транзакцией будет основано на неверных данных.

2 «Невоспроизводимое (неповторяемое) чтение» (nonrepeatable reads). Одна транзакция в ходе своего выполнения несколько раз читает одни и те же данные Д. Другая транзакция в интервалах между этими чтениями изменяет данные Д и успешно заканчивается. В результате получится, что чтения, осуществляемые первой транзакцией,дают разные результаты.

3 «Фантомное чтение» (phantoms reads). Одна транзакция в ходе своего выполнения несколько раз выбирает множество строк по одним и тем же критериям. Другая транзакция в интервалах между этими выборками добавляет или удаляет строки или изменяет столбцы некоторых строк, используемых в критериях выборки первой транзакции, и успешно заканчивается. В результате получится, что одни и те же выборки в первой транзакции дают разные множества строк.

Для определения допустимости тех или иных проблем можно определить уровни изоляции транзакций. Такие настройки можно сделать как в среде многопользовательской СУБД, так и в среде разработки приложений. Например, в среде Delphi установка уровней изоляции транзакций определяется свойством компонента Tdatabase property TransIsolation: TtransIsolation. Разработчик может указать желаемый уровень, а СУБД будет управлять транзакциями в соответствии с этими указаниями.

Уровни изоляции транзакций определяют:

— могут ли другие (конкурирующие транзакции) вносить изменения в данные, изменяемые текущей транзакцией;

— может ли текущая транзакция видеть изменения, произведенные конкурирующими транзакциями и наоборот.

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

1 Уровень изоляции Read Uncommetted (RU) — незавершенное, грязное чтение. На этом уровне запрещается изменение данных со стороны других транзакций, если эти данные модифицируются еще не окончившейся транзакцией. Иначе говоря, другие транзакции блокируются по записи для этих данных до тех пор, пока не окончится текущая транзакция. Однако другим транзакциям разрешается считывать еще не подтвержденные данные, что классифицируется как «грязное чтение».

2 Уровень Read Commetted (RC) — чтение данных. На этом уровне запрещается грязное чтение.

3 Repeatable Reads (RR) — воспроизводимое чтение. На этом уровне запрещается «грязное чтение» и «невоспроизводимое (неповторяемое) чтение».

4 Serializable (S) — сериализуемость. На этом уровне запрещается «фантомное чтение».

В таблице 30 приведены уровни изоляции транзакций и проблемы, которые они решают.

Таблица 30 – Уровни изоляции

Тип Проблемы Уровень изоляции
RU RC RR S
Грязное чтение Возможно Невозможно Невозможно Невозможно
Невоспроиз—водимое чтение Возможно Возможно Невозможно Невозможно
Фантомное чтение Возможно Возможно Возможно Невозможно

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

Наши рекомендации