03июн
Управление одновременным доступом, согласованность
Проблема одновременного доступа возникает только в многопользовательской среде. В чем состоит эта проблема?
Как в ORACLE обеспечивается одновременный доступ и достоверность данных? Что такое многоверсионность, где же храняться версии данных,согласованность ( согласованность на уровне запроса, согласованность на уровне транзакции), уровни изолированности транзакций? Основные вопросы, поднимаемые в этой статье и попытка на них ответить.
В многопользовательской базе данных в нескольких одновременно выполняющихся транзакциях могут обновляться одни и те же данные. Для конкурирующих транзакций необходимо обеспечить тот же эффект, как если бы эти транзакции выполнялись последовательно. То есть должна быть создана иллюзия того, что каждый из пользователей работает с БД в одиночку. Поэтому задача СУБД состоит в управление соперничеством и согласованностью данных:
- соперничество - необходимо координировать одновременный доступ к данным со стороны пользователей.
- согласованность - пользователь всегда видит согласованный образ всех данных.
В СУБД ORACLE одновременный доступ и достоверность информации обеспечивается использованием блокировок, транзакций и многоверсионной модели согласованности данных. О блокировках и транзакциях можно почитать в других статьях. Здесь же будет обращено внимание в основном на понятие многоверсионности.
Многоверсионность в контексте СУРБД ORACLE означает одновременную поддержку множества версий данных. Как в ORACLE реализована многоверсионность? Для этого рассмотрим некоторые механизмы.
СУРБД ORACLE обеспечивает согласованность по чтению на двух различных уровнях: на уровне команды и на уровне транзакции.
Согласованность по чтению на уровне запроса.
ORACLE всегда обеспечивает согласованность по чтению на уровне запроса и при этом данные НИКОГДА не блокируются для чтения другими процессами (это фундаментальное отличие oracle от других баз данных). Гарантируется, что данные, возвращаемые запросом, согласованы по отношению к моменту начала этого запроса. Запрос никогда не видит никаких изменений, произведенных транзакциями, которые подтверждены в ходе выполнения данного запроса. Как это обеспечивается? ORACLE запоминает текущий SCN (системный номер изменения) как только запрос начинает выполняться. Далее запросу доступны только те данные, которые были зафиксированы к моменту запомненного SCN. Операторы SELECT, INSERT с запросом, UPDATE и DELETE, всегда опрашивают данные (явный или неявный запрос на выборку) и все они получают для обработки согласованное множество данных. Замечание: запросы, используемые операторами INSERT, UPDATE и DELETE, получают согласованное множество данных, но они не видят изменений, осуществляемых самими этими операторами.
Согласованность по чтению на уровне транзакции.
В ORACLE предусмотрена необязательная возможность согласованности по чтению на уровне транзакции, которая гарантирует, что данные, которые видят ВСЕ запросы внутри одной и той же транзакции, согласованы по отношению к одной точке времени (моменту начала транзакции). Методы, которыми ORACLE достигает этого:
- транзакции read-only . Транзакции read-only (только-чтение) содержат только запросы на выборку и не содержат никаких предложений DML. ORACLE запоминает момент начала транзакции. В течение транзакции ей доступны только подтвержденные к началу транзакции данные.
- монопольные блокировки таблиц и строк. Если в транзакции присутствуют операторы DML, то необходимо явно запросить разделяемые блокировки по таблицам или монопольные блокировки по тем строкам, которые будут считываться неоднократно.
- Установка уровня изолированности транзакций в serializable (об этом ниже).
Таким образом, ORACLE на конкретный момент времени для команды или транзакции предоставляет согласованную версию данных.
Где и как хранятся версии данных?
Для этого используются сегменты отката и особый механизм выполнения транзакций. Почитаем Тома Кайта:
При любом изменении данных Oracle создает записи в двух разных местах. Одна запись попадает в журналы повторного выполнения, где Oracle хранит информацию, достаточную для повторного выполнения, или "наката", транзакции. Для оператора вставки это будет вставляемая строка. Для оператора удаления это будет запрос на удаление строки в слоте X блока Y файла Z. И так далее. Другая запись — это запись отмены, помещаемая в сегмент отката. Если транзакция завершается неудачно и должна быть отменена, СУБД Oracle будет читать "предварительный" образ из сегмента отката, восстанавливая необходимые данные. Помимо отмены транзакций, СУБД Oracle использует сегменты отката для отмены изменений в блоках при их чтении, то есть для восстановления данных блока на момент начала выполнения запроса. Это позволяет читать данные, несмотря на блокировку и получать корректные, согласованные результаты, не блокируя данные.
Вывод: в базе данных имеется несколько версий одной и той же информации, по состоянию на различные моменты времени. СУБД Oracle использует эти сделанные в разное время "моментальные снимки" данных для поддержки согласованности по чтению и неблокируемости запросов.
Запрос на чтение в Oracle никогда не блокируется, он никогда не станет причиной взаимной блокировки с другим сеансом и никогда не даст результат, не существующий в базе данных.
Чтобы обеспечить такую красоту ORACLE должен обеспечить следующие непростые процессы: хранение версий объектов данных; очистка устаревших версий объектов данных; затраты по актуализации запрашиваемых данных на чтение; принудительный откат транзакций в случае неактуальности считанных данных.
Теперь самое время поговорить о таком понятии как уровень изолированности транзакций, определенном стандартом SQL92.
В ANSI SQL-92 вводятся четыре уровня изолированности транзакций.
Эти четыре уровня изолированности определяются своим отношением к таким явлениям:
- неповторяемые чтения (фантомы). В процессе одной и той же транзакции, два идентичных запроса возвращают различные данные, так как изменения, произведенные другими подтвержденными транзакциями, становятся доступными. То есть критериям запроса стало удовлетворять больше данных, чем раньше. (Р3)
- невоспроизводимость при чтении. Если строка читается в момент времени Т1 , затем перечитывается в момент времени Т2, то за это время строка могла измениться: могла быть удалена или обновлена. (Р2)
- грязное чтение. Суть состоит в том, что допускается чтение незафиксированных (грязных) данных. (Р1)
- Сериализуемость определяется как режим выполнения операций конкурирующих транзакций, дающий в точности тот же результат, что и последовательное выполнение этих транзакций в некотором порядке.
Следующая таблица однозначно разграничивает уровни изолированности транзакций.

Сервер ORACLE явно поддерживает уровни изолированности READ COMMITED(достигается согласованность по чтению) и SERIALIZABLE.
Рассмотрим каждый уровень изолированности и покажем, какие все СУБД плохие, а только ORACLE хороший (розовый и пушистый).
READ UNCOMMITED
Уровень изолированности READ UNCOMMITED разрешает грязное чтение. Сервер Oracle не использует грязного чтения, и не допускает его.
Для чего нужен этот уровень? Для того, чтобы обеспечит неблокирующее чтение.
Но мы знаем, что фундаментальным свойством ORACLE есть согласованное неблокирующее чтение. То есть в ORACLE происходит так: когда запрос на чтение натыкается на заблокированную запись, которая изменяется другой транзакцией, oracle восстанавливает необходимую запись на момент начала выполнения нашего запроса и выдает результат не ожидая, когда транзакция, которая изменяет данные, будет зафиксирована. То есть блокировка просто будет обойдена.
Другие же СУБД, чтобы транзакции на чтение не зависали от блокировок, вынуждены допускать чтение незафиксированных данных. То есть большая вероятность получения недостоверных данных.
READ COMMITED
Этот уровень изолированности требует, чтобы транзакция читала только данные, зафиксированные до её начала. Не допускается грязное чтение, но разрешены чтения фантомов и невоспроизводимость чтения. Многоверсионность и согласованность запросов по чтению позволяют ораклу получить один и тот же результат как при уровне изолированности READ UNCOMMITTED так и при READ COMMITED. Просто ORACLE, в случае необходимости, из сегментов отката восстановит на соответствующий момент времени изменённые данные. В других же СУБД необходимо ждать снятия блокировки и к тому же могут быть прочитаны данные, изменённые и зафиксированные другими транзакциями в момент времени после начала чтения.
REPEATABLE READ
Этот уровень изолированности требует согласованности, корректности данных без потери изменений.В ORACLE мы получаем данные, согласованные на момент начала запроса. В других СУБД обычно получают согласованный результат на момент завершения запроса. В этих системах сеансы на чтение блокируют сеансы на запись и сеансы на запись блокируют сеансы на чтение. А все из-за использования разделяемых блокировок на чтение. ORACLE же никогда не использует разделяемые блокировки на чтение.
Предотвратить потерю изменений - это значит, добиться того, чтобы повторное чтение строки в продолжение транзакции давало один и тот же результат.
В других СУБД этого добиваются путем разделяемых блокировок на чтение и select for update. Но это чревато взаимными блокировками.
В ORACLE это можно добиться путем упорядочивания доступа к таблице с помощью select for update nowat или установить уровень изолированности serializable.
SERIALIZABLE
Это самая высокая степень изолированности, имитация однопользовательской среды. Для транзакции база данных выглядит как моментальный снимок. Все изменения проведенные другими транзакциями не видны. В Oracle это реализовано путем распространения согласованности по чтению на уровне оператора на уровень транзакции. То есть из сегментов отката ORACLE восстанавливает данные не на начало оператора, а на начало транзакции. Но есть один неприятный момент: при попытке обновления строки измененной после начала транзакции будет получена ошибка ORA-08177. Это все потому, что ORACLE при этом уровне изолированности придерживается оптимистического подхода: предполагается, что данные, которые будет изменять наша транзакция, никто не изменит в процессе нашей транзакции.
Уровень изолированности serializable повысит вероятность неудачного завершения транзакции при увеличении ее продолжительности. Он подходит как раз для сред с большой частотой транзакций, когда они короткие и выполняются быстро, читая и изменяя данные. Такой уровень изолированности не подходит для многих систем.
Транзакции только для чтения
Достаточно часто необходимо пользователю сформировать какой-то отчет по данным согласованным по чтению на определенный момент. Другие СУБД для этого используют уровень REPEATABLE READ и получая дополнительно нежелательные последствия разделяемого блокирования по чтению. В ORACLE для таких ситуаций используются транзакции только для чтения. При таком виде транзакции не возникнет ошибки ORA-08177, но будет в некоторых случаях (слишком долго формируете отчет) ошибка ORA-1555.