Студопедия

КАТЕГОРИИ:

АстрономияБиологияГеографияДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРиторикаСоциологияСпортСтроительствоТехнологияФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника


Блокирование и одновременный доступ.




 

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

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

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

Блокировка — это механизм, используемый для управления одновременным доступом к общему ресурсу. Обратите внимание: использован термин "общий ресурс", а не "строка таблицы".

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

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

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

Разница между тем, как это делается в Oracle и в других СУБД, — феноменальна. Не стоит и говорить, что ни Informix, ни Sybase не пытались регистрировать результаты в базе данных в ходе тестирования. Они предпочли записывать результаты в обычные файлы операционной системы.

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

Работая с Oracle, вы поймете, что:

• Транзакции — это хорошо, именно для их поддержки и создавались СУБД.

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

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

• Блокирование на уровне строк в Oracle не приводит к дополнительным расходам ресурсов.

• Никогда не нужно повышать уровень блокировки (например, блокировать таблицу вместо блокирования строк), поскольку "так лучше для системы". В Oracle для системы так лучше не будет: ресурсы при этом не экономятся.

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

 

Проблемы блокирования

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

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

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

1. Пользователь 1 выбирает (запрашивает) строку данных.

2. Пользователь 2 выбирает ту же строку.

3. Пользователь 1 изменяет строку, обновляет базу данных и фиксирует изменение.

4. Пользователь 2 изменяет строку, обновляет базу данных и фиксирует изменение.

Это называется потерянным изменением, поскольку все сделанные на шаге 3 изменения будут потеряны. Рассмотрим, например, окно редактирования информации о сотруднике, позволяющее изменить адрес, номер рабочего телефона и т.д. Само приложение — очень простое: небольшое окно поиска со списком сотрудников и возможность получить детальную информацию о каждом сотруднике. Проще некуда. Так что пишем приложение, не выполняющее никакого блокирования, — только простые операторы SELECT и UPDATE.

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

могло случиться? К сожалению, очень просто: другой пользователь (пользователь 2)

запросил ту же запись за 5 минут до того, как к ней обратился пользователь 1, и у него на экране отображались старые данные. Пользователь 1 запросил данные, изменил их, получил подтверждение изменения и даже выполнил повторный запрос, чтобы увидеть эти изменения. Однако затем пользователь 2 изменил поле номера рабочего телефона и щелкнул на кнопке сохранения, не зная, что переписал старые данные поверх внесенных пользователем 1 изменений адреса! Так может случиться потому, что разработчик

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

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

Эта проблема баз данных проявляется постоянно, когда разработчики графических интерфейсов, не имеющие достаточного опыта работы с базами данных, получают задание создать приложение для базы данных. Они получают общее представление об операторах SELECT, INSERT, UPDATE и DELETE и начинают писать программы.

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

Многие инструментальные средства, например Oracle Forms, автоматически защищают разработчиков от таких ситуаций, проверяя, не изменилась ли запись с момента запроса и заблокирована ли перед началом изменений. Но другие средства разработки (и обычные программы на языке VB или Java) такой защиты не обеспечивают. Как средства разработки, обеспечивающие защиту (за кадром), так и разработчики, использующие другие средства (явно), должны применять один из двух описанных ниже методов

блокирования.

 

Пессимистическое блокирование

Этот метод блокирования должен использоваться непосредственно перед изменением значения на экране, например, когда пользователь выбирает определенную строку с целью изменения (допустим, щелкая на кнопке в окне). Итак, пользователь запрашивает данные без блокирования:

> SELECT EMPNO, ENAME, SAL FROM EMP WHERE DEPTNO = 10;

EMPNO ENAME SAL

7782 CLARK 2450

7839 KING 5000

7934 MILLER 1300

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

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

SELECT EMPNO, ENAME, SAL 2 FROM EMP

3 WHERE EMPNO = :EMPNO

4 AND ENAME = :ENAME

5 AND SAL = :SAL

6 FOR UPDATE NOWAIT

7 /

EMPNO ENAME SAL

7934 MILLER 1300

Приложение передает значения для связываемых переменных в соответствии с данными на экране (в нашем случае —7934, MILLER и 1300) и повторно запрашивает ту же самую строку из базы данных, но в этот раз блокирует ее изменения другими сеансами. Вот почему такой подход называется пессимистическим блокированием. Мы блокируем строку перед попыткой изменения, поскольку сомневается, что она останется неизменной.

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

•Если данные не изменились, мы получим ту же строку сотрудника MILLER, на этот раз заблокированную от изменения (но не чтения) другими сеансами.

•Если другой сеанс находится в процессе изменения данной строки, мы получим сообщение об ошибке ORA-00054 Resource Busy (ресурс занят). Наш сеанс заблокирован и мы должны ждать, пока другой сеанс не завершит изменения строки.

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

После успешного блокирования строки приложение выполняет требуемые изменения и фиксирует их:

1 UPDATE EMP

2 SET ENAME = :ENAME, SAL = :SAL

3 WHERE EMPNO = .EMPNO;

1 row updated.

commit;

Commit complete.

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

 

Оптимистическое блокирование

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

Update table

Set column1 = :new_column1, column2 = :new_column2, ....

Where column1 = :old_column1

And column2 = :old_column2

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

Стоит заметить, что и в этом случае тоже можно использовать оператор SELECT FOR

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

Итак, какой же метод лучше? По моему опыту, пессимистическое блокирование очень хорошо работает в Oracle (но вряд ли так же хорошо подходит для других СУБД) и имеет много преимуществ по сравнению с оптимистическим.

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

изменения). Если сначала заблокировать строку, а затем изменять ее, пользователю будет удобнее работать. Если же пытаться изменить, не заблокировав заранее, пользователь может напрасно потерять время и силы на изменения, чтобы в конечном итоге получить сообщение: "Извините, данные изменились, попробуйте еще раз". Чтобы ограничить время блокирования строки перед изменением, можно снимать блокировку в

приложении, если пользователь занялся чем-то другим и некоторое время не использует строку, или использовать профили ресурсов (Resource Profiles) в базе данных для отключения простаивающих сеансов.

Более того, блокирование строки в Oracle не мешает ее читать, как в других СУБД;

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

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

потерянному изменению (логика работы приложения ошибочна, что и приводит к блокированию).

Блокирование в базе данных выполняют четыре основных оператора ЯМД: INSERT, UPDATE, DELETE и SELECT FOR UPDATE. Решение проблемы в последнем случае тривиально: добавьте конструкцию NOWAIT, и оператор SELECT FOR UPDATE больше не будет заблокирован. Вместо этого приложение должно сообщать пользователю, что строка уже заблокирована. Интерес представляют остальные три оператора ЯМД.

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

 

Заблокированные вставки

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

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

можно применить метод, описанный в приложении А при рассмотрении пакета DBMS_LOCK. Там я демонстрирую, как решить эту проблему, прибегнув к явному блокированию вручную.

 

Заблокированные изменения и удаления

В интерактивном приложении, которое запрашивает данные из базы, позволяет пользователю манипулировать ими, а затем возвращает их в базу данных, заблокированные операторы UPDATE или DELETE показывают, что в коде может быть проблема потерянного изменения. Вы пытаетесь изменить с помощью UPDATE строку, которую уже изменяет другой пользователь, другими словами, которая уже кем-то заблокирована. Этого блокирования можно избежать с помощью запроса SELECT FOR UPDATE

NOWAIT, позволяющего:

•проверить, не изменились ли данные с момента их прочтения (для предотвращения потерянного изменения);

•заблокировать строку (предотвращая ее блокирование другим оператором изменения или удаления).

Как уже упоминалось, это можно сделать независимо от принятого подхода —как при пессимистическом, так и при оптимистическом блокировании можно использовать оператор SELECT FOR UPDATE NOWAIT для проверки того, что строка не изменилась. При пессимистическом блокировании этот оператор выполняется в тот момент, когда пользователь выражает намерение изменять данные. При оптимистическом блокировании этот оператор выполняется непосредственно перед изменением данных в базе. Это не только решает проблемы блокирования в приложении, но и обеспечивает целостность данных.

 

Взаимные блокировки

Взаимные блокировки возникают, когда два сеанса удерживают ресурсы, необходимые другому сеансу. Взаимную блокировку легко продемонстрировать на примере базы данных с двумя таблицами, А и В, в каждой из которых по одной строке. Для этого необходимо начать два сеанса (скажем, два сеанса SQL*Plus) и в сеансе А изменить таблицу А. В сеансе Б надо изменить таблицу В. Теперь, если попытаться изменить таблицу А в сеансе Б, он окажется заблокированным, поскольку соответствующая строка уже заблокирована сеансом А. Это еще не взаимная блокировка —сеанс просто заблокирован. Взаимная блокировка еще не возникла, поскольку есть шанс, что сеанс А зафиксирует или откатит транзакцию и после этого сеанс Б продолжит работу.

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

update a set x = х+1

*

ERROR at line 1:

ORA-00060: deadlock detected while waiting for resource

Попытка сеанса А изменить таблицу В по-прежнему блокируется —сервер Oracle не откатывает всю транзакцию. Откатывается только один из операторов, ставших причиной возникновения взаимной блокировки. Сеанс Б по-прежнему удерживает блокировку строки в таблице В, а сеанс А терпеливо ждет, пока эта строка станет доступной. Получив сообщение о взаимной блокировке, сеанс Б должен решить, фиксировать ли уже

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

Для сервера Oracle взаимная блокировка —настолько редкий, необычный случай, что при каждом ее возникновении создается трассировочный файл. Содержимое трассировочного файла примерно таково:

*** 2001-02-23 14:03:35.041

*** SESSION ID:(8.82) 2001-02-23 14:03:35.001

DEADLOCK DETECTED

Current SQL statement for this session:

update a set x = x+1

The following deadlock is not an ORACLE error. It is a

deadlock due to user error in the design of an application

or from issuing incorrect ad-hoc SQL. The following.. .

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

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

случаях:

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

•при удалении строки в главной таблице подчиненная таблица также полностью блокируется (при отсутствии индекса по внешнему ключу).

Блокирование таблицы

С ограничивает возможность одновременной работы с базой данных, —любые изменения в ней становятся невозможными. Кроме того, увеличивается вероятность взаимного блокирования, поскольку сеанс в течение транзакции "владеет" слишком большим объемом данных. Вероятность того, что другой сеанс окажется заблокированным при

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

После индексирования соответствующего внешнего ключа взаимные блокировки и множество других конфликтов при доступе исчезают навсегда.

 

Эскалация блокирования

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

В СУБД Oracle никогда не применяется эскалация блокирования, однако выполняется преобразование блокировок (lock conversion) или распространение блокировок (lock promotion). Эти термины часто путают с эскалацией блокирования.

Термины "преобразование блокировок" и "распространение блокировок" —синонимы. В контексте Oracle обычно говорят о преобразовании.

Берется блокировка самого низкого из возможных уровней (наименее ограничивающая блокировка) и преобразуется к более высокому (ограничивающему) уровню. Например, при выборе строки из таблицы с конструкцией FOR UPDATE будет создано две блокировки. Одна из них устанавливается на выбранную строку (или строки); это — исключительная блокировка: ни один сеанс уже не сможет заблокировать соответствующие строки в исключительном режиме. Другая блокировка, ROW SHARE TABLE (совместное блокирование строк таблицы), устанавливается на соответствующую таблицу.

Это предотвратит исключительную блокировку таблицы другими сеансами и, следовательно, возможность изменения, например, структуры таблицы. Все остальные операторы смогут работать с таблицей. Другой сеанс может даже сделать таблицу доступной только для чтения с помощью оператора LOCK TABLE X IN SHARE MODE, предотвратив тем самым ее изменение. Однако этот другой сеанс не должен иметь права предотвращать изменения, которые уже происходят. Поэтому, как только будет выполнена

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

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

 

Типы блокировок

Ниже перечислены пять основных классов блокировок в Oracle. Первые три —общие (используются во всех базах данных Oracle), а две остальные —только в OPS (Oracle Parallel Server —параллельный сервер). Специфические блокировки OPS мы рассмотрим лишь вкратце, зато общие блокировки —очень подробно.

•Блокировки ЯМД (DML locks). ЯМД означает язык манипулирования данными (Data Manipulation Language), т.е. операторы SELECT, INSERT, UPDATE и DELETE. К блокировкам ЯМД относятся, например, блокировки строки данных или блокировка на уровне таблицы, затрагивающая все строки таблицы.

•Блокировки ЯОД (DDL locks). ЯОД означает язык определения данных (Data Definition Language), т.е. операторы CREATE, ALTER и так далее. Блокировки ЯОД защищают определения структур объектов.

•Внутренние блокировки (internal locks) и защелки (latches). Это блокировки, используемые сервером Oracle для защиты своих внутренних структур данных. Например, разбирая запрос и генерируя оптимизированный план его выполнения, сервер Oracle блокирует с помощью защелки библиотечный кэш, чтобы поместить в него этот план для использования другими сеансами. Защелка —это простое низкоуровневое средство обеспечения последовательности обращений, используемое сервером Oracle, и по функциям аналогичное блокировке.

•Распределенные блокировки (distributed locks). Эти блокировки используются сервером OPS для согласования ресурсов машин, входящих в кластер. Распределенные блокировки устанавливаются экземплярами баз данных, а не отдельными транзакциями.

•Блокировки параллельного управления кэшем (PCM —Parallel Cache Management Locks). Такие блокировки защищают блоки данных в кэше при использовании их несколькими экземплярами.

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

ТХ - блокировки транзакций

Блокировка ТХ устанавливается, когда транзакция инициирует первое изменение, и удерживается до тех пор, пока транзакция не выполнит оператор COMMIT или ROLLBACK. Она используется как механизм организации очереди для сеансов, ожидающих завершения транзакции. Каждая измененная или выбранная с помощью SELECT FOR UPDATE строка будет "указывать" на соответствующую блокировку ТХ. Казалось

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

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

1. Найти адрес строки, которую необходимо заблокировать.

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

3. Заблокировать список.

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

5. Создать в списке новую запись, фиксирующую факт блокирования строки.

6. Разблокировать список.

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

7. Снова подключиться к диспетчеру.

8. Заблокировать список блокировок.

9. Найти в списке и снять все установленные блокировки.

10. Разблокировать список.

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

1. Находит адрес строки, которую необходимо заблокировать.

2. Переходит на эту строку.

3. Блокирует ее (ожидая снятия блокировки, если она уже заблокирована и при этом не используется опция NOWAIT).

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

При блокировании строки данных в Oracle с блоком данных связывается идентификатор транзакции, причем остается там после снятия блокировки. Этот идентификатор уникален для нашей транзакции и задает номер сегмента отката, слот и номер изменения (sequence number). Оставляя его в блоке, содержащем измененную строку, мы как бы говорим другим сеансам, что "эти данные принадлежат нам" (не все данные в блоке, только одна строка, которую мы меняем). Когда другой сеанс обращается к блоку, он

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

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

 

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

хранения, задаваемыми в операторе CREATE при создании объекта:

•INITRANS —первоначальный, заранее заданный размер этой структуры. Стандартное значение: 2 —для индексов и 1 —для таблиц.

•MAXTRANS —максимальный размер, до которого может разрастаться эта структура. Стандартное значение: 255.

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

Блокировки ЯОД

Блокировки ЯОД автоматически устанавливаются на объекты в ходе выполнения операторов ЯОД для защиты их от изменения другими сеансами. Например, при выполнении оператора ЯОД ALTER TABLE Т на таблицу Т будет установлена исключительная блокировка ЯОД, что предотвращает установку блокировок ЯОД и ТМ на эту таблицу другими сеансами. Блокировки ЯОД удерживаются на период выполнения оператора ЯОД и снимаются сразу по его завершении. Это делается путем помещения операторов ЯОД в неявные пары операторов фиксации (или фиксации и отката). Вот почему операторы ЯОД в Oracle всегда фиксируются. Операторы CREATE, ALTER и т.д.

фактически выполняются, как показано в следующем псевдокоде:

Begin

Commit;

Оператор ЯОД

Commit;

Exception

When others then rollback;

End;

Поэтому операторы ЯОД всегда фиксируют транзакцию, даже если завершаются неудачно. Выполнение оператора ЯОД начинается с фиксации. Помните об этом. Сначала выполняется фиксация, чтобы в случае отката не пришлось откатывать предыдущую часть транзакции. При выполнении оператора ЯОД фиксируются все выполненные ранее изменения, даже если сам оператор ЯОД выполнен неудачно. Если должен быть выполнен оператор ЯОД, но не требуется, чтобы он зафиксировал существующую

транзакцию, можно использовать автономную транзакцию (подробнее см. в главе 15).

Имеется три типа блокировок ЯОД:

•Исключительные блокировки ЯОД. Они предотвращают установку блокировок ЯОД или ТМ (ЯМД) другими сеансами. Это означает, что можно запрашивать таблицу в ходе выполнения оператора ЯОД, но нельзя ее изменять.

•Разделяемые блокировки ЯОД. Они защищают структуру соответствующего объекта от изменения другими сеансами, но разрешают изменять данные.

•Нарушаемые блокировки разбора (breakable parse locks). Они позволяют объекту, например плану запроса, хранящемуся в кэше разделяемого пула, зарегистрировать свою зависимость от другого объекта. При выполнении оператора ЯОД, затрагивающего заблокированный таким образом объект, сервер Oracle получает список объектов, зарегистрировавших свою зависимость, и помечает их как недействительные. Вот почему эти блокировки —"нарушаемые": они не предотвращают выполнение операторов ЯОД.

Большинство операторов ЯОД устанавливает исключительную блокировку ЯОД. При выполнении оператора, подобного

Alter table t add new_column date;

таблица Т будет недоступна для изменения, пока оператор выполняется. К таблице в этот период можно обращаться с помощью оператора SELECT, но другие действия, в том числе операторы ЯОД, блокируются. В Oracle 8i некоторые операторы ЯОД теперь могут выполняться без установки блокировок ЯОД. Например, можно выполнить:

create index t_idx on t(x) ONLINE;

Ключевое слово ONLINE изменяет метод построения индекса. Вместо установки исключительной блокировки ЯОД, предотвращающей изменения данных, Oracle попытается установить на таблицу низкоуровневую (режим 2) блокировку ТМ. Это предотвращает изменения структуры с помощью операторов ЯОД, но позволяет нормально выполнять операторы ЯМД. Сервер Oracle достигает этого путем записи в таблице изменений, сделанных в ходе выполнения оператора ЯОД, и учитывает эти изменения в новом индексе, когда завершается его создание. Это существенно увеличивает доступность данных.

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

Create view MyView

As select * from emp, dept

where emp. deptno = dept.deptno;

Блокирование и одновременный доступ разделяемые блокировки ЯОД будут устанавливаться на таблицы ЕМР и DEPT на все время выполнения оператора CREATE VIEW. Мы можем изменять содержимое этих

таблиц, но не их структуру.

Последний тип блокировок ЯОД —нарушаемые блокировки разбора. Когда сеанс разбирает оператор, блокировка разбора устанавливается на каждый объект, упоминаемый в этом операторе. Эти блокировки устанавливаются, чтобы разобранный и помещенный в кэш оператор был признан недействительным (и выброшен из кэша в разделяемой памяти), если один из упоминаемых в нем объектов удален или изменена его структура.

При поиске этой информации особо ценным будет представление DBA_DDL_LOCKS.

(Ни одного походящего для этого представления V$ не существует). Представление DBA_DDL_LOCKS строится по более "мистическим" таблицам Х$ и по умолчанию не создается в базе данных. Для установки его и других представлений, связанных с блокировками, выполните сценарий CATBLOCK.SQL из каталога [ORACLE_HOME]/rdbms/admin. Этот сценарий можно успешно выполнить от имени пользователя SYS.

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

Защелки и внутренние блокировки (enqueues) —простейшие средства обеспечения очередности доступа, используемые для координации многопользовательского доступа к общим структурам данных, объектам и файлам.

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

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

ожидающих освобождения защелки —есть просто "толпа" пытающихся ее получить.

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

если владелец защелки "скоропостижно скончается", удерживая ее. Эта очистка обычно выполняется процессом PMON.

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

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

Блокирование вручную. Блокировки, определяемые пользователем

До сих пор мы рассматривали в основном блокировки, устанавливаемые сервером Oracle без нашего вмешательства. При изменении таблицы сервер Oracle устанавливает на нее блокировку ТМ, чтобы предотвратить ее удаление (как фактически и применение к ней большинства операторов ЯОД) другими сеансами. В изменяемых блоках оставляют блокировки ТХ —благодаря этому другие сеансы "знают", что с данными работают. Сервер использует блокировки ЯОД для защиты объектов от изменений по ходу

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

•блокирование данных вручную с помощью оператора SQL;

•создание собственных блокировок с помощью пакета DBMS_LOCK.

Рассмотрим, зачем могут понадобиться эти средства.

Мы уже описывали несколько случаев, когда может потребоваться блокирование

вручную. Основным методом явного блокирования данных вручную является использование оператора SELECT...FOR UPDATE. Мы использовали его в предыдущих примерах для решения проблемы потерянного изменения, когда один сеанс может переписать изменения, сделанные другим сеансом. Мы видели, что этот оператор используется для установки очередности доступа к подчиненным записям, диктуемой бизнес-правилами (вспомните пример с планировщиком ресурсов, приведенный в главе 1).

Данные можно также заблокировать вручную с помощью оператора LOCK TABLE.

На практике это используется редко в силу особенностей такой блокировки. Этот оператор блокирует таблицу, а не строки в ней. При изменении строк они "блокируются" как обычно. Так что это —не способ экономии ресурсов (как, возможно, в других реляционных СУБД). Оператор LOCK TABLE IN EXCLUSIVE MODE имеет смысл использовать при выполнении большого пакетного изменения, затрагивающего множество строк таблицы, и необходима уверенность, что никто не "заблокирует" это действие.

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

Создание собственных блокировок

Сервер Oracle открывает для разработчиков свои механизмы блокирования для обеспечения очередности доступа с помощью пакета DBMS_LOCK (который подробно описывается в приложении А). Может показаться странным, зачем вообще создавать собственные блокировки. Ответ обычно зависит от того, какое используется приложение.

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

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

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

 


Поделиться:

Дата добавления: 2015-09-14; просмотров: 462; Мы поможем в написании вашей работы!; Нарушение авторских прав


<== предыдущая лекция | следующая лекция ==>
 | Блокирование
lektsii.com - Лекции.Ком - 2014-2024 год. (0.007 сек.) Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав
Главная страница Случайная страница Контакты