powered by simpleCommunicator - 2.0.61     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / Firebird, InterBase [игнор отключен] [закрыт для гостей] / mon$context_variables - пример для проверок непересек. диапазонов c "dirty reads"
4 сообщений из 4, страница 1 из 1
mon$context_variables - пример для проверок непересек. диапазонов c "dirty reads"
    #38549240
Таблоид
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
hi all.

Что-то интересно было бошку поломать над вот этим: http://www.sql.ru/forum/1075333/kak-izbezhat-peresekaushhihsya-otrezkov-vremeni-v-tablice

В итоге, случилось следующее.
DDL:
Код: sql
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.
set term ^;
create or alter trigger trgcommit on transaction commit as
begin
end^
execute block as begin
    begin
    execute statement 'drop sequence g';
    when any do begin end
    end
    begin
    execute statement 'create sequence g';
    when any do begin end
    end
end^
set term ;^
commit;

recreate table t(id int primary key, tid int default current_transaction, f1 int, f2 int);
commit;
create index t_f1 on t(f1);
create index t_f2 on t(f2);
commit;

recreate exception ex_crossed_dirty 'crossed_intervals_encountered_in_uncommitted_data';
recreate exception ex_crossed_flushed 'crossed_intervals_encountered_in_committed_data';
commit;

set term ^;
create or alter trigger T_AIU for T
active after insert or update position 0 as
begin
  rdb$set_context( 'USER_TRANSACTION', 'ID_NEW'||cast(new.id as varchar(11)), cast(new.id as varchar(11)) );
  rdb$set_context( 'USER_TRANSACTION', 'F1_NEW'||cast(new.id as varchar(11)), cast(new.f1 as varchar(11)) );
  rdb$set_context( 'USER_TRANSACTION', 'F2_NEW'||cast(new.id as varchar(11)), cast(new.f2 as varchar(11)) );
end^
set term ;^
commit;

set term ^;
create or alter trigger trgcommit on transaction commit as
declare variable v_mode varchar(1);
declare v_id int;
declare v_f1 int;
declare v_f2 int;
begin
    if (NOT exists(select * from mon$context_variables c where c.mon$transaction_id=current_transaction and c.mon$variable_name starting with 'ID_NEW'))
    then
      exit;

    -- first check uncommitted data in all other transactions:
    if (
            exists(
                with c as(
                    select
                        c.mon$transaction_id tid
                        ,substring(c.mon$variable_name from 3) vn
                        ,min(cast( iif( c.mon$variable_name starting with 'ID_NEW', c.mon$variable_value, null) as int) ) ctx_id_new
                        ,min(cast( iif( c.mon$variable_name starting with 'F1_NEW', c.mon$variable_value, null) as int) ) ctx_f1_new
                        ,min(cast( iif( c.mon$variable_name starting with 'F2_NEW', c.mon$variable_value, null) as int) ) ctx_f2_new
                    from mon$context_variables c
                    group by 1,2
                )
                select *
                from c ca join c cb
                    on ca.ctx_id_new<>cb.ctx_id_new
                        and sign(ca.ctx_f1_new - cb.ctx_f2_new) * sign(cb.ctx_f1_new - ca.ctx_f2_new)>0
                 ) -- exists
       )
    then
       exception ex_crossed_dirty;

    -- then check already committed data:
    if (
          exists(
                  select *
                  from
                      (
                        select tid, f1, f2 from t where tid = current_transaction
                      ) ta
                      join t tb on
                        ta.tid<>tb.tid
                        and (   ta.f1 between tb.f1 and tb.f2
                                or ta.f2 between tb.f1 and tb.f2
                                or tb.f1 between ta.f1 and ta.f2
                                or tb.f2 between ta.f1 and ta.f2
                            )

                ) -- exists
        )
     then
       exception ex_crossed_flushed;
end^
set term ;^
commit;

Пояснения.
В таблицу `t` добавлено поле tid, в которое всегда пишется current_transaction. Это поле нужно для различения транзакцией "своих" и "чужих" записей.
Когда аттач добавляет/меняет в рамках одной транзакции одну или несколько записей таблицы `t`, то срабатывание after-триггера приведет к merge в таблицу mon$context_variables записей:
MON$ATTACHMENT_IDMON$TRANSACTION_IDMON$VARIABLE_NAMEMON$VARIABLE_VALUE993F1_NEW1300993F1_NEW2100993F1_NEW350993F2_NEW1400993F2_NEW2200993F2_NEW3500993ID_NEW11993ID_NEW22993ID_NEW33 - для трёх стейтментов вида:
Код: plaintext
1.
2.
insert into t(id, f1, f2) values (gen_id(g,1),300, 400);
insert into t(id, f1, f2) values (gen_id(g,1),100, 200);
insert into t(id, f1, f2) values (gen_id(g,1), 50, 500);

Независимо от того, работает ли один аттач или у него есть конкуренты, меняющие поля f1 & f2 на недопустимые значения, запрос вида:
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
with c as(
    select
        c.mon$transaction_id tid
        ,substring(c.mon$variable_name from 3) vn
        ,min(iif( c.mon$variable_name='OPMODE', c.mon$variable_value, null)) ctx_opmode
        ,min(cast( iif( c.mon$variable_name starting with 'ID_NEW', c.mon$variable_value, null) as int) ) ctx_id_new
        ,min(cast( iif( c.mon$variable_name starting with 'F1_NEW', c.mon$variable_value, null) as int) ) ctx_f1_new
        ,min(cast( iif( c.mon$variable_name starting with 'F2_NEW', c.mon$variable_value, null) as int) ) ctx_f2_new
    -- select * from mon$context_variables c
    from mon$context_variables c
    group by 1,2
)
select *
from c ca join c cb
    on ca.ctx_id_new<>cb.ctx_id_new
        and sign(ca.ctx_f1_new - cb.ctx_f2_new) * sign(cb.ctx_f1_new - ca.ctx_f2_new)>0

- выдаст непустое множество.
СТЕ-часть этого запроса выглядит так (для тех же трёх стейтментов):TIDVNCTX_OPMODECTX_ID_NEWCTX_F1_NEWCTX_F2_NEW1089_NEW113004001089_NEW221002001089_NEW3350500- и мы ищем среди этих строк те, что пересекаются по f1...f2. Для поиска оных применяется широко известная в узких кругах формула: произведение разностей "координат" (a-d) * (c-b) отрезков [a...b] & [c...d] должно быть положительным числом. Функция sign() - просто перестраховка от num overflow при перемножении.

Разумеется, это будет так только если с базой работает SYSDBA или её владелец. Иначе выборка останется пустой: "чужие" данные mon$context_variables не видны непривилегированным юзерам.

Таким обр, можно отловить попытку изменения полей на "плохие" диапазоны, пересекающиеся либо с диапазонами этого же аттача, либо незакоммиченными значениями аттачей-конкурентов.
Что касается проверки наличия закоммиченных диапазонов, пересекающихся с записями, изменёнными в текущей транзакции, то это отлавливается примерно так же: выборка
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
                  select *
                  from
                      (
                        select tid, f1, f2 from t where tid = current_transaction
                      ) ta
                      join t tb on
                        ta.tid<>tb.tid
                        and (   ta.f1 between tb.f1 and tb.f2
                                or ta.f2 between tb.f1 and tb.f2
                                or tb.f1 between ta.f1 and ta.f2
                                or tb.f2 between ta.f1 and ta.f2
                            )

- должна быть пустой, иначе есть проблемы с пересечением.

Проверял без особого пристрастия: просто открыл одно, затем два окна, и вводил разные данные (на "силовой установке" с 300 коннектами проверку не делал :)).

test #1. Работа в одном окне. Транзакция пытается ввести несколько записей, в числе которых есть пересекающиеся диапазоны f1 & f2.
Код: 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.
$ /opt/fb25sc/bin/isql localhost/3252:/var/db/fb25/empty25.fdb
Database:  localhost/3252:/var/db/fb25/empty25.fdb
SQL> commit; set transaction read committed record_version no wait;
SQL> insert into t(id, f1, f2) values( gen_id(g,1), 100, 200);
SQL> insert into t(id, f1, f2) values( gen_id(g,1), 300, 400);
SQL> insert into t(id, f1, f2) values( gen_id(g,1), 150, 380);
SQL> commit;
Statement failed, SQLSTATE = 42000
exception 47
-EX_CROSSED_DIRTY
-crossed_intervals_encountered_in_uncommitted_data
-At trigger 'TRGCOMMIT' line: 30, col: 5

SQL> rollback; set transaction read committed record_version no wait;
SQL> insert into t(id, f1, f2) values( gen_id(g,1), 100, 200);
SQL> insert into t(id, f1, f2) values( gen_id(g,1), 300, 400);
SQL> insert into t(id, f1, f2) values( gen_id(g,1), 500, 600);
SQL> select * from t;

          ID          TID           F1           F2
============ ============ ============ ============
           4         1048          100          200
           5         1048          300          400
           6         1048          500          600

SQL> update t set f1=550, f2=610 where id=5;
SQL> select * from t;

          ID          TID           F1           F2
============ ============ ============ ============
           4         1048          100          200
           5         1048          550          610
           6         1048          500          600

SQL> commit;
Statement failed, SQLSTATE = 42000
exception 47
-EX_CROSSED_DIRTY
-crossed_intervals_encountered_in_uncommitted_data
-At trigger 'TRGCOMMIT' line: 30, col: 5

test #2. Работа в одном окне. Транзакция вводит сначала непересекающиеся диапазоны, а затем апдейтит поля f1 & f2 - увеличивает их на одно и то же значение. Новые диапазоны будут пересекаться с ПРЕЖНИМИ в рамках одной записи, однако они не будут пересекаться "между" записями. То есть, COMMIT должен пройти успешно:
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
SQL> rollback; set transaction read committed record_version no wait;
SQL> insert into t(id, f1, f2) values( gen_id(g,1), 100, 200);
SQL> insert into t(id, f1, f2) values( gen_id(g,1), 300, 400);
SQL> insert into t(id, f1, f2) values( gen_id(g,1), 500, 600);
SQL> update t set f1=f1+150, f2=f2+150;
SQL> select * from t;

          ID          TID           F1           F2
============ ============ ============ ============
           7         1051          250          350
           8         1051          450          550
           9         1051          650          750

SQL> commit;

test #3. Работа в двух окнах. Транзакции стартуют одновременно и вводят "плохие" диапазоны
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
 session #1 
SQL> delete from t; commit;
SQL> rollback; set transaction read committed record_version no wait;
SQL> insert into t(id, f1, f2) values( gen_id(g,1), 100, 200);
SQL> insert into t(id, f1, f2) values( gen_id(g,1), 300, 400);

 session #2 
SQL> rollback; set transaction read committed record_version no wait;
SQL> insert into t(id, f1, f2) values( gen_id(g,1),  50, 500);
SQL> commit;
Statement failed, SQLSTATE = 42000
exception 47
-EX_CROSSED_DIRTY
-crossed_intervals_encountered_in_ un committed_data
-At trigger 'TRGCOMMIT' line: 30, col: 5

 session #1 
SQL> commit;
Statement failed, SQLSTATE = 42000
exception 47
-EX_CROSSED_DIRTY
-crossed_intervals_encountered_in_ un committed_data
-At trigger 'TRGCOMMIT' line: 30, col: 5

test #4. Работа в двух окнах. Транзакция-1 успевает делать commit до того, как транзакция-2 изменит данные на "плохой диапазон". Транзакция-2 должна обломиться.
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
 session #1 
SQL> rollback; set transaction read committed record_version no wait;
SQL> insert into t(id, f1, f2) values( gen_id(g,1), 100, 200);
SQL> insert into t(id, f1, f2) values( gen_id(g,1), 300, 400);
SQL> commit;

 session #2 
SQL> rollback; set transaction read committed record_version no wait;
SQL> insert into t(id, f1, f2) values( gen_id(g,1),  50, 500);
SQL> commit;
Statement failed, SQLSTATE = 42000
exception 48
-EX_CROSSED_FLUSHED
-crossed_intervals_encountered_in_ committed _data
-At trigger 'TRGCOMMIT' line: 50, col: 6

Вроде бы работает, но... сомнения есть, что тут всё пучком.
Покритикуйте, плз. Или дайте контр-пример.
...
Рейтинг: 0 / 0
mon$context_variables - пример для проверок непересек. диапазонов c "dirty reads"
    #38549459
Таблоид
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Проверил силовым методом на 2.5 - вроде бы работает ОК.
Создал пару вспом. вьюх:
V_CROSS_M
Код: sql
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.
CREATE OR ALTER VIEW V_CROSS_M(
    TID_A,
    VN_A,
    ID_A,
    F1_A,
    F2_A,
    TID_B,
    VN_B,
    ID_B,
    F1_B,
    F2_B)
AS
with c as(
        select
            c.mon$transaction_id tid
            ,substring(c.mon$variable_name from 3) vn
            ,min(cast( iif( c.mon$variable_name starting with 'ID_NEW', c.mon$variable_value, null) as int) ) ctx_id_new
            ,min(cast( iif( c.mon$variable_name starting with 'F1_NEW', c.mon$variable_value, null) as int) ) ctx_f1_new
            ,min(cast( iif( c.mon$variable_name starting with 'F2_NEW', c.mon$variable_value, null) as int) ) ctx_f2_new
        from mon$context_variables c
        group by 1,2
    )
    select
     ca.tid tid_a, ca.vn vn_a, ca.ctx_id_new id_a, ca.ctx_f1_new f1_a, ca.ctx_f2_new f2_a
    ,cb.tid tid_b, cb.vn vn_b, cb.ctx_id_new id_b, cb.ctx_f1_new f1_b, cb.ctx_f2_new f2_b
    from c ca join c cb
        on ca.ctx_id_new<>cb.ctx_id_new
            and sign(ca.ctx_f1_new - cb.ctx_f2_new) * sign(cb.ctx_f1_new - ca.ctx_f2_new)>0
;

V_CROSS_T
Код: sql
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.
CREATE OR ALTER VIEW V_CROSS_T(
    ID_A,
    TID_A,
    F1_A,
    F2_A,
    ID_B,
    TID_B,
    F1_B,
    F2_B)
AS
select
 ta.id id_a, ta.tid tid_a, ta.f1 f1_a, ta.f2 f2_a
,tb.id id_b, tb.tid tid_b, tb.f1 f1_b, tb.f2 f2_b
from
  (
    select id, tid, f1, f2 from t where tid = 2229
  ) ta
  join t tb on
    (ta.id<>tb.id)
    and (   ta.f1 between tb.f1 and tb.f2
            or ta.f2 between tb.f1 and tb.f2
            or tb.f1 between ta.f1 and ta.f2
            or tb.f2 between ta.f1 and ta.f2
        )
;


А также скрипт, который вставляет в таблицу 1000 "пакетов" записей, каждый из которых содержит 20 строк с непересекающимися диапазонами f1...f2:
cross.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.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
set echo off;
show version;
commit;
set transaction read committed record_version no wait;
set stat on;
set term ^;
execute block as
  declare n int = 1000;
  declare k int = 20;
  declare f_min int = 0;
  declare f_max int = 1000;
  declare i int;
  declare m int;
  declare v1 int;
  declare v2 int;
begin
  for
    with recursive 
    nx as(
      select 1 m from rdb$database union all select r.m+1 from nx r where r.m < :n
    )
    select m from nx 
    order by rand()
  into m
  do begin
    in autonomous transaction do
    begin
        insert into t(id, f1, f2)
        with recursive data as(
          select 1 x from rdb$database union all select r.x+1 from data r where r.x < :k
        )
        select gen_id(g,1), :m*10000 + 100*d.x+rand()*50 v1, :m*10000 + 100*d.x+51+rand()*49
        from data d
        order by rand();
        when any do begin end
    end
  end
end^
set term ;^
set stat off;
--commit;
set echo on;
select count(*) from v_cross_m;
set echo off;
rollback;
set echo on;
select count(*) from v_cross_t;
select count(*) from t;
rollback;

Однако, он добавляет эти "пакеты" в случайном порядке (insert into t ... select from .... order by rand()).
Каждый пакет (20 строк) добавляется в автономной транзакции. При обломе внутри триггера на COMMIT исключение будет задушено, чтобы скрипт мог продолжать свою работу до упора.

Запускаю 30 isql'ей:
Код: plaintext
1.
2.
3.
4.
for /l %%x in (1, 1, 30) do (
  del cross_%%x.log 2>nul
  start isql 192.168.0.220/3252:/var/db/fb25/empty25.fdb -pag 0 -i cross.sql -m -o cross_%%x.log
  @rem start isql 192.168.0.220/3333:/var/db/fb30/empty.fdb -pag 0 -i cross.sql -m -o cross_%%x.log
)

По окончании их работы делаю коннект и подсчет строк из вьюхи v_cross_t - результат д.б. равен 0, что соблюдается.

Но! Идиллия закончилась при проверке на LI-T3.0.0.30872: запрос select count(*) from mon$context_variables, введенный при запущенных 30 isql'ях, взвис наглухо .
...
Рейтинг: 0 / 0
mon$context_variables - пример для проверок непересек. диапазонов c "dirty reads"
    #38549484
Таблоид
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
бНОПНЯ!
Вот такой набор стейтментов, вводимых в одну строку в isql:
Код: plaintext
SQL> commit; select current_timestamp from rdb$database; set stat on; insert into dup_context_variables select * from mon$context_variables; set stat off; select current_timestamp from rdb$database; commit;

- выполняется в ФБ-3 достаточно долго, минут 5.
Однако, я вижу издевательски одинаковые значение таймштампа (и такое же глумливое elapsed time):

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
2014-02-04 23:09:45.1630

Current memory = 119241632
Delta memory = 67573864
Max memory = 394335144
Elapsed time= 0.02 sec
Buffers = 2048
Reads = 3
Writes 3
Fetches = 19261

2014-02-04 23:09:45.1820

Про elapsed time = 0.02 - это один вопрос.
Но что там с current_timestamp: он-то почему вывелся так, словно был выполнен execute block ?
...
Рейтинг: 0 / 0
mon$context_variables - пример для проверок непересек. диапазонов c "dirty reads"
    #38549501
dimitr
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Таблоид,

весь твой пакет висел 5 минут на первом коммите или селекте, после чего выполнился за 20мс. Об этом честно говорят все цифры.
...
Рейтинг: 0 / 0
4 сообщений из 4, страница 1 из 1
Форумы / Firebird, InterBase [игнор отключен] [закрыт для гостей] / mon$context_variables - пример для проверок непересек. диапазонов c "dirty reads"
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


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