46333 (762282)
Текст из файла
Проблемы совместного доступа к данным в Oracle
Олег Елманов
Чем больше количество одновременно работающих с базой данных пользователей, тем больше вероятность конфликта одновременного редактирования одной и той же строки. Что делать серверу, если два пользователя одновременно пытаются обновить одну и ту же запись? Можно принять то изменение, которое пришло позже, но тогда один из пользователей будет видеть у себя некорректные данные. А хуже всего - он будет думать, что все в порядке. Как разрешить подобные проблемы?
Допустим, что два пользователя открыли для редактирования форму с одним и тем же документом. Первый пользователь изменяет важные параметры, цены, количество и нажимает «сохранить». Другой пользователь не видит этих изменений, потому что он получил данные раньше. При этом он изменяет некий незначительный параметр, например, дату, и тоже нажимает сохранение. Незначительное изменение второго пользователя перезаписывает важные изменения первого.
В многопользовательских приложениях к программированию можно поступать несколькими способами:
1. Кто последний, тот и прав. В этом случае вы просто реализуете логику программы и не заботитесь о том, что два пользователя могут одновременно изменять какие-то данные. Прав будет тот, кто чуть позже нажмет кнопку обновления.
2. Попытаться реализовать «одновременную» работу собственными средствами, с помощью журналов — если в журнале есть запись, что кто-то открыл документ, но не закрыл, то не разрешать повторное открытие другим пользователям. Может быть, где-то это будет удобно и быстро, но в Oracle реализованы хорошие встроенные средства блокировок, которые работают быстрее, эффективнее и надежнее, поэтому данный метод мы не рекомендуем к использованию.
3. Блокировать записи, которые пользователь собирается изменять, средствами базы данных. Заблокированную запись невозможно изменить, поэтому все запросы на редактирование будут отклоняться. Этот подход является более правильным и именно ему посвящена данная статья.
Заблокированные средствами Oracle записи может изменить только тот пользователь, который установил блокировку. Остальные могут только просматривать данные и не могут выполнять UPDATE или DELETE.
Блокировка
Блокировка—это механизм базы данных, с помощью которого сервер удерживает определенные ресурсы за определенным пользователем. Остальные пользователи могут только читать заблокированные данные. Oracle достаточно интеллектуален и блокирует данные на необходимом уровне. Если изменению подвергается только одна строка, то только она и будет удержана (другие базы данных могут блокировать данные целыми страницами, а в одной странице может быть несколько строк, и все они становятся недоступными для редактирования).
Блокировки бывают явными и неявными. Неявные создаются сервером без нашего участия при каждом изменении данных таблицы или структуры и снимаются по завершению выполнения оператора. Такие блокировки существуют только во время выполнения операции модификации данных. Явные блокировки задаются пользователем и могут быть созданы на этапе выбора данных. В данном случае вы явно указываете, что определенный ресурс должен быть закреплен за вами до тех пор, пока вы его не отпустите.
Не стоит бояться блокировок, потому что в Oracle они никак не сказываются на производительности системы. Они лишь говорят о том, что какие-то данные взяты определенным пользователем для редактирования.
FOR UPDATE
Когда мы просто используем оператор SELECT для выборки данных, сервер выполняет наш запрос без блокирования каких-либо записей. Но если необходима выборка данных непосредственно для редактирования, то мы должны сообщить серверу о блокировке. Для этого в конец запроса необходимо добавить FOR UPDATE. Например, следующий запрос выбирает все записи из таблицы Users для редактирования:
SELECT * FROM Users FOR UPDATE
Этот запрос ужасен, но он является только примером. Дело в том, что запрос выбирает все записи из таблицы, а значит, все они будут заблокированы для других пользователей. Никогда так не поступайте. Если вам необходимо изменить всю таблицу, то можете сразу выполнять оператор UPDATE в определенной транзакции,—выбирать данные тут не имеет смысла. Если хотя бы одна строка окажется закрепленной за каким-то пользователем, то оператор UPDATE не пройдет и блокировка не поможет. Заблокировать таблицу можно еще с помощью оператора LOCKTABLE, но лучше все же выбирать с помощью запроса SELECT только те данные, которые нужны, и при этом указывать ключевые слова FOR UPDATE. Чаще всего работа с данными построена по принципу «окно реестра-окно редактирования». Например, у вас есть окно реестра документов, где пользователи могут просматривать счета, накладные и т.д. за определенный период времени. В этом окне происходит только просмотр, поэтому для выборки данных здесь не следует использовать блокировки, иначе это приведет к проблемам при многопользовательской работе. Если один пользователь выберет все документы за месяц, то остальные не смогут открыть данные за тот же период.
Листинг 1
rocedure TSorneDocurnent. ForrnShow (Sender: TObject); var
oldSql : String; begin
// сохраняем запрос и добавляем операторы блокировки
oldSql:=odsDocs. SQL. Text;
OdsDocs.SQL.AddO FOR UPDATE NOWAIT');
try
// пытаемся открыть набор данных
odsDocs.open;
odsDocs.Readonly :=false;
// проверяем, найден ли документ
if odsDocs.RecordCount=0 then
begin
Showmes sage ('Документ не найден, пока вы думали, его уже удалили') ;
Close;
exit;
end;
except
// документ заблокирован, поэтому открываем его только для чтения
odsDocs.SQL.Text:=oldSql;
Showmessage ('Документ заблокирован другим пользователем, открываем только для чтения');
odsDocs.open;
odsDocs.Readonly:=true;
end; end;
Когда пользователь решит отредактировать какой-либо документ, следует открыть отдельное окно, в котором будет выбран именно этот документ и на него будет установлена блокировка. Например:
SELECT *
FROM Docs
WHERE PrimaryKey=10
FOR UPDATE
В этом примере мы выбираем и блокируем запись из таблицы Docs с первичным ключом, равным 10. Блокировка будет поставлена только на одну запись и этот документ больше никто не сможет открыть. Так как в окне реестра документов выполняется запрос SELECT без FOR UPDATE, то он продолжит работать, и остальные пользователи смогут его просматривать и открывать для редактирования другие незаблокированные документы.
Не ждите!
А что произойдет, если пользователь попытается открыть документ, который уже заблокирован другим пользователем? Ответ прост—запрос зависнет в ожидании освобождения ресурсов. Если в вашей программе не предусмотрено возможности прерывания запросов, а блокировка оказалась мертвой, то программа зависнет навечно. Завершить работу можно будет только прерыванием процесса. Самое страшное, если какой-то пользователь открыл окно и ушел на обед. Ресурс оказывается заблокированным надолго, и это мешает работе других пользователей.
Если процесс прерывается аварийно, то и все заблокированные этим пользователем ресурсы блокируются. Чтобы их освободить, необходимо подключиться к серверу с правами системного администратора и завершить сессии.
Чтобы сессия не зависла из-за бесконечного ожидания заблокированных данных, я рекомендую добавлять еще опцию NOWAIT:
SELECT *
FROM Docs
WHERE PrimaryKey=10
FOR UPDATE NOWAIT
Такой запрос попытается получить данные и установить на них блокировку, но если это невозможно, то ожидания не будет. Сервер просто вернет ошибку с номером ORA-00054:
ORA-00054 Resource busy and acquire
with NOWAIT specified
Теперь, когда мы увидели, что данные заблокированы, можно показать пользователю сообщение о том, что кто-то уже редактирует таблицу, и открыть карточку документа, но только в режиме редактирования. Для этого нужно снова выполнить запрос SELECT без попытки блокирования ресурсов.
Пример
Давайте посмотрим, как реализовать возможность открытия карточки редактирования с использованием блокировок на Delphi. Допустим, у нас есть форма TSomeDocument для редактирования и данные выбираются с помощью компонента TOracleDataSet (назовем его odsDocs) из состава DOA (Direct Oracle Access, прямой доступ к Oracle). В компоненте odsDocs прописан запрос на выборку данных без каких-либо блокировок. По событию OnShow для формы пишем код, показанный в листинге! Разберем содержимое представленного листинга. Сначала сохраняем запрос, который прописан в компоненте, а затем добавляем к запросу опции FOR UPDATE NOWAIT. Теперь открываем набор данных внутри блока try... except. Если код отработал нормально, то ресурс свободен и уже заблокирован нами. Нужно только проверить количество записей на 0. А вдруг, пока мы работали с выборкой в реестре документов, этот документ уже кто-то удалил?
Если во время открытия набора данных произошла ошибка из-за блокировки, то выполнение программы переходит на блок except. Здесь возвращаем сохраненный запрос в компонент odsDocs, сообщаем пользователю, что данные невозможно открыть для редактирования, и открываем набор данных, но уже без опции FOR UPDATE NOWAIT.
Это достаточно простой, но эффективный способ блокирования документов.
Блокировки в связанных запросах
Допустим, что у нас есть две таблицы Docs и Users. В таблице Docs есть поле UserlD, где сохраняется первичный ключ из таблицы Users. Таким образом, каждый документ привязан к определенному пользователю, например, создавшему, ответственному или кому-то еще. Посмотрим, как будет выглядеть запрос на выборку данных для редактирования:
SELECT *
FROM Docs d, Users u WHERE d.PrimaryKey=10 AND d.UserlD =u.PrimaryKey FOR UPDATE
В результате блокировка будет установлена не только на выбранный документ под номером 10, но и на запись в таблице Users, которая связана с данным документом. Это очень плохо. Теперь, если кто-то другой попытается открыть на редактирование другой документ, но тоже связанный с этим пользователем, то сервер не даст этого сделать. Все документы пользователя будут заблокированы, а это неправильно. Блокироваться должен только определенный документ, а таблица пользователей не будет редактироваться (из нее только выбирается запись), и ее сервер не должен трогать.
Как сообщить Oracle, что записи в Users блокировать нельзя? Для этого нужно явно указать таблицу, а лучше—первичный ключ в этой таблице: FOR UPDATE OF имя поля. После ключевого слова OF указывается поле, по которому сервер узнает, какую запись из связанных таблиц нужно заблокировать. Итак, наш запрос должен выглядеть следующим образом:
SELECT *
FROM Docs d, Users u WHERE d.PrimaryKey=10 AND d.UserlD =u.PrimaryKey FOR UPDATE OF d.PrimaryKey
Вот теперь будет заблокирована только одна запись документа и только из таблицы Docs.
Продолжительность
Чтобы пользователи не блокировали данные надолго, при открытии формы можно запускать таймер и через пять минут запрашивать подтверждения продолжения работы. Если пользователь не подтвердит, то форма должна закрыться и освободить ресурс. Это поможет в тех случаях, если кто-нибудь забывчивый уйдет на обед или домой, оставив запущенную программу и открытые ресурсы. Если у вас многооконная система и предусмотрена возможность открытия сразу множества документов, то пользователь может забывать закрывать окна редактирования, что опять-таки приведет к лишним, а главное — неоправданным блокировкам. Не помешало бы средство, отключающее таймер нате случаи, когда пользователь действительно хочет работать с данными долго и должен делать это осознано.
Система
Теперь поговорим о системных представлениях, с помощью которых вы можете управлять и контролировать блокировки. Все блокировки можно получить с помощью представления v$lock:
SELECT * FROM v$lock
Результат не очень информативен, потому что содержит какие-то адреса и цифры, да и записей очень много. В поле sid находиться идентификатор сессии, а в поле Туре можно увидеть тип блокировки. Когда вызывается SELECT FOR UPDATE, то создается блокировка транзакции, а в поле Туре можно увидеть ТХ. Существуют и другие типы блокировки, например, блокировка сервера, изменение структуры таблиц и т.д. Более подробно об этом можно прочитать в документации по Oracle.
Исходя из вышесказанного, более информативным будет следующий запрос:
SELECT s.username, 1.* FROM v$lock 1, v$session s WHERE l.TYPE = 'TX' and l.sid=s.sid
Здесь мы связались с представлением v$session, которое возвращает сессии, и теперь в результат попадает имя пользователя, который удерживает блокировку. Из представления v$session можно получить много полезной информации. Просто выполните следующий запрос, чтобы определиться, какие еще поля можно включить в запрос, показанный выше:
SELECT *
FROM v$session
Характеристики
Тип файла документ
Документы такого типа открываются такими программами, как Microsoft Office Word на компьютерах Windows, Apple Pages на компьютерах Mac, Open Office - бесплатная альтернатива на различных платформах, в том числе Linux. Наиболее простым и современным решением будут Google документы, так как открываются онлайн без скачивания прямо в браузере на любой платформе. Существуют российские качественные аналоги, например от Яндекса.
Будьте внимательны на мобильных устройствах, так как там используются упрощённый функционал даже в официальном приложении от Microsoft, поэтому для просмотра скачивайте PDF-версию. А если нужно редактировать файл, то используйте оригинальный файл.
Файлы такого типа обычно разбиты на страницы, а текст может быть форматированным (жирный, курсив, выбор шрифта, таблицы и т.п.), а также в него можно добавлять изображения. Формат идеально подходит для рефератов, докладов и РПЗ курсовых проектов, которые необходимо распечатать. Кстати перед печатью также сохраняйте файл в PDF, так как принтер может начудить со шрифтами.















