При закрытии подписчики были переданы в рассылку "СУБД Oracle "с нуля"" на которую и рекомендуем вам подписаться.
Вы можете найти рассылки сходной тематики в Каталоге рассылок.
Информационный Канал Subscribe.Ru |
Этот выпуск посвящен оптимистическому блокированию как способу предотвращения потерянных изменений, в частности, в Web-приложениях. К этой теме Том Кайт последний рраз возвращался в апреле 2003 года.
В web-приложениях обычно несколько строк выбирается для показа и обработки пользователю, который выбирает одну из них и изменяет в ней поле. В результате, приложение должно вызвать процедуру, изменяющую соответствующий столбец данной строки. Обычно я пишу процедуру, изменяющую ВСЕ столбцы строки с данным значением первичного ключа. Но в web-приложении нельзя обеспечить надежное хранение значений всех столбцов для сотен строк, поэтому к моменту изменения базы данных не все значения могут быть доступны. Не знаете ли вы, как обойти эту проблему? Не хотелось бы писать отдельную процедуру изменения для каждого столбца и использовать динамический SQL...
Ну, здесь придется идти на компромисс.
Можно написать одну процедуру со статическим sql для изменения строк следующим образом:
create procedure update_t(p_ts in ..., p_pk in ..., p_c1 in ..., p_c2 in ..., ...) as begin update t set c1 = nvl(p_c1,c1), c2 = nvl(p_c2,c2), ..., ts = ts+1 where pk = p_pk AND ts = p_ts; /* Про ts / p_ts я объясню чуть позже */ if (sql%rowcount = 0) then raise_application_error(-20001, 'Жаль, но вам не повезло - эта запись кому-то понадобилась, и ее уже изменили'); end if; end;
Или можно строить запрос динамически, как показано ниже:
as begin if (p_c1 is not null) then dbms_session.set_context('my_ctx', 'c1', p_c1); l_set_clause := l_set_clause || 'c1 = sys_context(''my_ctx'',''c1''),' end if; ... execute immediate 'update t set ' || l_set_clause || ' ts = ts+1 ' || ' where pk = :pk and ts = :ts' using p_pk, p_ts; if ... end;
Первый подход упрощает код, но приводит к генерации намного большего объема данных отмены (UNDO).
Второй подход - сложнее для программирования, но изменяется только то, что необходимо.
Проблема обоих подходов - невозможность установить столбцу значение NULL, так что, если это необходимо, логику работы придется изменить.
Столбец TS - временнАя отметка (timestamp). Она необходима для реализации оптимистического блокирования при одновременном доступе. При получении строки необходим перичный ключ И текущая "временная отметка". Это должен быть своего рода счетчик. При обновлении строки необходимо убедиться, что этого еще никто не сделал! Иначе возникают многочисленные проблемы потерянных изменений.
Вы пишете:
"Столбец TS - временнАя отметка (timestamp). Она необходима для реализации оптимистического блокирования при одновременном доступе. При получении строки необходим перичный ключ И текущая "временная отметка". Это должен быть своего рода счетчик. При обновлении строки необходимо убедиться, что этого еще никто не сделал! Иначе возникают многочисленные проблемы потерянных изменений."
Не могли бы вы объяснить это подробнее?
Я этого не понимаю...
При измении таблицы столбца временной ометки нет... Откуда он берется? Вы используете
его в конструкциях set и where...
Что такое проблема потерянного изменения?
Сценарий потерянного изменения:
Момент времени t1: вы читаете строку
Момент времени t2: ваш коллега читает ту же строку
Момент времени t3: вы изменяете строку, поменяв только столбец ADDRESS, но записываете в таблицу
всю строку в целом
Момент времени t4: ваш коллега изменяет столбец PHONE, но записывает в таблицу
всю строку в целом, поэтому выполненное вами изменение теряется.
Это классическая проблема баз данных, с мометна их появления.
Временная отметка - это столбец, который добавляете ВЫ. Это може быть число, дата, все, что вам угодно.
Момент времени t1: вы читаете строку с временной отметкой "X"
Момент времени t2: ваш коллега читает ту же строку с временной отметкой "X"
Момент времени t3: вы изменяете строку, проверяя, что она все еще имеет временную отметку "X".
При изменении вы меняете временную отметку на "Y"
Момент времени t4: ваш коллега пытается изменить строку, но поскольку временная отметка в ней -
"Y", а не "X", то он не может этого сделать и не делает. Ему не повезло.
Если у вас есть моя книга "Expert One On One Oracle", в ней я достаточно детально разъясняю оба понятия.
"Временная отметка - это столбец, который добавляете ВЫ. Это може быть число, дата, все, что вам угодно."
O.K. Вы имеет ввиду, что в каждой таблице, которую предполагается изменять в web-приложениях, должетн быть столбец временной отметки (Timestamp) (типа date или number)... так что при выборке записей мы всегда выбираем значение этого столбца... и увеличиваем его значение при каждом выполненном пользователем изменении... и значение этого столбца будет передаваться процедуре
У нас уже есть столбец modifieddt во всех таблицах. Но мы используем его для записи значения sysdate при изменении столбца или вставке новой строки... Так что, у нас есть триггеры по всем таблицам, которые изменяют дату последнего изменения если было изменено значение хоть одного столбца, так что если из web-приложения посылаются все столбцы, независимо от того, были они изменены или нет, эти триггеры изменят значение в столбце modifieddt
IF (INSERTING) THEN :NEW.MODIFIEDBY := NVL(:NEW.MODIFIEDBY,USER); :NEW.MODIFIEDDT := NVL(:NEW.MODIFIEDDT,SYSDATE); END IF; IF (UPDATING) THEN IF ((NVL(:NEW.col1,'-X')) != (NVL(:OLD.col1, '-X'))) OR ((NVL(:NEW.col2, -1)) != (NVL(:OLD.col2, -1))) THEN IF (:NEW.MODIFIEDBY = :OLD.MODIFIEDBY) THEN :NEW.MODIFIEDBY := USER; END IF; IF (:NEW.MODIFIEDDT = :OLD.MODIFIEDDT) THEN :NEW.MODIFIEDDT := SYSDATE; END IF; END IF; END IF;
Вопрос:
Можем ли мы использовать этот столбец modifieddt и первичный ключ для выборки строки, а затем триггер
изменит его значение, установив текущее время? Так что, все выборки из web-приложения всегда будут
выбирать значение modifieddt в формате 'dd-mon-yyyy hh:mi:ss pm' и передавать процедуре
изменения в таком же формате, чтобы можно было сравнивать эти временные отметки...
Многовато работы... Нет ли более простого способа предотвратить потерянные изменения?
Я не писал, что в каждой таблице должна быть временная отметка...
Это лишь один из способов - вот другие:
В вашем случае оператор update будет выглядеть так:
update T set ..... where primary_key = :primary_key AND timestamp = :временная_отметка_полученная_при_выборке строки; if sql%rowcount = 0 then -- сообщение пользователю о неудаче.
В среде клиент-сервер был более простой способ предотвратить потерянные изменения - сторока блокировалась при выборке и оставалась заблокированной. Это называлось пессимистическим блокированием. Оно происходило само собой. Надо возвращаться к прежним архитектурам ;)
Еще одно - усилия надо прикладывать всегда. Надо поработать, чтобы создать хорошо продуманное и хорошо работающее приложение. Надо думать о подобных проблемах. Для этого нужно время и силы. И 15 лишних нажатий на клавиши. Но в результате получится намного лушче, чем при изпользовании подхода "будем надеяться, что это никогда не случится". Серверы баз данных - не черные ящики. Это нетривальные программы. Серверы баз данных - не товар широкого потребления. Это - инструменты. У меня на ноге - большой шрам от бензопилы. Я был немного неосторожен с этим достаточно мощным инструментом (это случилось всего один раз, столкнувшись с такими вещами ближе, начинаешь по-другому относиться к инструментам)...
Вы написали:
Второй подход - сложнее для программирования, но изменяется только то, что необходимо.
Проблема обоих подходов - невозможность установить столбцу значение NULL, так что, если это необходимо, логику работы придется изменить.
Меня в связи с этим интересует следующее:
Не могли бы вы объяснить, почему этот подход не работает для null-значений? Как надо изменить алгоритм работы с контекстом, чтобы можно было обрабатывать null?
Придется добавить дополнительные элементы в экранные формы - флажки или что-то подобное, что позволило бы сказать "установить значение null". Вот и все.
Не то, чтобы подход "не работал" для null, - это просто была универсальная процедура изменения - точка. Подходящая для изменения любого набора столбцов, когда null означает "игнорировать". Надо добавить флаг для каждого входного параметра, означающий "не игнорировать меня, использовать"...
Я уверен, что есть (буквально) миллионы приложений, создатели которых не задумывались над решением проблемы потерянного изменения. Подозреваю, что среди них есть много web-приложений - средства разработки типа Forms, я уверен, эту проблему решают.
Как всегда, Том, ты оказываешь сообществу пользователей Oracle неоценимую услугу. Я бы сказал: "Так держать!", но если ты продолжишь в том же духе, в один прекрасный день плохих разработчиков не останется, и тебе нечего будет делать (по крайней мере, с точки зрения поддержки этого сайта) :)
Оригинал обсуждения этого вопроса можно найти здесь.
Copyright © 2003 Oracle Corporation
Получение строк с N-ой по М-ую из результирующего множества. Следите за новостями на сайте проекта Open Oracle.
С наилучшими пожеланиями,
В.К.
http://subscribe.ru/
E-mail: ask@subscribe.ru |
Отписаться
Убрать рекламу |
В избранное | ||