Oracle 10g: Каков хороший академический подход к тому, чтобы не допускать постоянного обновления записи? - PullRequest
2 голосов
/ 11 августа 2011

У нас есть таблица, которая называется Контракты.Эти записи контракта создаются пользователями на внешнем сайте и должны быть одобрены или отклонены сотрудниками на внутреннем сайте.Когда контракт отклоняется, он просто удаляется из БД.Однако, когда она принята, создается новая запись под названием «Принятие контракта», которая записывается в его собственную таблицу и извлекается из данных, существующих в контракте.

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

Быстрый и грязный способ преодолеть это - извлечь договор из БД.незадолго до того, как оно будет принято, проверьте состояние и выдайте сообщение об ошибке, в котором говорится, что оно уже принято.Это, вероятно, будет работать в большинстве случаев, но пользователи все равно могут одновременно нажать кнопку «Принять» и прокрасться по этому коду проверки.

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

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

Ищите хорошие предложения о том, как это сделать.

Ответы [ 2 ]

2 голосов
/ 11 августа 2011

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

select ...
from Contract
where contract_id = :the_contract
for update nowait;

insert into Contract_Acceptance ...

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

ORA-00054: resource busy and acquire with nowait specified
1 голос
/ 11 августа 2011

В общем, есть два подхода к проблеме

Вариант 1: пессимистическая блокировка

В этом сценарии вы пессимистичны, поэтому блокируете строку втаблица, когда вы выбираете его.Когда пользователь запрашивает таблицу Contracts, он делает что-то вроде

SELECT *
  FROM contracts
 WHERE contract_id = <<some contract ID>>
   FOR UPDATE NOWAIT;

Кто бы ни выбрал запись первым, она заблокирует ее.Любой, кто выберет секунду записи, получит ошибку ORA-00054, которую приложение затем поймает и сообщит им, что другой пользователь уже заблокировал запись.Когда первый пользователь завершает свою работу, он вводит свой INSERT в таблицу Contract_Acceptance и фиксирует свою транзакцию.Это снимает блокировку строки в таблице Contracts.

Вариант 2: оптимистическая блокировка

В этом сценарии вы с оптимизмом полагаете, что оба пользователяне будет конфликтовать, поэтому вы не заблокируете запись изначально.Вместо этого вы выбираете необходимые данные вместе со столбцом Last_Updated_Timestamp, который добавляете в таблицу, если он еще не существует.Что-то вроде

SELECT <<list of columns>>, Last_Updated_Timestamp
  FROM Contracts
 WHERE contract_id = <<some contract ID>>

Когда пользователь принимает контракт, перед выполнением INSERT в Contract_Acceptance он выдает UPDATE по контрактам

UPDATE Contracts
   SET last_updated_timestamp = systimestamp
 WHERE contract_id = <<some contract ID>>
   AND last_update_timestamp = <<timestamp from the initial SELECT>>;

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

В любом случае

Вв любом случае вы, вероятно, захотите добавить ограничение UNIQUE в таблицу Contract_Acceptance.Это обеспечит наличие только одной строки в таблице Contract_Acceptance для любого заданного Contract_ID.

ALTER TABLE Contract_Acceptance
  ADD CONSTRAINT unique_contract_id UNIQUE (Contract_ID)

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

...