powered by simpleCommunicator - 2.0.61     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / Oracle [игнор отключен] [закрыт для гостей] / Задачка
25 сообщений из 145, страница 4 из 6
Задачка
    #33319224
Фотография Elic
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Sxak ElicЕдинственный способ получить уникальный номер блокировки - это dbms_lock.allocate_unique, который в триггере использовать нельзя. Все остальные способы не гарантируют отсутствие конфликтов.А зачем обязателнь оуникальный? Ну будет происходить ожидание не на той фамилии иногда но редко. Разве ето критично?Дело не в уникальном хэше. Номера блокировок - это очень ценный и ограниченный ресурс, который нельзя использовать как попало.
Представь, что некая задача тоже использует пользовательские блокировки в таком же широком диапазоне номеров. И для такой задачи может оказаться крайне критичным напороться на чужую (а не свою) блокировку. Т.е. подсистемы могут пересечься по диапазону используемых номеров блокировок с более катастрофическими последствиями, чем "подумаешь, немножко подождёт".

Вячеслав ЛюбомудровАвтономная транзакция?А вот это уже похоже на правду. Выходит, я был не прав.
...
Рейтинг: 0 / 0
Задачка
    #33319270
va_kochnev
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Для решения задачи можно создать дополнительную таблицу со структурой
-отдел
-фамилия
-число_однофамильцев
с первичным ключом на (отдел, фамилия)

На исходную таблицу повесить триггер
for each row
before insert or update or delete
begin
select ... from доп_таблица where :new.отдел=отдел and ... for update; -- for update для многопользовательской работы
if inserting and число_однофамильцев=3 then
raise_application_error(...)
elsif inserting and число_однофамильцев=0 then
insert into доп_таблица ... -- если до commit кто-то еще дернется с insert-ом, то получит отлуп из-за уникальности, что не есть хорошо, т.к. в сумме получается только 2 однофамильца.
elsif inserting and число_однофамильцев between 1 and 2 then
update доп_таблица set число_однофамильцев+1 where ...;
elsif deleting and число_однофамильцев>1 then
update доп_таблица set число_однофамильцев-1 where ...;
elsif -- дальше писать лень, но идея, думаю, понятна

end if;

end;
...
Рейтинг: 0 / 0
Задачка
    #33319286
Sxak
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ElicДело не в уникальном хэше. Номера блокировок - это очень ценный и ограниченный ресурс, который нельзя использовать как попало.
Представь, что некая задача тоже использует пользовательские блокировки в таком же широком диапазоне номеров. И для такой задачи может оказаться крайне критичным напороться на чужую (а не свою) блокировку. Т.е. подсистемы могут пересечься по диапазону используемых номеров блокировок с более катастрофическими последствиями, чем "подумаешь, немножко подождёт".
Ага похоже на правду а вот интренесно перекрестные блокировки dbms_lockом оракл отлавливает? Если нет то есть риск получить 2 зависшие сессии
...
Рейтинг: 0 / 0
Задачка
    #33319297
Sxak
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
va_kochnevДля решения задачи можно создать дополнительную таблицу со структурой
-отдел
-фамилия
-число_однофамильцев
с первичным ключом на (отдел, фамилия)

На исходную таблицу повесить триггер
for each row
....
select ... from доп_таблица where :new.отдел=отдел and ... for update; -- for update для многопользовательской работы
.....
insert into доп_таблица ... -- если до commit кто-то еще дернется с insert-ом, Сдается мне что и ето рискует вызвать ...триггер фция может не заметить етого
...
Рейтинг: 0 / 0
Задачка
    #33319391
Фотография Elic
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Sxakа вот интренесно перекрестные блокировки dbms_lockом оракл отлавливает? Если нет то есть риск получить 2 зависшие сессииЭти блокировки ничем не "особенней". dbmslock.sql
Код: plaintext
1.
2.
3.
  function  request(...
  --    If a deadlock is detected, then an arbitrary session is
  --    chosen to receive deadlock status.
...
Рейтинг: 0 / 0
Задачка
    #33319487
Stax.
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Elic
Единственный способ получить уникальный номер блокировки - это dbms_lock.allocate_unique, который в триггере использовать нельзя. Все остальные способы не гарантируют отсутствие конфликтов.
а нам и не надо уникальный, надо по фалилии
.....
Stax
...
Рейтинг: 0 / 0
Задачка
    #33319494
Вячеслав Любомудров
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Ну вот что у меня получилось (кому не лень потестите, а то я ухожу) - сразу говорю - весьма нагрузит систему
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
tst> create or replace function get_unique(name varchar2) return varchar2 as
   2       pragma autonomous_transaction;
   3       handle varchar2( 128 );
   4   begin dbms_lock.allocate_unique(name, handle);
   5       commit;
   6       return handle;
   7   end;
   8   /

Function created.

tst> create or replace package p1 as
   2   type lock_rec is record (name varchar2( 128 ), handle varchar2( 128 ));
   3   type lock_rec_tab is table of lock_rec index by binary_integer;
   4   procedure set_lock(name varchar2);
   5   procedure clear_locks;
   6   procedure submit_locks;
   7   end;
   8   /

Package created.

tst> create or replace package body p1 as
   2   locks lock_rec_tab;
   3   procedure set_lock(name varchar2) is
   4       i number :=  1 ;
   5       ret number;
   6       rec lock_rec;
   7   begin
   8       if locks.first is not null then
   9           for i in locks.first..locks.last loop
  10               if locks(i).name=name then
  11                   return;
  12               end if;
  13           end loop;
  14       end if;
  15       rec.name := name;
  16       rec.handle := get_unique(name);
  17       ret := dbms_lock.request(rec.handle, release_on_commit=>true);
  18       if ret= 4  then
  19           return; /* already locked by me */
  20       elsif ret !=  0  then
  21           raise_application_error(- 20001 , 'Can''t lock for '||name);
  22       end if;
  23       locks(i) := rec;
  24   end;
  25   /* ----- */
  26   procedure clear_locks is
  27       i number :=  1 ;
  28       ret number;
  29   begin
  30       if locks.first is not null then
  31           for i in locks.first..locks.last loop
  32               ret := dbms_lock.release(locks(i).handle);
  33               if ret !=  0  then
  34                   raise_application_error(- 20001 , 'Can''t release lock for '||locks(i).name);
  35               end if;
  36               locks.delete(i);
  37           end loop;
  38       end if;
  39   end;
  40   /* ----- */
  41   procedure submit_locks is
  42   begin
  43       locks.delete;
  44   end;
  45   end;
  46   /

Package body created.

tst> drop table t1;

Table dropped.

tst> create table t1(name varchar2( 30 ));

Table created.

tst> create or replace trigger t1_bef
   2   before update or insert or delete on t1
   3   for each row
   4   begin
   5       if inserting then
   6           p1.set_lock(:new.name);
   7       elsif deleting then
   8           p1.set_lock(:old.name);
   9       else
  10           p1.set_lock(:old.name);
  11           p1.set_lock(:new.name);
  12       end if;
  13   end;
  14   /

Trigger created.

tst> create or replace trigger t1_aft
   2   after update or insert or delete on t1
   3   begin
   4       for r in (select name, count(*) cnt from t1
   5                 group by name
   6                 HAVING count(*) >  2  /* Our limit */ ) loop
   7           p1.clear_locks;
   8           raise_application_error(- 20003 , 'Too many count for (as example) '||r.name);
   9       end loop;
  10       p1.submit_locks;
  11   end;
  12   /

Trigger created.
И в одной сессии
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
tst> insert into t1 values('One');

 1  row created.

tst> /

 1  row created.

tst> /
insert into t1 values('One')
*
ERROR at line  1 :
ORA- 20003 : Too many count for (as example) One
ORA- 06512 : at "U1.T1_AFT", line  6 
ORA- 04088 : error during execution of trigger 'U1.T1_AFT'

В другой
Код: plaintext
ts2> insert into t1 values('One');
Ждемс до коммита в первой ...
и получаем
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
insert into t1 values('One')
*
ERROR at line  1 :
ORA- 20003 : Too many count for (as example) One
ORA- 06512 : at "U1.T1_AFT", line  6 
ORA- 04088 : error during execution of trigger 'U1.T1_AFT'


ts2> 
Все блокировки вроде почистились
...
Рейтинг: 0 / 0
Задачка
    #33319708
mcureenab
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
va_kochnevДля решения задачи можно создать дополнительную таблицу со структурой
-отдел
-фамилия
-число_однофамильцев
с первичным ключом на (отдел, фамилия)

end;

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

Чтобы обойти проблему одновременной вставки первого сотрудника однофамильца, можно использовать автономную транзакцию.

Если после update счётчика SQL%rowcount = 0, нужно в автономной транзакции добавить запись с новой фамилией и счётчиком = 0, а затем снова повторить update.


Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
if deleting then
    inc := - 1 ;
    update fam_counter set cnt=cnt+inc where ... returning cnt into cnt;
    if  0  = sql%rowcount then
        raise application_error; -- Нет записи, хотя должна быть!
    end if;
    if not  0  < cnt then
         delete fam_counter where ... ;
    end if;
end if;

if inserting then
    inc :=  1 ;
    loop
        update fam_counter set cnt=cnt+inc where ... returning cnt into cnt;
        exit when  0  < sql%rowcount;
        {insert  (...., cnt) values (...,  0 ); в автономной транзакции с обработкой dupval_on_index}
    end loop;
    if  3  < cnt then
         raise dupval_on_index; -- Дублирование в размере > 3!
    end if;
end if;

-- Изменение фамилии или перевод в другой отдел обрабатывается последовательным удалением старой фамилии и заведением новой.


Иногда в БД будут оставаться записи с cnt=0, их время от времени придётся удалять.
...
Рейтинг: 0 / 0
Задачка
    #33320039
Stax.
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Вячеслав ЛюбомудровНу вот что у меня получилось (кому не лень потестите, а то я ухожу) - сразу говорю - весьма нагрузит систему
....

Если грубо нужен обход мутации
лично я пишу несколько иначе
в операторном before очищаю pl/sql таблицу
імхо
бокировку надо выставить до проверки (до фор)
иначе успеют вставить

мона несколько улучшить сам селект
select name, count(*) cnt from t1
where name in (таблица)

может ето и неправильно но я сначала организовываю
цикл по таблице
и селект получается для каждой фамилии

....
Stax
...
Рейтинг: 0 / 0
Задачка
    #33321379
Вячеслав Любомудров
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Stax.Если грубо нужен обход мутацииТам нет мутации (по крайней мере, я не смог нарваться) Stax.
лично я пишу несколько иначе
в операторном before очищаю pl/sql таблицуЭто дополнительный триггер - все равно нужет операторный AFTER для проверки результата и строчный BEFORE для блокировок.
Хотя, конечно, дело вкуса
Stax.імхо
бокировку надо выставить до проверки (до фор)
иначе успеют вставитьПроверка по фор чисто для ускорения (там ошибка, кстати), чтоб не дергать лишний раз DBMS_LOCK.ALLOCATE_UNIQUE и DBMS_LOCK.REQUEST. Вставить успеют, только если успеют выставить блокировку, тогда уже мы будем ждать
Stax.
мона несколько улучшить сам селект
select name, count(*) cnt from t1
where name in (таблица)

может ето и неправильно но я сначала организовываю
цикл по таблице
и селект получается для каждой фамилии

....
StaxЛогично

Теперь об ошибках
Неправильно ведется таблица блокировок (всегда перезаписывается первая и единственная):
Код: plaintext
1.
2.
3.
4.
5.
6.
...
  20       elsif ret !=  0  then
  21           raise_application_error(- 20001 , 'Can''t lock for '||name);
  22       end if;
  23       locks(i+ 1 ) := rec;
  24   end;
...


Функция P1.CLEAR_LOCKS нафинг не нужна, т.к. при сбое оператора освободятся все блокировки выставленные в этом операторе (по крайней мере для RELEASE_ON_COMMIT=>TRUE, этакий мини-роллбэк), но нужно почистить таблицу блокировок (P1.SUBMIT_LOCKS), иначе при commit/rollback блокировки освободятся, а мы будем считать что они установлены


Как следствие, нет необходимости хранить handle блокировки, соответствующий имени


По большому счету таблицу блокировок можно вообще не вести (соответственно, лишние вызовы DBMS_LOCK.ALLOCATE_UNIQUE и DBMS_LOCK.REQUEST)


Триггер, естественно, нужно лепить не на любое обновление, а только при обновлении используемых полей (UPDATE OF name)


Наверняка, еще куча ошибок/неоптимальностей
...
Рейтинг: 0 / 0
Задачка
    #33321454
xif
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
вот из этого можно что нибудь придумать
http://www.dbazine.com/oracle/or-articles/tropashko8
...
Рейтинг: 0 / 0
Задачка
    #33321459
Владимир Бегун
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Да... мучения похоже будут продолжаться долго...

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
Присоединен к:
Oracle Database 10g Enterprise Edition Release  10 . 1 . 0 . 4 . 0  - Production
With the Partitioning, Oracle Label Security, OLAP and Data Mining Scoring Engine options

SQL> DROP TABLE tbl;

Таблица удалена.

SQL> CREATE TABLE tbl (v VARCHAR2( 10 ) NOT NULL);

Таблица создана.

SQL> CREATE MATERIALIZED VIEW LOG ON tbl WITH ROWID, SEQUENCE (v) INCLUDING NEW VALUES;

Журнал материализованного представления создан.

SQL> DROP MATERIALIZED VIEW tbl_mv;
DROP MATERIALIZED VIEW tbl_mv
*
ошибка в строке  1 :
ORA- 12003 : materialized view "APPS"."TBL_MV" does not exist


SQL> CREATE MATERIALIZED VIEW tbl_mv
   2   REFRESH FAST ON COMMIT
   3   AS
   4      SELECT v, COUNT(v) cnt, COUNT(*)
   5        FROM tbl
   6       GROUP BY v
   7   /

Материализованное представление создано.

SQL> ALTER TABLE tbl_mv ADD CONSTRAINT c$tbl_mv CHECK (cnt <=  3 )
   2   /

Таблица изменена.

SQL> DELETE tbl;

 0  строк удалено.

SQL> INSERT INTO tbl VALUES('t');

 1  строка создана.

SQL> INSERT INTO tbl VALUES('t');

 1  строка создана.

SQL> INSERT INTO tbl VALUES('t');

 1  строка создана.

SQL> INSERT INTO tbl VALUES('t1');

 1  строка создана.

SQL> COMMIT;

Фиксация обновлений завершена.

SQL> INSERT INTO tbl VALUES('t');

 1  строка создана.

SQL> COMMIT;
COMMIT
*
ошибка в строке  1 :
ORA- 12008 : error in materialized view refresh path
ORA- 02290 : check constraint (APPS.C$TBL_MV) violated


SQL> UPDATE tbl SET v = 't' WHERE v = 't1';

 1  строка обновлена.

SQL> COMMIT;
COMMIT
*
ошибка в строке  1 :
ORA- 12008 : error in materialized view refresh path
ORA- 02290 : check constraint (APPS.C$TBL_MV) violated


SQL> 
...
Рейтинг: 0 / 0
Задачка
    #33321463
Вячеслав Любомудров
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Да нет, причем тут мучения
Просто с MV сушественный недостаток - это "отложенность" проверки до коммита (полчаса вводили, а потом узнали, что так низзя)
Хотя может кому и подойдет

С блокировками мне больше нравится, т.к. попутно решается и задача найти того, кто держит записи не давая их обновить
...
Рейтинг: 0 / 0
Задачка
    #33321468
Владимир Бегун
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Вячеслав ЛюбомудровДа нет, причем тут мучения
Просто с MV сушественный недостаток - это "отложенность" проверки до коммита (полчаса вводили, а потом узнали, что так низзя)
Хотя может кому и подойдет

С блокировками мне больше нравится, т.к. попутно решается и задача найти того, кто держит записи не давая их обновить
"Пилите, Шура, пилите..." (с)
...
Рейтинг: 0 / 0
Задачка
    #33321700
Stax.
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Вячеслав Любомудров Stax.Если грубо нужен обход мутацииТам нет мутации (по крайней мере, я не смог нарваться) Stax.
лично я пишу несколько иначе
в операторном before очищаю pl/sql таблицуЭто дополнительный триггер - все равно нужет операторный AFTER для проверки результата и строчный BEFORE для блокировок.
Хотя, конечно, дело вкуса

я не имел ввиду что в коде есть мутациия,
просто берется стандартная схема обхода
и даписываем проверку на 3

Очищаю в бифоре в основном из-за рестарта оператора,
если не очищать, то таблицу заполним два раза
Вячеслав Любомудров
Stax.імхо
бокировку надо выставить до проверки (до фор)
иначе успеют вставитьПроверка по фор чисто для ускорения (там ошибка, кстати), чтоб не дергать лишний раз DBMS_LOCK.ALLOCATE_UNIQUE и DBMS_LOCK.REQUEST. Вставить успеют, только если успеют выставить блокировку, тогда уже мы будем ждать


Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
tst> create or replace trigger t1_aft
   2   after update or insert or delete on t1
   3   begin
   4       for r in (select name, count(*) cnt from t1
   5                 group by name
   6                 HAVING count(*) >  2  /* Our limit */ ) loop
   7           p1.clear_locks;
   8           raise_application_error(- 20003 , 'Too many count for (as example) '||r.name);
   9       end loop;
 мы вставили две фамилии и не блокировали
 во второй тоже вставили две
 теперь не важно кто первый заблокирует
 проверки уже выполнены если будут комитеть
 получим четыре фамилии
  10       p1.submit_locks;
  11   end;
  12   /

Вячеслав Любомудров
Функция P1.CLEAR_LOCKS нафинг не нужна, т.к. при сбое оператора освободятся все блокировки выставленные в этом операторе (по крайней мере для RELEASE_ON_COMMIT=>TRUE, этакий мини-роллбэк), но нужно почистить таблицу блокировок (P1.SUBMIT_LOCKS), иначе при commit/rollback блокировки освободятся, а мы будем считать что они установлены


тут я не понял, ну освободятся и добре,
что плохого что блокировки снимаеются в случае
ошибки

Вячеслав Любомудров
По большому счету таблицу блокировок можно вообще не вести (соответственно, лишние вызовы DBMS_LOCK.ALLOCATE_UNIQUE и DBMS_LOCK.REQUEST)


я бы вел токо таблицу имен, мона даже с distinct

.....
Stax
...
Рейтинг: 0 / 0
Задачка
    #33321745
Вячеслав Любомудров
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Stax.
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
tst> create or replace trigger t1_aft
   2   after update or insert or delete on t1
   3   begin
   4       for r in (select name, count(*) cnt from t1
   5                 group by name
   6                 HAVING count(*) >  2  /* Our limit */ ) loop
   7           p1.clear_locks;
   8           raise_application_error(- 20003 , 'Too many count for (as example) '||r.name);
   9       end loop;
 мы вставили две фамилии и не блокировали
 во второй тоже вставили две
 теперь не важно кто первый заблокирует
 проверки уже выполнены если будут комитеть
 получим четыре фамилии
  10       p1.submit_locks;
  11   end;
  12   /
Мы их заблокировали при вставке в триггере BEFORE ... FOR EACH ROWS (точнее повесили блокировки на фамилии). И вторая сессия эти фамилии уже не вставит до нашего окончания транзакции.

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

В общем, вот такое (приглаженное) получилось - я думаю добавить еще и отдел будет нетрудно
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
define TAB_NAME=T1
define FLD_NAME=NAME
define MAX_COUNT= 2 
define LK_TIMEOUT= 2 

set verify off
column user_and_table new_value LK_PREFIX noprint
select '('||user||'.&&TAB_NAME.) ' user_and_table from dual
/
create table &&TAB_NAME(&&FLD_NAME varchar2( 30 ))
/
create or replace package &&TAB_NAME._lock_mgr as
	type lock_rec_tab is table of &&TAB_NAME..&&FLD_NAME%type index by binary_integer;
	locks lock_rec_tab;
	procedure set_lock(str varchar2);
end;
/
create or replace package body &&TAB_NAME._lock_mgr as
/* ----- */
function get_unq(str varchar2) return varchar2 is
	pragma autonomous_transaction;
	handle varchar2( 128 );
begin
	dbms_lock.allocate_unique('&&LK_PREFIX'||str, handle);
	commit;
	return handle;
end;
/* ----- */
procedure set_lock(str varchar2) is
	i number :=  0 ;
	ret number;
begin
	if locks.first is not null then
		for j in locks.first..locks.last loop
			if locks(j)=str then
				return;
			end if;
		end loop;
		i := locks.last;
	end if;
	ret := dbms_lock.request(get_unq(str), timeout=>&&LK_TIMEOUT,
			release_on_commit=>true);
	if ret= 0  or ret= 4  /* already locked by me */ then
		locks(i+ 1 ) := str;
	elsif ret= 1  /* locked by another session */ then
		for r in (select username, s.sid from v$session s, v$lock l, sys.dbms_lock_allocated la
				where l.type='UL' and l.lmode> 0  and l.id1=la.lockid
				and s.sid=l.sid and la.name='&&LK_PREFIX'||str) loop
			raise_application_error(- 20002 ,	'"'||str||'" locked by '||
					r.username||' (SID='||r.sid||')');
		end loop;
	else
		raise_application_error(- 20001 ,	'Can''t lock for "'||str||'"');
	end if;
end;
end;
/
create or replace trigger &&TAB_NAME._lock_before
before insert or delete or update of &&FLD_NAME on &&TAB_NAME
for each row
begin
	if inserting or updating then
		&&TAB_NAME._lock_mgr.set_lock(:new.&&FLD_NAME);
	end if;
	if updating or deleting then
		&&TAB_NAME._lock_mgr.set_lock(:old.&&FLD_NAME);
	end if;
end;
/
create or replace trigger &&TAB_NAME._lock_after
after insert or delete or update of &&FLD_NAME on &&TAB_NAME
declare
	nm &&TAB_NAME..&&FLD_NAME%type;
	cnt number;
begin
	if &&TAB_NAME._lock_mgr.locks.first is not null then
		for i in &&TAB_NAME._lock_mgr.locks.first..&&TAB_NAME._lock_mgr.locks.last loop
			nm := &&TAB_NAME._lock_mgr.locks(i);
			select count(*) into cnt from &&TAB_NAME where &&FLD_NAME=nm;
			if cnt > &&MAX_COUNT then
				&&TAB_NAME._lock_mgr.locks.delete;
				raise_application_error(- 20003 , 'Too many count for "'||nm||'"');
			end if;
		end loop;
	end if;
	&&TAB_NAME._lock_mgr.locks.delete;
end;
/
set verify on
undef LK_PREFIX
undef LK_TIMEOUT
undef MAX_COUNT
undef FLD_NAME
undef TAB_NAME
...
Рейтинг: 0 / 0
Задачка
    #33321900
Stax.
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Вячеслав Любомудров
Мы их заблокировали при вставке в триггере BEFORE ... FOR EACH ROWS (точнее повесили блокировки на фамилии). И вторая сессия эти фамилии уже не вставит до нашего окончания транзакции.

Понял, вот что такое невнимательность,
просто я блокировал непосредственно перед проверкой
(перед select count(*) )

А еще забыл сразу написать,
блокировать желательно отсортировав по какому то критерию,
иначе легко получить деадлок
PS
зря Вы не очищаете таблицу в операторном before,
но это так мысли в слух
......
Stax
...
Рейтинг: 0 / 0
Задачка
    #33321947
__LEV__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Суть идеи: при редактировании оракл блокирует запись, поэтому я попытался свести операции над фамилией+отделом(tab1) к изменению одной записи в другой таблице(tab2). В tab2 фамилия, отдел - первичный ключ, таким образом любые операции над одной фамилией+отдел встанут "в очередь".
Проверил работоспобность при insert,delete и update в одной и разных транзакциях, вроде работает.
Сразу предупрежу, не работает с множественными изменениями (ограничения на merge). То есть если за один DML удалить, обновить, создать несколько записей в tab1. Ошибки можно измежать, надо просто усовершенствовать триггер tr_tab1_name3_row. Если есть интерес буду рад поделиться дальше...

create table tab1(
id number primary key,
name varchar2(20),
otdel number
)
/
create table tab2(
name varchar2(20) not null,
otdel number not null,
cnt number default 1 not null
)
/
CREATE GLOBAL TEMPORARY TABLE tab3
(
name VARCHAR2(20) NOT NULL,
otdel NUMBER NOT NULL,
action char(1) NOT NULL
)
ON COMMIT DELETE ROWS
/
alter table tab2 add constraint pk_tab2 primary key (name,otdel);
CREATE OR REPLACE TRIGGER tr_tab2_name3
AFTER
UPDATE OF CNT
ON TAB2
FOR EACH ROW
begin
if :new.cnt>3 then
raise_application_error(-20000,'Нарушение целостности ...');
end if;
end;
/
create index ind_name3 on tab1 (name asc,otdel asc)
/
create or replace trigger tr_tab1_name3_row
after
insert or update or delete of name,otdel
on tab1
for each row
begin
if INSERTING then
insert into tab3(name,otdel,action)
values (:new.name,:new.otdel,'I');
elsif UPDATING and (:new.name<>:old.name or :new.otdel<>:old.otdel) then
insert into tab3(name,otdel,action)
values (:new.name,:new.otdel,'I');
insert into tab3(name,otdel,action)
values (:old.name,:old.otdel,'D');
else
insert into tab3(name,otdel,action)
values (:old.name,:old.otdel,'D');
end if;
end;
/
create or replace trigger tr_tab1_name3_st
after
insert or update or delete of name,otdel
on tab1
begin
for r in (select * from tab3) loop
dbms_output.put_line(R.name||' '||R.otdel||' '||R.action);
end loop;
merge into tab2 T2
using (select * from tab3) T3
on (T2.name=T3.name and T2.otdel=T3.otdel)
when matched then update set T2.cnt=T2.cnt + decode(T3.action,'D',-1,1)
when not matched then insert(name,otdel) values(decode(T3.action,'D',null,T3.name),T3.otdel);

delete from tab3;
end;
/
...
Рейтинг: 0 / 0
Задачка
    #33321974
__LEV__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
... и еще, в данном примере нет никаких автономных транзакций, ипользования dbms_lock и т.п.. Все операции поиска идут по первичным ключам, должно летать на любых объемах, во всяком случае не тормозить (нет select count(*) from tab group by having - перебор всех записей таблицы - из моего опыта - это подвесит систему при промышленных объемах данных)

Спасибо.
...
Рейтинг: 0 / 0
Задачка
    #33322158
Stax.
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
__LEV__... и еще, в данном примере нет никаких автономных транзакций, ипользования dbms_lock и т.п.. Все операции поиска идут по первичным ключам, должно летать на любых объемах, во всяком случае не тормозить (нет select count(*) from tab group by having - перебор всех записей таблицы - из моего опыта - это подвесит систему при промышленных объемах данных)
Спасибо.

1 Если есть индекс(а он у вас есть) то летать будет
и вариант с count(*) where name=p_name

2 у вас две дополнительные таблицы
а ето нагрузка на систему ввода вывода

3 если заводить другую таблицу то имхо мона обойтись
одним триггером for ech row на основной,
во второй поставить CHECK (cnt <= 3) (как у Владимира)
(непонял зачем так сложно)

.....
Stax
...
Рейтинг: 0 / 0
Задачка
    #33322177
Вячеслав Любомудров
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Да и где-то в логике напутано
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
tst> insert into tab1 values( 1 , 'One',  1 );
One  1  I

 1  row created.

tst> insert into tab1 values( 2 , 'One',  1 );
One  1  I

 1  row created.

tst> insert into tab1 values( 3 , 'One',  1 );
One  1  I

 1  row created.

tst> update tab1 set name='Two' where id= 1 ;
Two  1  I
One  1  D

 1  row updated.
во второй сессии
Код: plaintext
ts2> insert into tab1 values( 4 , 'One',  1 );
висим - ждемс до коммита в первой.
Вроде все правильно, но после коммита в первой сессии получаем во второй
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
insert into tab1 values( 4 , 'One',  1 )
            *
ERROR at line  1 :
ORA- 00001 : unique constraint (U1.PK_TAB2) violated
ORA- 06512 : at "U1.TR_TAB1_NAME3_ST", line  5 
ORA- 04088 : error during execution of trigger 'U1.TR_TAB1_NAME3_ST'


ts2> select * from tab1;

        ID NAME                      OTDEL
---------- -------------------- ----------
          1  Two                            1 
          2  One                            1 
          3  One                            1 
PS. А идею заценить трудновато - нет никакого желания копать неформатированный код
...
Рейтинг: 0 / 0
Задачка
    #33322335
__LEV__
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Да согласен, наверно можно упростить.
По поводу дополнительных таблиц - блокировки (dbms_lock)тоже ведь хранятся в системных таблицах - теже таблицы,та же нагрузка на ввод/вывод.
К тому же количество блокировок ограничено (порядок 10^9), боюсь "на всех не хватит".
Могу ошибаться,но мне кажется, что без дополнительной таблицы (своей, системной и т.п.) не обойтись. Сами посудите: анализировать текущую таблицу в любом типе триггера(пакете и т.п.) - бесполезно - не видно не подтвержденных транзакций других сессий. Хочешь упорядочить операции - юзай оракл (ключи, юник индексы и т.п.). Мне кажется что ответив на этот вопрос(с доп таблицами), мы определимся в каком направлении копать.
...
Рейтинг: 0 / 0
Задачка
    #33322563
Stax.
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Сначала надо решить Lom-22 ,
характер задачи,
насколько критичны блокировки,
как часто происходят операции над таблицей,
мож у них например запрещено удаление инфы(отдел кадров)
мож это редкие операции,
тогда вообще мона блокировать напр dual
надавно обсуждали

если код позволяет,
пользовать простой пример Владимира

Вариантов много,
но главное нужно блокировть "инсерт"
.......
Stax
...
Рейтинг: 0 / 0
Задачка
    #33324322
TRust
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Lom-22Помогите пожалуйста решить такую задачку:
Создать триггер базы данных, не позволяющий вводить в таблицу ААА
более трех однофамильцев, работающих в одном отделе.
В общем случае решить эту задачу только средствами сервера Oracle нельзя, так как задача является несколько завуалированной классической задачей эмуляции констрейнтов ч/л, кроме самих констрейнтов. А эта задача не имеет решения.
...
Рейтинг: 0 / 0
Задачка
    #33324530
Stax.
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
TRust Lom-22Помогите пожалуйста решить такую задачку:
Создать триггер базы данных, не позволяющий вводить в таблицу ААА
более трех однофамильцев, работающих в одном отделе.
В общем случае решить эту задачу только средствами сервера Oracle нельзя, так как задача является несколько завуалированной классической задачей эмуляции констрейнтов ч/л, кроме самих констрейнтов. А эта задача не имеет решения.

Как это не имеет?
на триггерах Вячеслав Любомудров показал
с доп таблицей __LEV__
через снапшоты Владимир Бегун

Выбирайте

......
Stax
...
Рейтинг: 0 / 0
25 сообщений из 145, страница 4 из 6
Форумы / Oracle [игнор отключен] [закрыт для гостей] / Задачка
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]