Допустим, у меня есть таблица T
со следующими столбцами:
create table T as
(
supplier varchar2,
item varchar2,
price number,
is_best_price number,
);
И у нас есть процедура для вставки элементов:
create or replace procedure insert_item
(p_supplier as varchar2, p_item as varchar2, p_price as number) as
declare
v_best_price;
v_is_best_price number;
begin
select min(price) into v_best_price from T where item = p_item;
if (v_best_price is null) then
v_is_best_price := 1;
elsif (price <= v_best_price) then
v_is_best_price := 1;
else
v_is_best_price := 0;
end if;
if (v_is_best_price = 0) then
update T set is_best_price = 0 where item = p_item and is_best_price = 1;
end if;
insert into T values (p_supplier, p_item, p_price, v_is_best_price);
end;
Инвариант здесь такой, что
Forall rows x: (x.v_best_price = 1) iff (x.v_price = select min(price) from T where item = x.item)
Или, если говорить простым языком, is_best_price
равно 1
, если цена товара является лучшей.
Проблема возникает, если я делаю это в двух разных сессиях:
insert_item('alice', 'pants', '30');
insert_item('bob', 'pants', '20');
Теперь, если я правильно понимаю, может произойти следующее:
(1) Выполнить insert_item('alice', 'pants', '30')
(нить A)
(2) Выполнить insert_item('bob', 'pants', '20')
(резьба B)
(3) Поток A запрашивает таблицу, замечает, что других штанов нет, поэтому задает v_is_best_price := 1
.
(4) Поток B запрашивает таблицу, замечая, что других трусов нет (поскольку поток A еще не вставлен), поэтому задает v_is_best_price := 1
.
(5) Нить А вставляет («Алиса», «брюки», «30», 1).
(6) Резьба B вставляет («боб», «штаны», «20», 1).
Мы нарушили наш инвариант.
Итак, я понимаю, что могу заблокировать всю таблицу в первой строке процедуры перед выбором, выполнив следующее:
lock table T in exclusive mode;
Что, если я правильно понимаю, будет означать, что любые операции чтения или записи в таблицу будут остановлены до завершения потока A (то есть поток A и поток B не могут работать параллельно).
Есть ли другой способ сделать это, кроме блокировки всей таблицы? select ... for update
помогает? Или есть какой-то другой способ сделать более точную блокировку зерна?
Я использую Oracle 10g, если это что-то меняет.