|
Программирование >> Oracle
чем одному лицу на любой период времени. То есть, приложение содержало специальный код, который проверял, что никто из пользователей не затребовал ресурс на тот же период времени (по крайней мере разработчик думал, что его код это проверяет). Код обращался к таблице планов и, если в ней не было строк с перекрывающимся временным интервалом, вставлял в нее новую строку. Итак, разработчик просто работал с парой таблиц: create table resources(resource name varchar2(25) primary key, . ..) ; create table schedules(resource name varchar2(25) references resources, start time date, end time date); И прежде чем зарезервировать на определенный период, скажем, учебный класс, приложение выполняло запрос вида: select count(*) from schedules where resource name = :room name and (start tiroe between :new start time and :new end time or end time between :new start time and :new end time) Он казался разработчику простым и надежным: если возвращено значение 0, учебный класс можно занимать; если возвращено ненулевое значение, значит, учебный класс на этот период уже кем-то занят. Ознакомившись с используемым алгоритмом, я подготовил простой тест, показывающий, какая ошибка будет возникать при реальной эксплуатации приложения. Эту ошибку будет крайне сложно обнаружить и тем более установить ее причину, - кому-то может даже показаться, что это ошибка СУБД. Я предложил его коллеге сесть за соседний компьютер, перейти на тот же экран и попросил на счет три обоих нажать на кнопку Go и попытаться зарезервировать класс на одно то же время. Оба смогли это сделать - то, что прекрасно работало в изолированной среде, не сработало в среде многопользовательской. Проблема в этом случае была вызвана неблокирующим чтением в Oracle. Ни один из сеансов не блокировал другой. Оба сеанса просто выполняли представленный выше запрос и применяли алгоритм резервирования ресурса. Они оба могли выполнять запрос, проверяющий занятость ресурса, даже если другой сеанс уже начал изменять таблицу планов (это изменение невидимо для других сеансов до фиксации, то есть до тех пор, когда уже слишком поздно). Поскольку сеансы никогда не пытались изменить одну и ту же строку в таблице планов, они никогда и не блокировали друг друга, вследствие чего бизнес-правило не срабатывало так, как ожидалось. Разработчику необходим метод реализации данного бизнес-правила в многопользовательской среде, способ, гарантирующий что в каждый момент времени только один сеанс резервирует данный ресурс. В данном случае решение состояло в программном упорядочении доступа - кроме представленного выше запроса count(*), необходимо было сначала выполнить: select * from resources where resource name = :room name FOR UPDATE; Ранее в этой главе рассматривался пример, когда использование конструкции FOR UPDATE приводило к проблемам, но в этом случае она обеспечивает корректную ра- боту бизнес-правила. Мы просто блокируем ресурс (учебный класс), использование которого планируется непосредственно перед его резервированием, до выполнения запроса к таблице планов, выбирающего строки для данного ресурса. Блокируя ресурс, который мы пытаемся зарезервировать, мы гарантируем, что никакой другой сеанс в это же время не изменяет план использования ресурса. Ему придется ждать, пока наша транзакция не будет зафиксирована - после этого он сможет увидеть сделанное в ней резервирование. Возможность перекрытия планов, таким образом, устранена. Разработчик должен понимать, что в многопользовательской среде иногда необходимо использовать те же приемы, что и при многопотоковом программировании. В данном случае конструкция FOR UPDATE работает как семафор. Она обеспечивает последовательный доступ к конкретной строке в таблице ресурсов, гарантируя, что два сеанса одновременно не резервируют ресурс. Этот подход обеспечивает высокую степень параллелизма, поскольку резервируемых ресурсов могут быть тысячи, а мы всего лишь гарантируем, что сеансы изменяют конкретный ресурс поочередно. Это один из немногих случаев, когда необходимо блокирование вручную данных, которые не должны изменяться. Требуется уметь распознавать ситуации, когда это необходимо, и, что не менее важно, когда этого делать не нужно (пример, когда не нужно, приведен далее). Кроме того, такой прием не блокирует чтение ресурса другими сеансами, как это могло бы произойти в других СУБД, благодаря чему обеспечивается высокая масштабируемость. Подобные проблемы приводят к масштабным последствиям при переносе приложения с одной СУБД на другую (к этой теме я вернусь чуть позже), которых разработчикам сложно избежать. Например, при наличии опыта разработки для другой СУБД, в которой пишущие сеансы блокируют читающих, и наоборот, разработчик может полагаться на это блокирование как защищающее от подобного рода проблем - именно так все и работает во многих СУБД, отличных от Oracle. В Oracle приоритет отдан одновременности доступа, и необходимо учитывать, что в результате все может работать по-другому. В 99 процентах случаев блокирование выполняется незаметно, и о нем можно не заботиться. Но оставшийся 1 процент надо научиться распознавать. Для решения этой проблемы нет простого списка критериев типа в таком-то случае надо сделать то-то . Нужно понимать, как приложение будет работать в многопользовательской среде и что оно будет делать в базе данных. Многовариантность Эта тема очень тесно связана с управлением одновременным доступом, поскольку создает основу для механизмов управления одновременным доступом в СУБД Oracle - Oracle использует модель многовариантной согласованности по чтению при одновременном доступе. В главе 3 мы более детально рассмотрим технические аспекты многовариантности, но по сути это механизм, с помощью которого СУБД Oracle обеспечивает: согласованность по чтению для запросов: запросы в1дают согласованные результаты на момент начала их выполнения; неблокируем1е запросы: запросы не блокируются сеансами, в котор1х изменяются данные, как это бывает в других СУБД.
18 rows selected. В этом примере мы создали тестовую таблицу Т и заполнили ее данными из представления ALLUSERS. Мы открыли курсор для этой таблицы. Мы не выбирали дан-Hie с помощью этого курсора, просто открыли его. Помните, что при открытии курсора сервер Oracle не отвечает на запрос; он никуда не копирует данные при открытии курсора (представьте, сколько времени потребовало бы открытие курсора для таблицы с миллиардом строк в противном случае). Курсор просто открывается и дает результаты запроса по ходу обращения к данным. Другими словами, он будет читать данные из таблицы при извлечении их через курсор. Это две очень важные концепции СУБД Oracle. Термин многовариантность произошел от того, что фактически СУБД Oracle может одновременно поддерживать множество версий данных в базе данных. Понимая сущность многовариантности, всегда можно понять результаты, получаемые из базы данных. Наиболее простой из известных мне способов продемонстрировать многовариантность в Oracle: txyte@TKYTE816> create table t 2 as 3 select * from all users; Table created. tkyte@TKYTE816> variable x refcursor tkyte@TKYTE816> begin 2 open :x for select * from t; 3 end; PL/SQL procedure successfully completed. tkyte@TKYTE816> delete from t; 18 rows deleted. txyte@TKYTE816> commit; Commit complete. tkyte@TKYTE816> print x
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0.001
При копировании материалов приветствуются ссылки. |