Гость
Форумы / Firebird, InterBase [игнор отключен] [закрыт для гостей] / Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину. / 25 сообщений из 43, страница 1 из 2
12.08.2014, 00:36
    #38717722
Таблоид
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
hi all

Имеется след. схема:
tmx - таблица заголовков "документов"; имеет поле OP, принимающее одно из двух значений: 1 или 2;
tdx - подчиненная ей деталь, связана по FK с tmx *двумя* каскадами (см ниже); имеет такое же поле OP, как и tmx, а также int-поле QTY;
tq1 и tq2 - две таблицы одинаковой структуры, в которых хранятся строки, логически подчинённые tdx, но НЕ связанные с этой таблицей FK.

Когда в tdx добавляется строка с каким-то целочисленным QTY, то если new.op = 1, будет добавлено N записей в таблицу TQ1, а если new.op=2 - то N записей в таблицу TQ2. При этом N всегда = new.qty (напомню, QTY - целочисленное).

Записи в таблице tmx могут, помимо добавления и удаления, также обновляться, но при этом меняется только содержимое поля OP: если оно = 1, то новое значение будет равно 2, и наоборот. Каскадный триггер на update приведёт к обновлению этого же поля (OP) в таблице tdx.

На таблице tdx определен триггер before update or delete.
Этот триггер:
1) при удалении - грохает все записи в TQ1 & TQ2, относящиеся к tdx.pid;
2) при обновлении (а это м.б. только поле OP) - выполняет перенос строк:
2.1) если new.op = 1, то из TQ2 в TQ1;
2.2) если new.op = 2, то из TQ1 в TQ2.

Перенос строк TQ1<-->TQ2 делается БЕЗ "искажения" записей, т.е. с сохранением первичных ключей, которые были им присвоены при вставке (через gen_id). Выполняется этот перенос так (пример для SOURCE = TQ1, TARGET = TQ2):
Код: sql
1.
2.
3.
4.
5.
              for select id, pid from tq1 where pid = new.pid as cursor c
              do begin
                  insert into tq2(id, pid) values (c.id, c.pid);
                  delete from tq1 where current of c;
              end



Кроме того, есть еще таблица TLOG для логирования возникающих исключений в автономной транзакции.

Эта схема, при работе транзакций с TIL = SNAPSHOT, будет допускать исключения вида lock_conflict, однако в ней не должны создаваться исключения вида PK/UK violation, либо FK violation. Потому что сначала лочатся родительские записи, и только потом начинается перенос строк TQ1<-->TQ2 (либо их удаление).

Однако, при интенсивной борьбе 50 молотилок можно быстро получить по лбу исключением unique_key_violation (gds=335544665).
Причину этого я понять не могу, поможыте разобраться, плз.

Вот DDL :
Код: 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.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
recreate exception ex_bad_error_encountered 'at least one forbidden error found, gdscode=@1';
commit;
create or alter procedure p_tmx_hadle_doc as begin end;
create or alter procedure p_tmx_add_doc as begin end;
create or alter function fn_halt_test returns int as begin end;
set term ^;
execute block as
begin
  begin execute statement 'drop trigger tdx_bi'; when any do begin end end
  begin execute statement 'drop trigger tdx_bud'; when any do begin end end
  begin execute statement 'drop trigger tmx_bud'; when any do begin end end
end
^set term ;^
commit;

recreate table tq1(
    id int primary key using index tq1_pk,
    pid int,
    x int default 1
);
create index tq1_pid on tq1(pid);
commit;

recreate table tq2(
    id int primary key using index tq2_pk,
    pid int,
    x int default 1
);
create index tq2_pid on tq2(pid);
commit;


recreate table tdx(
    id int primary key using index tdx_pk,
    pid int,
    op int not null check(op in(1,2)),
    qty int not null check(qty>0)
);
recreate table tlog(
    sel_id int,
    unit varchar(31),
    handle_mode varchar(50),
    fb_gdscode int,
    trn_id int default current_transaction,
    dts timestamp default 'now'
);
create index tlog_fb_gdscode on tlog(fb_gdscode);
commit;

recreate table tmx(
    id int primary key using index tmx_pk,
    op int not null check(op in(1,2)),
    unique(id, op) using index tmx_unq_for_tdx_fk
);
create descending index tmx_id_desc on tmx(id);
commit;

recreate sequence g;
commit;

alter table tdx add constraint tdx_fk
    foreign key (pid, op) references tmx(id, op)
    on delete cascade
    on update cascade;
commit;

set term ^;
create or alter trigger tdx_bi for tdx active before insert as
    declare v_qty int;
    declare v_stt varchar(128);
begin
    -- Multiply rows in either in TQ1 or TQ2 - depending on new.op value
    -- (which is generated randomly and can be 1 or 2).
    -- Number of created rows = int values of new.qty,
    --  which is being inserted in tdx.qty field:
    v_stt = 'insert into ' || iif( new.op=1, 'tq1', 'tq2' ) || '(id, pid) values(:a_id, :a_pid)';
    v_qty = new.qty;
    while (v_qty>0) do
    begin
        execute statement (v_stt) ( a_id := gen_id(g,1), a_pid := new.pid );
        v_qty = v_qty - 1;
    end
end
^

create or alter trigger tdx_bud for tdx active before update or delete as
    declare v_sel_id int;
    declare v_handle_mode varchar(50);
begin
    if (deleting) then  -- remove all rows in TQ1 and TQ2 which have pid = old.pid
        begin
            v_sel_id=old.pid;
            v_handle_mode='del in tq1'; delete from tq1 q where q.pid = old.pid;
            v_handle_mode='del in tq2'; delete from tq2 q where q.pid = old.pid;
        end
    else -- updating: MOVE all rows related to document, with DELETION in SOURCE
        begin
            v_sel_id=new.pid;
            if ( new.op = 1 ) then begin
              -- SOURCE = TQ2, TARGET = TQ1
              v_handle_mode='ins tq1, del in tq2 - before cursor'; -- to be logged in case of exc.
              for select id, pid from tq2 where pid = new.pid as cursor c
              do begin
                  v_handle_mode='try: ins tq1, del in tq2, id='||c.id;
                  insert into tq1(id, pid) values (c.id, c.pid);
                  delete from tq2 where current of c;
              end
            end
            else begin
              -- SOURCE = TQ1, TARGET = TQ2
              v_handle_mode='ins tq2, del in tq1 - before cursor'; -- to be logged in case of exc.
              for select id, pid from tq1 where pid = new.pid as cursor c
              do begin
                  v_handle_mode='try: ins tq2, del in tq1, id='||c.id;
                  insert into tq2(id, pid) values (c.id, c.pid);
                  delete from tq1 where current of c;
              end
            end
        end -- deleting or updating branch
when any do
    begin
      in autonomous transaction do
      insert into tlog(sel_id, unit, handle_mode, fb_gdscode)
      values( :v_sel_id, 'tdx_bud', :v_handle_mode, gdscode);

      exception;

    end
end

^set term ;^
commit;
--------------------------------------------------------------------------------
set term ^;
create or alter procedure p_tmx_add_doc(m_rows int) returns(sel_id int)
as
    declare v_op int;
    declare i int;
    declare const_qty int = 100;
begin
    -- NB: trigger tdx_bi will "multiply" rows in either TQ1 or TQ2,
    -- number of added rows = new.qty:
    insert into tmx(id, op) values( gen_id(g,1), iif(rand()<0.5, 1, 2))
    returning id, op into sel_id, v_op;
    i=0;
    while (i < m_rows) do
    begin
        insert into tdx(id, pid, op, qty) values( gen_id(g,1), :sel_id, :v_op, 1+rand()*(:const_qty-1) );
        i=i+1;
    end
    suspend;
end
^

create or alter procedure p_tmx_hadle_doc returns(sel_id int, result char(3))
as
    declare v_min_id double precision;
    declare v_max_id double precision;
    declare v_rnd_id double precision;
    declare r double precision;
    declare v_sel_id int;
    declare const_qty int = 100;
    declare v_handle_mode varchar(50);
begin
    -- Performs action with ONE "doc", depending on random value (:r)
    -- if :r < 0.25 then add new "doc"
    -- else if :r < 0.75 then make update field tmx.op (this fires cascade update
    --                   in detail (tdx) and "exchange" operation between tq1 & tq2)
    -- else - remove "doc" (this fires cascade deletion in detail (tdx) and also
    --                      removing of all corresp. rows in tq1 & tq2)
    r = rand();
    if (r < 0.25) then
        begin
          select p.sel_id,'add' from p_tmx_add_doc( 1 + rand()*(:const_qty-1) ) p
          into sel_id, result;
        end
    else
        begin
            select id from tmx order by id rows 1 into v_min_id;
            select id from tmx order by id desc rows 1 into v_max_id;
            v_rnd_id = v_min_id - 0.5 + rand() * ( v_max_id - v_min_id + 1);
            select id from tmx where id >= :v_rnd_id order by id rows 1 into sel_id;
            if ( r < 0.75 ) then
                begin
                    v_handle_mode='upd in tmx, id='||sel_id;
                    update tmx m set m.op = iif(m.op=1, 2, 1) where id = :sel_id
                    returning 'upd' into result;
                end
            else
                begin
                    v_handle_mode='del in tmx, id='||sel_id;
                    delete from tmx m where id = :sel_id
                    returning 'del' into result;
                end
        end

    suspend;

when any do
    begin
      in autonomous transaction do
      insert into tlog(sel_id, unit, handle_mode, fb_gdscode)
      values( :sel_id, 'p_tmx_hadle_doc', :v_handle_mode, gdscode);

      exception;

    end
end

^

create or alter function fn_halt_test returns int
as
    declare result int;
begin
    -- Used in .sql to immediatelly stop test (via set bail on) if one of the
    -- following errors registered in tlog: PK / UQ / FK violation.
    select first 1 g.fb_gdscode
    from tlog g
                       where g.fb_gdscode
                       in (
                                 335544665 -- unique_key_violation (violation of PRIMARY or UNIQUE KEY constraint "T1_XY" on table "T1")
                                ,335544349 -- no_dup (attempt to store duplicate value (visible to active transactions) in unique index "T2_XY") - without UNQ constraint
                                ,335544466 -- violation of FOREIGN KEY constraint @1 on table @2
                                ,335544838 -- Foreign key reference target does not exist (when attempt to ins/upd in DETAIL table FK-field with value for which parent ID has been changed or deleted - even in uncommitted concurrent Tx)
                                ,335544839 -- Foreign key references are present for the record  (when attempt to upd/del in PARENT table PK-field and rows in DETAIL (no-cascaded!) exists for old value)
                           ) -- in
    into result;
    return result;
end
^
set term ;^
commit;

------------------------------- initial data filling ---------------------------
set term ^;
execute block as
    declare n int = 500;
    declare const_qty int = 100;
    declare i int;
begin
    while (n>0) do
    begin
        execute procedure p_tmx_add_doc(1 + rand()*(:const_qty-1)) returning_values i;
        n = n-1;
    end
end
^set term ;^
commit;
(он отрабатывает на достаточно серьёзном серваке примерно за 1 минуту, т.к. добавляет много начальных данных)

Вот запрос, который надо "размножить" 100500 млн раз, чтобы натравливание на него isql'ей не приводило к частым переконнектам:
file = `tq_run.sql`; требуется размножить его текст многократно, чтобы получить большой скрипт
Код: 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.
set term ^;
execute block as
    declare n int = -1;
begin
    n=fn_halt_test();
    if ( n > 0 ) then
      exception ex_bad_error_encountered using (:n);
end
^ set term ;^
set bail off;

commit; set transaction snapshot no wait;
set term ^;
execute block returns(trn_id int, dts varchar(12), sel_id int, result varchar(3))
as
  declare n int = 10;
begin
  trn_id = current_transaction;
  while ( n>0 ) do begin
    select sel_id, result from p_tmx_hadle_doc into sel_id, result;
    dts=substring(cast(cast('now' as timestamp) as varchar(24)) from 12 for 12);
    suspend;
    n=n-1;
  end
end
^ set term ;^
--------------------

И вот батник:
file = `tq_run.bat`, запускать с параметром, равным числу запускаемых окон-молотилок
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
setlocal enabledelayedexpansion enableextensions

@rem path to ISQL utility, without trailing backslash:
set fbc=C:\MIX\firebird\fb25\bin

@rem path where temp logs will be created:
set tmpdir=c:\temp\tq

md %tmpdir% 2>nul
del %tmpdir%\tq_run_*.log 2>nul
del %tmpdir%\tq_run_*.err 2>nul
if not .%1.==.. set /a winq=%1
if .%winq%.==.0. set winq=10
@echo off
for /l %%i in (1, 1, %winq%) do (
  set str=%fbc%\isql 192.168.0.220/3333:/var/db/fb30/tq.fdb -n -i tq_run.sql 1^>%tmpdir%\tq_run_%%i.log 2^>%tmpdir%\tq_run_%%i.err
  echo str = !str!
  start /min cmd /c !str!
)
goto end
:end

Батник надо подправить под свои переменные окружения.

Если запустить этот батник вот так: tq_run.bat 50 - то будет открыто 50 isql'ей, которые начнут молотьбу.
В аттаче - сжатый скрипт tq_run.sql, который есть результат 100500 размножений вышеприеденного .sql.

Если в базе будет зарегистрировано одно из след. исключений:
Код: plaintext
1.
2.
3.
4.
5.
335544665 -- unique_key_violation (violation of PRIMARY or UNIQUE KEY constraint "T1_XY" on table "T1")
335544349 -- no_dup (attempt to store duplicate value (visible to active transactions) in unique index "T2_XY") - without UNQ constraint
335544466 -- violation of FOREIGN KEY constraint @1 on table @2
335544838 -- Foreign key reference target does not exist (when attempt to ins/upd in DETAIL table FK-field with value for which parent ID has been changed or deleted - even in uncommitted concurrent Tx)
335544839 -- Foreign key references are present for the record  (when attempt to upd/del in PARENT table PK-field and rows in DETAIL (no-cascaded!) exists for old value)
- то тест автоматически остановится.
В трейсе при этом будет:
Код: plaintext
1.
2.
3.
4.
5.
2014-08-11T23:59:14.2470 (5617:0x7f4f0e46f838) ERROR AT JStatement::execute
        /var/db/fb30/tq.fdb (ATT_304, SYSDBA:NONE, NONE, TCPv4:192.168.43.62)
        C:\1INSTALL\FB25SNAP\bin\isql.exe:2136
335544517 : exception 25
335544382 : EX_BAD_ERROR_ENCOUNTERED
335545016 : at least one forbidden error found, gdscode=335544665

Ну так вот.
При запуске 50 окон достаточно быстро (через 5-10 минут) тест самоостановится и в таблице TLOG будет вот это:
Код: sql
1.
select * from tlog where fb_gdscode=335544665

SEL_IDUNITHANDLE_MODEFB_GDSCODETRN_IDDTS1165462tdx_budtry: ins tq2, del in tq1, id=11663973355446651842812.08.2014 00:06:06.8621165462p_tmx_hadle_docupd in tmx, id=11654623355446651843012.08.2014 00:06:06.8922476196tdx_budtry: ins tq1, del in tq2, id=25775263355446651867912.08.2014 00:07:06.4623147023tdx_budtry: ins tq2, del in tq1, id=31792543355446651868012.08.2014 00:07:06.5273147023p_tmx_hadle_docupd in tmx, id=31470233355446651868112.08.2014 00:07:06.5542476196p_tmx_hadle_docupd in tmx, id=24761963355446651868412.08.2014 00:07:06.6262534478tdx_budtry: ins tq1, del in tq2, id=25881503355446651868612.08.2014 00:07:07.5462534478p_tmx_hadle_docupd in tmx, id=25344783355446651868712.08.2014 00:07:07.5701831720tdx_budtry: ins tq1, del in tq2, id=19156083355446651869812.08.2014 00:07:13.6041831720p_tmx_hadle_docupd in tmx, id=18317203355446651869912.08.2014 00:07:13.642

Как такое может происходить ?

PS.
В ddl-коде, приведенном выше, на самом деле есть одна "ошибка проектирования": таблицы TQ1 & TQ2 должны ссылаться полем PID не на TDX. P ID, а на TDX. ID . Из-за этой ошибки получается, что при удалении уже первой из строки документа в TDX сразу грохаются (или же переносятся между TQ1 и TQ2) не только строки, относящиеся к "первой из деталей", но и строки ко всем остальным "деталям" данного документа. То есть, TQ1.PID & TQ2.PID ссылаются на самом деле не на строку-деталь, а на строку-заголовок документа.
Однако, сиё не может (как мну кажется) влиять на появление PK/UK-violation'ов.
...
Рейтинг: 0 / 0
12.08.2014, 02:53
    #38717742
Таблоид
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
ТаблоидPS.
В ddl-коде, приведенном выше, на самом деле есть одна "ошибка проектирования": таблицы TQ1 & TQ2 должны ссылаться полем PID не на TDX. P ID, а на TDX. ID .Подправил код, теперь TQ1 & TQ2 ссылаются своим PID на отдельную строку: TDX.ID, а не на документ, как было ошибочно сделано ранее.
Кроме того, добавил FK на каждую из этих таблиц, для верности (но без каскада).
И всё равно при запуске 100 молотилок лезет та же грабля: через 5-7 минут какое-то из isql-окон получает PK-violation:
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
2014-08-12T02:37:14.5610 (5617:0x7f4f0e474138) ERROR AT JStatement::fetch
        /var/db/fb30/tq.fdb (ATT_566, SYSDBA:NONE, NONE, TCPv4:192.168.43.96)
        C:\MIX\firebird\fb25\bin\isql.exe:1892
335544665 : violation of PRIMARY or UNIQUE KEY constraint "INTEG_243" on table "TQ1"
335545072 : Problematic key value is ("ID" = 957327)
335544842 : At trigger 'TDX_BUD' line: 23, col: 19
At trigger 'CHECK_127'
At procedure 'P_TMX_HADLE_DOC' line: 33, col: 21
335544842 : At trigger 'TDX_BUD' line: 44, col: 7
At trigger 'CHECK_127'
At procedure 'P_TMX_HADLE_DOC' line: 33, col: 21
- что соответствует вот этому:
Код: sql
1.
2.
3.
4.
5.
6.
              for select id, pid from tq2 where pid = new.id as cursor c
              do begin
                  v_handle_mode='try ins tq1, del in tq2, id='||c.id; -- to be logged in case of exc.
                  insert into tq1(id, pid) values (c.id, c.pid);
                  delete from tq2 where current of c;
              end




DDL :
Код: 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.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
250.
251.
252.
253.
254.
255.
256.
257.
258.
259.
260.
261.
262.
263.
recreate exception ex_bad_error_encountered 'at least one forbidden error found, gdscode=@1';
commit;
create or alter procedure p_tmx_hadle_doc as begin end;
create or alter procedure p_tmx_add_doc as begin end;
create or alter function fn_halt_test returns int as begin end;
set term ^;
execute block as
begin
  begin execute statement 'drop trigger tdx_bi'; when any do begin end end
  begin execute statement 'drop trigger tdx_bud'; when any do begin end end
  begin execute statement 'drop trigger tmx_bud'; when any do begin end end
end
^set term ;^
commit;

recreate table tq1(
    id int primary key using index tq1_pk,
    pid int,
    x int default 1
);
-- replaced with FK: create index tq1_pid on tq1(pid);
commit;

recreate table tq2(
    id int primary key using index tq2_pk,
    pid int,
    x int default 1
);
-- replaced with FK: create index tq2_pid on tq2(pid);
commit;

recreate table tdx( -- detail for `tmx` table
    id int primary key using index tdx_pk,
    pid int,
    op int not null check(op in(1,2)),
    qty int not null check(qty>0)
);
commit;

recreate table tmx(
    id int primary key using index tmx_pk,
    op int not null check(op in(1,2)),
    unique(id, op) using index tmx_unq_for_tdx_fk
);
create descending index tmx_id_desc on tmx(id);
commit;

recreate table tlog(
    sel_id int,
    unit varchar(31),
    handle_mode varchar(50),
    fb_gdscode int,
    trn_id int default current_transaction,
    dts timestamp default 'now'
);
create index tlog_fb_gdscode on tlog(fb_gdscode);
commit;

recreate sequence g;
commit;

-- FK constraints:
alter table tdx add constraint tdx_fk
    foreign key (pid, op) references tmx(id, op)
    on delete cascade
    on update cascade;
commit;
 alter table tq1 add constraint tq1_fk
    foreign key (pid) references tdx(id);
alter table tq2 add constraint tq2_fk
    foreign key (pid) references tdx(id); 
commit;

-- Triggers:
set term ^;
create or alter trigger tdx_ a i for tdx active  after  insert as 
    -- NB: changed 'before' to 'after' due to FK from TQ1 & TQ2 ==> TDX
    declare v_qty int;
    declare v_stt varchar(128);
begin
    -- Multiply rows in either in TQ1 or TQ2 - depending on new.op value
    -- (which is generated randomly and can be 1 or 2).
    -- Number of created rows = int values of new.qty,
    -- which is being inserted in tdx.qty field:
    v_stt = 'insert into ' || iif( new.op=1, 'tq1', 'tq2' ) || '(id, pid) values(:a_id, :a_pid)';
    v_qty = new.qty;
    while (v_qty>0) do
    begin
        execute statement (v_stt) ( a_id := gen_id(g,1), a_pid := new .id  );
        v_qty = v_qty - 1;
    end
end
^

create or alter trigger tdx_bud for  tdx  active before update or delete as
    declare v_sel_id int;
    declare v_handle_mode varchar(50);
begin
    if (deleting) then -- remove all rows in TQ1 and TQ2 which have pid = old.pid
        begin
            v_sel_id=old.pid; -- to be logged in case of exc.
            v_handle_mode='del in tq1, pid='||old.id; -- to be logged in case of exc.
            delete from tq1 q where q.pid = old .id ;

            v_handle_mode='del in tq2, pid='||old.id; -- to be logged in case of exc.
            delete from tq2 q where q.pid = old .id ;
        end
    else -- updating: MOVE all rows related to document, with DELETION in SOURCE
        begin
            v_sel_id=new.pid; -- to be logged in case of exc.
            if ( new.op = 1 ) then begin
              -- SOURCE = TQ2, TARGET = TQ1
              --v_handle_mode='ins tq1, del in tq2 - before cursor'; -- to be logged in case of exc.
              for select id, pid from tq2 where pid = new .id  as cursor c
              do begin
                  v_handle_mode='try ins tq1, del in tq2, id='||c.id; -- to be logged in case of exc.
                  insert into tq1(id, pid) values (c.id, c.pid); -- ::: NB ::: PK  c.id - does NOT change!
                  delete from tq2 where current of c;
              end
            end
            else begin
              -- SOURCE = TQ1, TARGET = TQ2
              --v_handle_mode='ins tq2, del in tq1 - before cursor'; -- to be logged in case of exc.
              for select id, pid from tq1 where pid = new .id  as cursor c
              do begin
                  v_handle_mode='try ins tq2, del in tq1, id='||c.id; -- to be logged in case of exc.
                  insert into tq2(id, pid) values (c.id, c.pid);  -- ::: NB ::: PK  c.id - does NOT change!
                  delete from tq1 where current of c;
              end
            end
        end -- deleting or updating branch
when any do
    begin
      in autonomous transaction do
      insert into tlog(sel_id, unit, handle_mode, fb_gdscode)
      values( :v_sel_id, 'tdx_bud', :v_handle_mode, gdscode);

      exception;

    end
end
^
set term ;^
commit;
--------------------------------------------------------------------------------
set term ^;
create or alter procedure p_tmx_add_doc(m_rows int) returns(sel_id int)
as
    declare v_op int;
    declare i int;
    declare const_qty int = 100;
begin
    -- Creates new "doc" and filling it with random number of rows:
    insert into tmx(id, op) values( gen_id(g,1), iif(rand()<0.5, 1, 2))
    returning id, op into sel_id, v_op;
    i=0;
    while (i < m_rows) do
    begin
        -- NB: trigger tdx_bi will "multiply" rows in either TQ1 or TQ2,
        -- number of added rows = new.qty:
        insert into tdx(id, pid, op, qty) values( gen_id(g,1), :sel_id, :v_op, 1+rand()*(:const_qty-1) );
        i=i+1;
    end
    suspend;
end
^

create or alter procedure p_tmx_hadle_doc returns(sel_id int, result char(3))
as
    declare v_min_id double precision;
    declare v_max_id double precision;
    declare v_rnd_id double precision;
    declare r double precision;
    declare v_sel_id int;
    declare const_qty int = 100;
    declare v_handle_mode varchar(50);
begin
    -- Performs action with ONE "doc", depending on random value (:r)
    -- if :r < 0.10 then add new "doc"
    -- else if :r < 0.90 then make update field tmx.op (this fires cascade update
    --                   in detail (tdx) and "exchange" operation between tq1 & tq2)
    -- else - remove "doc" (this fires cascade deletion in detail (tdx) and also
    --                      removing of all corresp. rows in tq1 & tq2)
    r = rand();
    if (r <  0.10  ) then
        begin
          v_handle_mode='before call p_tmx_add_doc'; -- to be logged in case of exc.
          select p.sel_id,'add' from p_tmx_add_doc( 1 + rand()*(:const_qty-1) ) p
          into sel_id, result;
        end
    else
        begin
            select id from tmx order by id rows 1 into v_min_id;
            select id from tmx order by id desc rows 1 into v_max_id;
            v_rnd_id = v_min_id - 0.5 + rand() * ( v_max_id - v_min_id + 1);
            select id from tmx where id >= :v_rnd_id order by id rows 1 into sel_id;
            if ( r <  0.90  ) then
                begin
                    v_handle_mode='upd in tmx, id='||sel_id; -- to be logged in case of exc.
                    update tmx m set m.op = iif(m.op=1, 2, 1) where id = :sel_id
                    returning 'upd' into result;
                end
            else
                begin
                    v_handle_mode='del in tmx, id='||sel_id; -- to be logged in case of exc.
                    delete from tmx m where id = :sel_id
                    returning 'del' into result;
                end
        end

    suspend;

when any do
    begin
      in autonomous transaction do
      insert into tlog(sel_id, unit, handle_mode, fb_gdscode)
      values( :sel_id, 'p_tmx_hadle_doc', :v_handle_mode, gdscode);

      exception;

    end
end

^

create or alter function fn_halt_test returns int
as
    declare result int;
begin
    -- Used in .sql to immediatelly stop test (via set bail on) if one of the
    -- following errors registered in tlog: PK / UQ / FK violation.
    select first 1 g.fb_gdscode
    from tlog g
                       where g.fb_gdscode
                       in (
                                 335544665 -- unique_key_violation (violation of PRIMARY or UNIQUE KEY constraint "T1_XY" on table "T1")
                                ,335544349 -- no_dup (attempt to store duplicate value (visible to active transactions) in unique index "T2_XY") - without UNQ constraint
                                ,335544466 -- violation of FOREIGN KEY constraint @1 on table @2
                                ,335544838 -- Foreign key reference target does not exist (when attempt to ins/upd in DETAIL table FK-field with value for which parent ID has been changed or deleted - even in uncommitted concurrent Tx)
                                ,335544839 -- Foreign key references are present for the record  (when attempt to upd/del in PARENT table PK-field and rows in DETAIL (no-cascaded!) exists for old value)
                           ) -- in
    into result;
    return result;
end
^
set term ;^
commit;

------------------------------- initial data filling ---------------------------
set term ^;
execute block as
    declare n int = 100;
    declare const_qty int = 100;
    declare i int;
begin
    while (n>0) do
    begin
        execute procedure p_tmx_add_doc(1 + rand()*(:const_qty-1)) returning_values i;
        n = n-1;
    end
end
^set term ;^
commit;
(исправления выделены болдом).
...
Рейтинг: 0 / 0
12.08.2014, 09:32
    #38717809
Таблоид
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
... и даже если добавить вот такой before-триггерок на master-таблицу, который будет явно лочить все подчинённые записи в tdx:
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
create or alter trigger tmx_bud for tmx active before update or delete as
  declare v_id int;
begin
    for
        select id from tdx d
        where d.pid = old.id
        for update with lock
        into v_id
    do begin
        -- nop! just ensure that we really lock all detail records --
    end
end

- всё равно не помогает.
Конструкция держится подольше, конечно. Но через час (при грузилове от 150 окон) всё равно разломается на том же месте - загадочном нарушении PK таблицы TQx:
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
2014-08-12T03:50:37.9760 (5617:0x7f4ea8a60b00) ERROR AT JStatement::fetch
        /var/db/fb30/tq.fdb (ATT_742, SYSDBA:NONE, NONE, TCPv4:192.168.0.201)
        C:\1Install\FIREBIRD_2_5\bin\isql.exe:920
335544665 : violation of PRIMARY or UNIQUE KEY constraint "INTEG_275" on table " TQ1 "
335545072 : Problematic key value is ("ID" = 9791401)
335544842 : At trigger 'TDX_BUD' line: 23, col: 19
At trigger 'CHECK_143'
At procedure 'P_TMX_HADLE_DOC' line: 33, col: 21
335544842 : At trigger 'TDX_BUD' line: 44, col: 7
At trigger 'CHECK_143'
At procedure 'P_TMX_HADLE_DOC' line: 33, col: 21

DDL :
Код: 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.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
250.
251.
252.
253.
254.
255.
256.
257.
258.
259.
260.
261.
262.
263.
264.
265.
266.
267.
268.
269.
270.
271.
272.
273.
274.
recreate exception ex_bad_error_encountered 'at least one forbidden error found, gdscode=@1';
commit;
create or alter procedure p_tmx_hadle_doc as begin end;
create or alter procedure p_tmx_add_doc as begin end;
create or alter function fn_halt_test returns int as begin end;
set term ^;
execute block as
begin
  begin execute statement 'drop trigger tdx_ai'; when any do begin end end
  begin execute statement 'drop trigger tdx_bud'; when any do begin end end
  begin execute statement 'drop trigger tmx_bud'; when any do begin end end
end
^set term ;^
commit;

recreate table tq1(
    id int primary key using index tq1_pk,
    pid int,
    x int default 1
);
-- replaced with FK: create index tq1_pid on tq1(pid);
commit;

recreate table tq2(
    id int primary key using index tq2_pk,
    pid int,
    x int default 1
);
-- replaced with FK: create index tq2_pid on tq2(pid);
commit;

recreate table tdx( -- detail for `tmx` table
    id int primary key using index tdx_pk,
    pid int,
    op int not null check(op in(1,2)),
    qty int not null check(qty>0)
);
commit;

recreate table tmx(
    id int primary key using index tmx_pk,
    op int not null check(op in(1,2)),
    unique(id, op) using index tmx_unq_for_tdx_fk
);
create descending index tmx_id_desc on tmx(id);
commit;

recreate table tlog(
    sel_id int,
    unit varchar(31),
    handle_mode varchar(50),
    fb_gdscode int,
    trn_id int default current_transaction,
    dts timestamp default 'now'
);
create index tlog_fb_gdscode on tlog(fb_gdscode);
commit;

recreate sequence g;
commit;

-- FK constraints:
alter table tdx add constraint tdx_fk
    foreign key (pid, op) references tmx(id, op)
    on delete cascade
    on update cascade;
commit;
alter table tq1 add constraint tq1_fk
    foreign key (pid) references tdx(id);
alter table tq2 add constraint tq2_fk
    foreign key (pid) references tdx(id);
commit;

-- Triggers:
set term ^;
create or alter trigger tdx_ai for tdx active after insert as
    declare v_qty int;
    declare v_stt varchar(128);
begin
    -- Multiply rows in either in TQ1 or TQ2 - depending on new.op value
    -- (which is generated randomly and can be 1 or 2).
    -- Number of created rows = int values of new.qty,
    -- which is being inserted in tdx.qty field:
    v_stt = 'insert into ' || iif( new.op=1, 'tq1', 'tq2' ) || '(id, pid) values(:a_id, :a_pid)';
    v_qty = new.qty;
    while (v_qty>0) do
    begin
        execute statement (v_stt) ( a_id := gen_id(g,1), a_pid := new.id );
        v_qty = v_qty - 1;
    end
end
^

create or alter trigger tdx_bud for tdx active before update or delete as
    declare v_sel_id int;
    declare v_handle_mode varchar(50);
begin
    if (deleting) then -- remove all rows in TQ1 and TQ2 which have pid = old.pid
        begin
            v_sel_id=old.pid; -- to be logged in case of exc.
            v_handle_mode='del in tq1, pid='||old.id; -- to be logged in case of exc.
            delete from tq1 q where q.pid = old.id;

            v_handle_mode='del in tq2, pid='||old.id; -- to be logged in case of exc.
            delete from tq2 q where q.pid = old.id;
        end
    else -- updating: MOVE all rows related to document, with DELETION in SOURCE
        begin
            v_sel_id=new.pid; -- to be logged in case of exc.
            if ( new.op = 1 ) then begin
              -- SOURCE = TQ2, TARGET = TQ1
              --v_handle_mode='ins tq1, del in tq2 - before cursor'; -- to be logged in case of exc.
              for select id, pid from tq2 where pid = new.id as cursor c
              do begin
                  v_handle_mode='try ins tq1, del in tq2, id='||c.id; -- to be logged in case of exc.
                  insert into tq1(id, pid) values (c.id, c.pid);
                  delete from tq2 where current of c;
              end
            end
            else begin
              -- SOURCE = TQ1, TARGET = TQ2
              --v_handle_mode='ins tq2, del in tq1 - before cursor'; -- to be logged in case of exc.
              for select id, pid from tq1 where pid = new.id as cursor c
              do begin
                  v_handle_mode='try ins tq2, del in tq1, id='||c.id; -- to be logged in case of exc.
                  insert into tq2(id, pid) values (c.id, c.pid);
                  delete from tq1 where current of c;
              end
            end
        end -- deleting or updating branch
when any do
    begin
      in autonomous transaction do
      insert into tlog(sel_id, unit, handle_mode, fb_gdscode)
      values( :v_sel_id, 'tdx_bud', :v_handle_mode, gdscode);

      exception;

    end
end
^
create or alter trigger  tmx_bud for tmx  active before update or delete as
  declare v_id int;
begin
    for
        select id from tdx d
        where d.pid = old.id
        for update with lock
        into v_id
    do begin
        -- nop! just ensure that we really lock all detail records --
    end
end
^
set term ;^
commit;
--------------------------------------------------------------------------------
set term ^;
create or alter procedure p_tmx_add_doc(m_rows int) returns(sel_id int)
as
    declare v_op int;
    declare i int;
    declare const_qty int = 100;
begin
    -- Creates new "doc" and filling it with random number of rows:
    insert into tmx(id, op) values( gen_id(g,1), iif(rand()<0.5, 1, 2))
    returning id, op into sel_id, v_op;
    i=0;
    while (i < m_rows) do
    begin
        -- NB: trigger tdx_bi will "multiply" rows in either TQ1 or TQ2,
        -- number of added rows = new.qty:
        insert into tdx(id, pid, op, qty) values( gen_id(g,1), :sel_id, :v_op, 1+rand()*(:const_qty-1) );
        i=i+1;
    end
    suspend;
end
^

create or alter procedure p_tmx_hadle_doc returns(sel_id int, result char(3))
as
    declare v_min_id double precision;
    declare v_max_id double precision;
    declare v_rnd_id double precision;
    declare r double precision;
    declare v_sel_id int;
    declare const_qty int = 100;
    declare v_handle_mode varchar(50);
begin
    -- Performs action with ONE "doc", depending on random value (:r)
    -- if :r < 0.10 then add new "doc"
    -- else if :r < 0.90 then make update field tmx.op (this fires cascade update
    --                   in detail (tdx) and "exchange" operation between tq1 & tq2)
    -- else - remove "doc" (this fires cascade deletion in detail (tdx) and also
    --                      removing of all corresp. rows in tq1 & tq2)
    r = rand();
    if (r < 0.10) then
        begin
          v_handle_mode='before call p_tmx_add_doc'; -- to be logged in case of exc.
          select p.sel_id,'add' from p_tmx_add_doc( 1 + rand()*(:const_qty-1) ) p
          into sel_id, result;
        end
    else
        begin
            select id from tmx order by id rows 1 into v_min_id;
            select id from tmx order by id desc rows 1 into v_max_id;
            v_rnd_id = v_min_id - 0.5 + rand() * ( v_max_id - v_min_id + 1);
            select id from tmx where id >= :v_rnd_id order by id rows 1 into sel_id;
            if ( r < 0.90 ) then
                begin
                    v_handle_mode='upd in tmx, id='||sel_id; -- to be logged in case of exc.
                    update tmx m set m.op = iif(m.op=1, 2, 1) where id = :sel_id
                    returning 'upd' into result;
                end
            else
                begin
                    v_handle_mode='del in tmx, id='||sel_id; -- to be logged in case of exc.
                    delete from tmx m where id = :sel_id
                    returning 'del' into result;
                end
        end

    suspend;

when any do
    begin
      in autonomous transaction do
      insert into tlog(sel_id, unit, handle_mode, fb_gdscode)
      values( :sel_id, 'p_tmx_hadle_doc', :v_handle_mode, gdscode);

      exception;

    end
end

^

create or alter function fn_halt_test returns int
as
    declare result int;
begin
    -- Used in .sql to immediatelly stop test (via set bail on) if one of the
    -- following errors registered in tlog: PK / UQ / FK violation.
    select first 1 g.fb_gdscode
    from tlog g
                       where g.fb_gdscode
                       in (
                                 335544665 -- unique_key_violation (violation of PRIMARY or UNIQUE KEY constraint "T1_XY" on table "T1")
                                ,335544349 -- no_dup (attempt to store duplicate value (visible to active transactions) in unique index "T2_XY") - without UNQ constraint
                                ,335544466 -- violation of FOREIGN KEY constraint @1 on table @2
                                ,335544838 -- Foreign key reference target does not exist (when attempt to ins/upd in DETAIL table FK-field with value for which parent ID has been changed or deleted - even in uncommitted concurrent Tx)
                                ,335544839 -- Foreign key references are present for the record  (when attempt to upd/del in PARENT table PK-field and rows in DETAIL (no-cascaded!) exists for old value)
                           ) -- in
    into result;
    return result;
end
^
set term ;^
commit;

------------------------------- initial data filling ---------------------------
set term ^;
execute block as
    declare n int = 30;
    declare const_qty int = 100;
    declare i int;
begin
    while (n>0) do
    begin
        execute procedure p_tmx_add_doc(1 + rand()*(:const_qty-1)) returning_values i;
        n = n-1;
    end
end
^set term ;^
commit;
ЗЫ. За полторы минуты эту же ошибку огребли две других транзакции:
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
SQL> set width handle_mode 30;
SQL> select * from tlog where fb_gdscode=335544665;
  SEL_ID UNIT                            HANDLE_MODE                      FB_GDSCODE       TRN_ID                       D
======== =============================== ============================== ============ ============ =======================
 9777367 tdx_bud                         try ins tq1, del in tq2, id=97    335544665        74617 2014-08-12 03:50:37.800
 9777367 p_tmx_hadle_doc                 upd in tmx, id=9777367            335544665        74618 2014-08-12 03:50:37.862
 2058595 tdx_bud                         try ins tq1, del in tq2, id=20    335544665        75015 2014-08-12 03:51:52.663
 2058595 p_tmx_hadle_doc                 upd in tmx, id=2058595            335544665        75016 2014-08-12 03:51:52.712
12461075 tdx_bud                         try ins tq1, del in tq2, id=12    335544665        75034 2014-08-12 03:52:02.562
12461075 p_tmx_hadle_doc                 upd in tmx, id=12461075           335544665        75035 2014-08-12 03:52:02.765
...
Рейтинг: 0 / 0
12.08.2014, 13:38
    #38718127
Таблоид
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
мнды... тут еще и FK можно получить, оказывается (правда, долго ждать пришлось: полтора часа молотьбы).

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
2014-08-12T11:44:41.7750 (5617:0x7f4f0e4730d8) ERROR AT JStatement::fetch
        /var/db/fb30/tq.fdb (ATT_1129, SYSDBA:NONE, NONE, TCPv4:192.168.43.96)
        C:\MIX\firebird\fb25\bin\isql.exe:3256
335544466 : violation of FOREIGN KEY constraint " TQ1_FK " on table " TQ1 "
-- это взбрыкнула связь TDX 1 => N TQx при попытке каскада удалить строку в TDX:
335544839 : Foreign key references are present for the record 
335545072 : Problematic key value is ("ID" = 15119525)
335544842 : At trigger 'CHECK_176'
At procedure 'P_TMX_HADLE_DOC' line: 44, col: 21

И строка эта соотв-вует попытке удаления master-записи:
Код: plaintext
1.
2.
                    delete from  tmx  m where id = :sel_id
                    returning 'del' into result;

Но срабатывание каскадного триггера (FK tdx->tmx, on delete cascade) должно вроде бы дёргать удаление всех подчинённых строк в tdx, а тамошний НЕ-каскадный триггер tdx_bud ( before delete or update) должен сначала грохать все подчинённые строки в TQ1 & TQ2:
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
    if (deleting) then -- remove all rows in TQ1 and TQ2 which have pid = old.pid
        begin
            v_sel_id=old.pid; -- to be logged in case of exc.
            v_handle_mode='del in tq1, pid='||old.id; -- to be logged in case of exc.
             delete from tq1 q  where q.pid = old.id;

            v_handle_mode='del in tq2, pid='||old.id; -- to be logged in case of exc.
             delete from tq2  q where q.pid = old.id;
        end
    else -- updating: MOVE all rows related to document, with DELETION in SOURCE
        . . .

ЯНХНП! Как вообще такое может происходить, что "путается" порядок срабатывания триггеров ?!
...
Рейтинг: 0 / 0
12.08.2014, 13:51
    #38718154
Dimitry Sibiryakov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
ТаблоидКак вообще такое может происходить, что "путается" порядок срабатывания
триггеров ?!
Ты апдейтишь первичный и вторичный ключи в триггерах. Триггера работают в пределах
транзакции, ключи - надтранзакционны. Результат в виде гонки потоков закономерен.
Posted via ActualForum NNTP Server 1.5
...
Рейтинг: 0 / 0
12.08.2014, 14:02
    #38718180
hvlad
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
ТаблоидЯНХНП!+100500
...
Рейтинг: 0 / 0
12.08.2014, 14:07
    #38718192
Таблоид
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
Dimitry SibiryakovТы апдейтишь первичный и вторичный ключи в триггерах. Триггера работают в пределах транзакции, ключи - надтранзакционны. Результат в виде гонки потоков закономерен.Погодь! Триггера работают в строго определённом порядке.
Если я делаю: delete from <master> where id = ... - то сначала будет залочена строка в master-таблице (TMX), а затем начнётся срабатывание триггера-каскада, который будет удалять строки в detail-таблице (TDX).
Но на этой detail-таблице также есть триггер на before_delete_or_update (обычный, не каскад). И он вызывает уже обработку строк в таблицах-"внуках".

Таким обр., когда дело доходит до обработки строк таблиц-"внуков" (TQ1 & TQ2), их родительская строка в TDX уже залочена .
Кроме того, логика работы исключает доступ к отдельным строкам таблиц-"внуков", минуя обработку их родительской строки: в коде нет операторов "прямого доступа" к TQx, в т.ч. инсертов в них.

Почему тогда получаются такие "эффекты", как было показано выше ? Как можно "пробиться" к стейтменту вида:
Код: plaintext
1.
2.
                  insert into tq1( id , pid) values ( c.id , c.pid);
                  delete from tq2 where current of c;
- и нарушить PK таблицы TQ1, если этот стейтмент записан только в триггере tdx_bud for tdx active before update ?

ЗЫ. И еще: если бы всё упиралось только в "надтранзакционность ключей", то всё легко можно было бы воспроизвести без гемора типа открытия 100 окон.
...
Рейтинг: 0 / 0
12.08.2014, 14:13
    #38718208
hvlad
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
ТаблоидЕсли я делаю: delete from <master> where id = ... - то сначала будет залочена строка в master-таблице (TMX)Нет конечно
Напоминаю краткий порядок работы апдейта

1. чтение записи
2. вычисление новых значений (то, что написано в UPDATE ... SET <>)
3. триггеры before
4. физический апдейт
5. триггеры after

Как видишь, "залочена" строка будет только после триггеров before
...
Рейтинг: 0 / 0
12.08.2014, 15:03
    #38718303
Таблоид
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
hvladНапоминаю краткий порядок работы апдейта

1. чтение записи
2. вычисление новых значений (то, что написано в UPDATE ... SET <>)
3. триггеры before
4. физический апдейт
5. триггеры after

Как видишь, "залочена" строка будет только после триггеров before Гут. А теперь делаю вот так:
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
create or alter procedure p_tmx_hadle_doc returns(sel_id int, result char(3))
as
. . .
begin
    . . .
    select id from  tmx  where id >= :v_rnd_id order by id rows 1  FOR UPDATE WITH LOCK  into sel_id;
    if ( :random < 0.75 ) then update tmx set ... where id = :sel_id;
    else delete from tmx where id = :sel_id;
    . . .
end
То есть, сначала делаю явный лок master-записи, и только после - её апдейт или удаление, что уже вызовет каскадный триггер на детали (tdx) и далее на "внуков" (tq1 & yq2). Этот лок мастер-строки должен (вроде) гарантировать, что НИКТО не сможет добраться до строк таблицы tdx и соотв-щих строк "внуков", пока текущая транзакция, залочившая мастер-строку, не закоммитится или не откатится.

Запускаю 80 молотилок, жду 5-10 минут.

И получаю то же самое. Это чем объяснить ?

ЗЫ. Получил сиё на первом варианте DDL, который приведен в стартовом посте (и который с "ошибкой проектирования"; сделал так потому, что на нём эффект проявляется быстрее всего).
...
Рейтинг: 0 / 0
12.08.2014, 15:12
    #38718320
hvlad
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
ТаблоидЭто чем объяснить ?Я не вникал в твой тест. См. выше 16431065
...
Рейтинг: 0 / 0
12.08.2014, 15:13
    #38718321
hvlad
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
ТаблоидТо есть, сначала делаю явный лок master-записи, и только после - её апдейт или удаление, что уже вызовет каскадный триггер на детали (tdx) и далее на "внуков" (tq1 & yq2). Этот лок мастер-строки должен (вроде) гарантировать, что НИКТО не сможет добраться до строк таблицы tdx и соотв-щих строк "внуков", пока текущая транзакция, залочившая мастер-строку, не закоммитится или не откатится.С чего бы это ?
...
Рейтинг: 0 / 0
12.08.2014, 15:26
    #38718351
Таблоид
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
hvladТаблоидТо есть, сначала делаю явный лок master-записи, и только после - её апдейт или удаление, что уже вызовет каскадный триггер на детали (tdx) и далее на "внуков" (tq1 & yq2). Этот лок мастер-строки должен (вроде) гарантировать, что НИКТО не сможет добраться до строк таблицы tdx и соотв-щих строк "внуков" , пока текущая транзакция, залочившая мастер-строку, не закоммитится или не откатится.С чего бы это ?Если твой вопрос про выделенную фразу, то... что в ней неожиданного ?

Код: plaintext
1.
SQL> recreate table t(id int); commit;
SQL> insert into t values(1); commit;

Script (file = 'rq'):
Код: 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.
rollback;
set transaction no wait;
set term ^;
execute block returns(msg varchar(25)) as
  declare i int;
begin
  select id from t order by id rows 1 for update with lock  into i;
  msg='waw! i capture id='||i;
  suspend;
end
^set term ;^

 session #1 
SQL> in rq;

MSG
=========================
waw! i capture id=1

 session #2 
SQL> in rq;

MSG
=========================
Statement failed, SQLSTATE = HY000
concurrent transaction number is 552314
After line 3 in file rq

PS. Кстати, на одном из выполнений этого "драматического сценария" ФБ.... завалился с багчеком (при вызове в session #2 скрипта`rq`).
В логе:
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
oel64   Tue Aug 12 15:19:51 2014
        Database: /var/db/fb30/tq.fdb
        internal Firebird consistency check (decompression overran buffer (179), file: sqz.cpp line: 282)


oel64   Tue Aug 12 15:20:10 2014
        /opt/fb30trnk/bin/fbguard: /opt/fb30trnk/bin/firebird terminated abnormally (-1)



oel64   Tue Aug 12 15:20:10 2014
        /opt/fb30trnk/bin/fbguard: guardian starting /opt/fb30trnk/bin/firebird

Бактрассу могу прислать, если нужно.
...
Рейтинг: 0 / 0
12.08.2014, 15:35
    #38718372
Таблоид
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
hvladТаблоидЭто чем объяснить ?Я не вникал в твой тест.Ну вот же, смотри, всё просто:

Код: plaintext
1.
2.
3.
4.
TMX (имеет before-триггер на update or delete, который дёргает select * from TDX for update with lock)
 |
 +--- TDX (FK1 tdx.pid references tmx on delete cascade; FK2 tdx.pid, tdx.op ref. tmx on update cascade)
       +----- TQ1 (при update tdx set tdx.op=2 -  перенос  строк из TQ1 в TQ2)
       +----- TQ2 (при update tdx set tdx.op=1 -  перенос  строк из TQ2 в TQ1)

Теперь делаю так:

Код: plaintext
1.
2.
select <some_id> from TMX  for update with lock  into sel_id;
if ( :random < 0.75) then update TMX set op = iif( op=1, 2, 1) where id = :sel_id;
else delete from TMX where id = :sel_id;

Кто и как сможет подобраться к строкам в TQ1 & TQ2, которые подчинены строкам в TDX, а те - подчинены залоченной сейчас строке в TMX, - если я буду держать незакоммиченной транзакцию, которая выполнила "синенькие " стейтменты ?
...
Рейтинг: 0 / 0
12.08.2014, 15:46
    #38718394
hvlad
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
ТаблоидЕсли твой вопрос про выделенную фразу, то... что в ней неожиданного ?Каким боком "лок мастер-строки" должен что-то гарантировать про строки в детальных таблицах ???
Каким боком твой пример с конкурентным апдейтом одной таблицы относится к проблемам отцов и детей ???
...
Рейтинг: 0 / 0
12.08.2014, 15:52
    #38718414
Dimitry Sibiryakov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
ТаблоидКстати, на одном из выполнений этого "драматического сценария" ФБ....
завалился с багчеком
Это известный побочный эффект стабильности курсора в сочетании с double update. У Влада
всё руки не доходят его пофиксить. Так что ты проверь где ты из триггеров апдейтишь ту же
запись, которая уже была изменена основным запросом.
Posted via ActualForum NNTP Server 1.5
...
Рейтинг: 0 / 0
12.08.2014, 15:59
    #38718428
Таблоид
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
hvladТаблоидЕсли твой вопрос про выделенную фразу, то... что в ней неожиданного ?Каким боком "лок мастер-строки" должен что-то гарантировать про строки в детальных таблицах ???
Каким боком твой пример с конкурентным апдейтом одной таблицы относится к проблемам отцов и детей ???Дык посмотри в код, плз: я НЕ делаю никаких явных (в ХП или в EB) апдейтов или удалений в "детях" и "внуках". Это делают триггеры. Как раз при апдейте или удалении одной строки мастер-таблицы.

Прочитав вот это:hvlad1. чтение записи
2. вычисление новых значений (то, что написано в UPDATE ... SET <>)
3. триггеры before
4. физический апдейт
5. триггеры after

Как видишь, "залочена" строка будет только после триггеров before - я решил предотвратить конкуретный доступ before-треггеров к одним и тем же строкам деталь-таблицы (TDX).
Для этого:
1) добавил select from TMX for update with lock;
2) в before-update-delete триггере таблицы TMX - добавил аналогичный for update with lock для всей группы подчинённых мастеру строк таблицы TDX.

Объясни мне прямо, по-пролетарски: этого разве не достаточно для сериализации обращений к деталь-строкам ? И если не достаточно, то как она, сериализация эта, может быть нарушена ?
...
Рейтинг: 0 / 0
12.08.2014, 16:07
    #38718453
Таблоид
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
Dimitry SibiryakovТаблоидКстати, на одном из выполнений этого "драматического сценария" ФБ....
завалился с багчекомЭто известный побочный эффект стабильности курсора в сочетании с double update.Воспроизводится трудно (надо повторять в двух окнах по нескольку раз: "SQL> in rq;", затем выйти из обоих окон, снова зайти и опять попытаться - тогда, возможно , ФБ опять завалится; в общем, танцы с бубном).

Dimitry SibiryakovТак что ты проверь где ты из триггеров апдейтишь ту же запись, которая уже была изменена основным запросом.Это как это ?! даже если бы я запускал транзакции не с NO wait, а с LOCK TIMEOUT nn, они бы сразу по лбу получали в ввиду того, что запись уже была обновлена и изменений закоммичено... Здесь вам не орацле, где по-тихому всё перетирается :-)
...
Рейтинг: 0 / 0
12.08.2014, 16:18
    #38718486
Dimitry Sibiryakov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
ТаблоидВоспроизводится трудно
Воспроизводится легко:
create table t...
create trigger tt after update t .... update t ... where id=new.id; ...

Всё, почти любой update этой таблицы приведёт к багчеку. Тот же механизм, что и в CORE-4369.

ТаблоидЭто как это ?!
Это как выше. Другим транзакциям - облом, но этой же - всё дозволено.
Posted via ActualForum NNTP Server 1.5
...
Рейтинг: 0 / 0
12.08.2014, 16:47
    #38718548
Таблоид
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
Dimitry SibiryakovТаблоидЭто как это ?!Это как выше. Другим транзакциям - облом, но этой же - всё дозволено.Ты про то, может ли транзакция несколько раз проапдейтить один и тот же документ ? Я добавил контроль на это, введя GTT'шку, в которую транзакция складывает уже обработанные документы:
Код: 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.
create or alter procedure p_tmx_hadle_doc returns(sel_id int, result char(3))
as
    declare v_min_id double precision;
    declare v_max_id double precision;
    declare v_rnd_id double precision;
    declare r double precision;
    declare v_sel_id int;
    declare const_qty int = 100;
    declare v_handle_mode varchar(50);
begin
    -- Performs action with ONE "doc", depending on random value (:r)
    -- if :r < 0.25 then add new "doc"
    -- else if :r < 0.75 then make update field tmx.op (this fires cascade update
    --                   in detail (tdx) and "exchange" operation between tq1 & tq2)
    -- else - remove "doc" (this fires cascade deletion in detail (tdx) and also
    --                      removing of all corresp. rows in tq1 & tq2)
    r = rand();
    if (r < 0.25) then
        begin
          select p.sel_id,'add' from p_tmx_add_doc( 1 + rand()*(:const_qty-1) ) p
          into sel_id, result;
        end
    else
        begin
            select id from tmx order by id rows 1 into v_min_id;
            select id from tmx order by id desc rows 1 into v_max_id;
            v_rnd_id = v_min_id - 0.5 + rand() * ( v_max_id - v_min_id + 1);

            select m.id
            from tmx m
            where m.id >= :v_rnd_id
                   and not exists(select * from gtt_trn_log g where g.sel_id = m.id) 
            order by id rows 1
            FOR UPDATE WITH LOCK
            into sel_id;

            if ( r < 0.75 ) then
                begin
                    v_handle_mode='upd in tmx, id='||sel_id;
                    update tmx m set m.op = iif(m.op=1, 2, 1) where id = :sel_id
                    returning 'upd' into result;
                end
            else
                begin
                    v_handle_mode='del in tmx, id='||sel_id;
                    delete from tmx m where id = :sel_id
                    returning 'del' into result;
                end
        end

     insert into gtt_trn_log(sel_id) values(:sel_id); 

    suspend;

when any do
    begin
      in autonomous transaction do
      insert into tlog(sel_id, unit, handle_mode, fb_gdscode)
      values( :sel_id, 'p_tmx_hadle_doc', :v_handle_mode, gdscode);

      exception;

    end
end
Увы и ах - не помогает. Всё равно за 10 минут 100 молотилок разламывают это: лезет unique violation.
...
Рейтинг: 0 / 0
12.08.2014, 17:09
    #38718594
hvlad
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
Dimitry SibiryakovВоспроизводится легко:
create table t...
create trigger tt after update t .... update t ... where id=new.id; ...Это рекурсия, бесконечная.
Воспроизводится легко, подтверждаю
...
Рейтинг: 0 / 0
12.08.2014, 17:26
    #38718614
Таблоид
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
hvladDimitry SibiryakovВоспроизводится легко:
create table t...
create trigger tt after update t .... update t ... where id=new.id; ...Это рекурсия, бесконечная.
Воспроизводится легко, подтверждаюгы... только сейчас вгляделся, что делает этот триггер :-)
Но у мну нет такого изврата: "апдейтить самого себя на событие апдейта"

2 hvlad: ну так объясни, плз, про мои сериализационные потуги : как там может быть такое, что две транзакции одновременно дёргают before-триггера деталей, при том что обе они предварительно должны выполнить select for update with lock строки мастер-таблицы ?
...
Рейтинг: 0 / 0
12.08.2014, 17:35
    #38718624
hvlad
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
Таблоидну так объясни, плз 16431597
...
Рейтинг: 0 / 0
12.08.2014, 17:38
    #38718626
Dimitry Sibiryakov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
ТаблоидНо у мну нет такого изврата: "апдейтить самого себя на событие апдейта"

Этот изврат не обязан быть непосредственным и рекурсия не обязана быть бесконечной. Всё
дело в волшебных пузырьках ловкости рук. Твои схемы имеют привычку перерастать в
can of spaghetti где легко не заметить какой-нибудь побочный эффект.
Posted via ActualForum NNTP Server 1.5
...
Рейтинг: 0 / 0
12.08.2014, 17:44
    #38718633
Таблоид
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
hvlad 16431597
...
Рейтинг: 0 / 0
12.08.2014, 17:49
    #38718641
Таблоид
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину.
Dimitry SibiryakovТвои схемы имеют привычку перерастать в can of spaghetti где легко не заметить какой-нибудь побочный эффект.Ну вот скажи мне: где в этой "картинке" может быть побочный эффект ?
Просто, В ТУПУЮ, выполняю сначала явный лок мастер-строки (table TMX), затем - апдейт её. При этом before update триггер этой мастер-строки делает такую же ТУПЕЙШУЮ вещь: ставит явные локи на все подчинённые деталь-строки (table TDX). И только затем идут уже обмены кортежами между таблицами-"внуками" (см триггер tdx_bud for tdx active before update or delete).
...
Рейтинг: 0 / 0
Форумы / Firebird, InterBase [игнор отключен] [закрыт для гостей] / Нарушение PK при интенсивном обмене данными между таблицами. Не могу понять причину. / 25 сообщений из 43, страница 1 из 2
Целевая тема:
Создать новую тему:
Автор:
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


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