powered by simpleCommunicator - 2.0.61     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / Firebird, InterBase [игнор отключен] [закрыт для гостей] / Две тр-ции хотят update одной строки: может ли быть deadlock вместо concurrent_transaction
7 сообщений из 7, страница 1 из 1
Две тр-ции хотят update одной строки: может ли быть deadlock вместо concurrent_transaction
    #38613858
Таблоид
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
hi all

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

И есть ХП (её имя - sp_try_lock_record), которая получает на вход имя таблицы и значение ID'шника, а делает при этом следующее (почти псевдокод):
Код: 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.
create or alter procedure . . .
as
   . . .
begin
    -- проверить, что транзакция сейчас запущена или с NO_wait или с lock_timeout. 
    -- Иначе - выброс:
    execute procedure sp_check_nowait_or_timeout;
    
    -- если транзакция запущена с lock_timeout >0, то получить с настроек приложения max число повторов; 
    -- иначе (при no_wait) установить это число = 1.
    -- Число это далее обозначено v_retries, код его получения не принципиален (через rdb$get_context):

    i = 1;
    while ( i < v_retries ) do 
    begin
        begin -- доп блок для возможности регистрации в логе ошибки
                v_stt = ' select rdb$db_key  from '||a_table||' where id = :x  for update with lock ';
                execute statement (v_stt) ( x := :a_id ) into v_dbkey;

                if ( v_dbkey is NOT null) then
                begin
                   -- То, что запись получена для апдейта, еще не означает, что она не была изменена
                   -- за период этого ожидания. Делаем холостой апдейт, чтобы получить при обнаружении 
                   -- изменений ошибку 335544451 (update_conflict) и вывалиться с  этой  ошибкой (и это - НЕ deadlock!).
                    v_stt = ' update  '||a_table||' set id=id  where rdb$db_key = :x ';
                    execute statement (v_stt) ( x := v_dbkey );
                end
                i = v_retries;

                LEAVE; -- получили блокировку, исключений нет ==> всё ОК, выходим отсюда.

        when  gdscode concurrent_transaction 
            if ( i <= v_retries ) then
            begin
                if ( i = v_retries ) then  
                begin
                   -- добавить в лог текст и код ошибки. 
                   -- Текст получаю и заранее созданного справочника кодов ФБ-ошибок:
                   execute procedure sp_add_to_abend_log( v_log_msg, gdscode );

                   exception; -- т.к. макс число попыток залочить строку исчерпано

                end
            end
        when any do
            exception;    
        end -- begin_end с возможностью регистрации в логе ошибки

        i = i+1;

    end

end 
^
Ну так вот. Перед тем, как ваять эту ХП, я делал тестовый execute block и выяснял, какой номер будет иметь ошибка, когда нелтьзя залочить запись, но при этом НЕТ признаков дедлока. То есть, когда запись банально занята другой тр-цией, а мы, в свою очередь НИЧЕГО не держим из того, что нужно конкуренту.
Эта ошибка имеет мнемону = "concurrent_transaction", её код = 335544878.
Пробные запуски в двух isql'ях также показывали, что возникает именно ЭТА ошибка и ХП пишет именно её номер в лог.

А теперь вопрос.
Как такое может быть, что в этой ХП возникает НЕ concurrent_transaction, а deadlock ?
При условии, конечно же, что две транзакции лезут только за одной записью в одну и ту же таблицу.
...
Рейтинг: 0 / 0
Две тр-ции хотят update одной строки: может ли быть deadlock вместо concurrent_transaction
    #38613979
dimitr
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Таблоид,

во-первых, "мнемона" concurrent_transaction это вторичный код ошибки (номер транзакции-конкурента), первичный код должен быть update_conflict. Во-вторых, для дедлока нужен всего-навсего встречный (в разном направлении) последовательный апдейт двух записей. С одной записью он если и вылезет когда-либо, то только в случае ошибки лок-менеджера или кеш-менеджера (вместе с lock conversion denied). Закладывать на это логику ХП никакого смысла не имеет.
...
Рейтинг: 0 / 0
Две тр-ции хотят update одной строки: может ли быть deadlock вместо concurrent_transaction
    #38614170
Таблоид
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
dimitrво-первых, "мнемона" concurrent_transaction это вторичный код ошибки (номер транзакции-конкурента), первичный код должен быть update_conflict. Во-вторых, для дедлока нужен всего-навсего встречный (в разном направлении) последовательный апдейт двух записей. С одной записью он если и вылезет когда-либо, то только в случае ошибки лок-менеджера или кеш-менеджера (вместе с lock conversion denied). Закладывать на это логику ХП никакого смысла не имеет.Вроде бы понятно. Хотя и не очень.

Если сделать так:

1) создать таблицу в 1 строку, которая залочена сессией #1 :
Код: plaintext
1.
2.
SQL> recreate table t(id int); commit;
SQL> insert into t values(1); commit;
SQL> update t set id=id where id=1;

2) выполнить execute block, запускаемый сессией #2:
Код: 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.
commit; set transaction snapshot lock timeout 2;
set list on;
set term ^;
execute block returns( txt varchar(40), gds int ) as
declare x int;
begin
    gds = null;
    begin
        txt='try select locked row for update:';
        select id from t where id=1 for update with lock into x;
    when any do 
        begin
           gds = gdscode;
           suspend;
        end  
    end

    begin
        txt='try to direct update locked row:';
        update t set id=id where id=1;
    when any do 
        begin
           gds = gdscode;
           suspend;
        end  
    end
end
^set term ;^
set list off;

- то результат вып-я этого кода будет:

Код: plaintext
1.
2.
3.
4.
5.
TXT                             try select locked row for update:
GDS                             335544878 -- это "concurrent_transaction"

TXT                             try to direct update locked row:
GDS                             335544336 -- а это "deadlock"

То же самое, выполняемое просто как команды в isql:

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
SQL> select id from t where id=1 for update with lock;

          ID
============
Statement failed, SQLSTATE = HY000
concurrent transaction number is 4772130


SQL> update t set id=id where id=1;
Statement failed, SQLSTATE = 40001
 deadlock 
 -update conflicts with concurrent update 
-concurrent transaction number is 4772130

Т.е. "update conflicts" всё таки есть, но он почему-то спрятался за дедлоком. Который непонятно, с какого будуна лезет :-/
...
Рейтинг: 0 / 0
Две тр-ции хотят update одной строки: может ли быть deadlock вместо concurrent_transaction
    #38614216
dimitr
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ТаблоидТ.е. "update conflicts" всё таки есть, но он почему-то спрятался за дедлоком
да, все именно так. И будет так оставаться, к сожалению (из-за обратной совместимости).
...
Рейтинг: 0 / 0
Две тр-ции хотят update одной строки: может ли быть deadlock вместо concurrent_transaction
    #38614229
Таблоид
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
А тогда еще вопросик.

Вот такой блок, с двумя when-блоками:

Код: 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.
commit; set transaction snapshot lock timeout 2;
set list on;
set term ^;
execute block returns( txt varchar(40), gds int ) as
declare x int;
begin
    gds = null;
    begin
        txt='try select locked row for update:';
        select id from t where id=1 for update with lock into x;
    when  gdscode concurrent_transaction, gdscode deadlock do
        begin
           gds = gdscode;
           suspend;
        end
    when any do
        begin
            txt='something else happens...';
            gds = gdscode;
            suspend;
        end
    end
end
^set term ;^
set list off;

- выдаёт (при обломе залочить запись):

Код: plaintext
1.
2.
Statement failed, SQLSTATE = HY000
request synchronization error
After line 3 in file trylock.sql


И ничего не выдаёт на гор а саспендом.

То же самое, если заменить select for update на "просто" update.
Это бага или так и должно быть ?
...
Рейтинг: 0 / 0
Две тр-ции хотят update одной строки: может ли быть deadlock вместо concurrent_transaction
    #38614269
dimitr
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Таблоид,

старая бага
...
Рейтинг: 0 / 0
Две тр-ции хотят update одной строки: может ли быть deadlock вместо concurrent_transaction
    #38614294
Таблоид
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Да, и еще тут нарыл. Может, так и должно быть, но странно как-то.

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.
recreate exception ex_nowait_or_timeout_required 'transaction must start in NO WAIT mode or with LOCK_TIMEOUT.';
recreate table test_log(dts timestamp default 'now', msg varchar(50), fb_gdscode int);
recreate table t(id int);
insert into t values(1);
commit;
set term ^;
create or alter procedure try_lock_test(
  a_table varchar(31),
  a_id dm_ids
) as
    declare v_dbkey dm_dbkey;
    declare v_retries int;
    declare v_timeout int;
    declare v_stt varchar(255);
    declare i int;
begin

    -- Check that current Tx run in NO wait or with lock_timeout.
    -- Otherwise raise error: performance degrades almost to zero.
    --execute procedure sp_check_nowait_or_timeout;
    if ( rdb$get_context('SYSTEM', 'LOCK_TIMEOUT') < 0 ) then
        exception ex_nowait_or_timeout_required;
    
    -- Tries to LOCK record if current Tx was started with LOCK TIMEOUT > 0
    -- or NO wait; log errors if they occur.
    -- Raises: concurrent_transaction OR deadlock (TWO kind of exceptions!)

    v_timeout = cast( rdb$get_context('SYSTEM', 'LOCK_TIMEOUT') as int );
    if ( v_timeout >= 0) then -- ">=" - do not forget NO wait mode!
    begin

        v_retries = iif( v_timeout > 0, 5, 1);
        i=1;
        while (i <= v_retries) do
        begin
            begin
                v_stt = 'select rdb$db_key from '||a_table||' where id = :x for update with lock';
                -- this can raise exc. "concurrent_transaction (335544878)":
                 execute statement (v_stt) ( x := :a_id ) into v_dbkey; 

                if ( v_dbkey is NOT null) then
                begin
                    v_stt = 'update '||a_table||' set id=id where rdb$db_key = :x';
                    -- this can raise TWO exceptions at the same time:
                    -- 1) deadlock  (335544336)
                    -- 2) -update conflicts with concurrent update (335544451)
                    -- But in when-block we'll get only first of them ("deadlock").
                    -- Test: sql.ru/forum/actualutils.aspx?action=gotomsg&tid=1088890&msg=15879042
                    execute statement (v_stt) ( x := v_dbkey );
                    --if ( i > 1 ) then
                       --execute procedure sp_add_to_abend_log( v_prefix||' - achieve lock after '||(i-1)||' fault attempt(s).' );
                end
                i = v_retries;
                LEAVE;

            when gdscode concurrent_transaction, gdscode deadlock do
                begin
                    if ( i = v_retries ) then
                    begin
                        in autonomous transaction
                        do insert into test_log(msg, fb_gdscode) values('can`t lock row after '||:v_retries||' attempts', gdscode);
                         exception;  -- all number of retries exceeded: raise concurrent_transaction OR deadlock
                    end
                end
            when any do
                begin
                    in autonomous transaction
                    do insert into test_log(msg, fb_gdscode) values('smth else happens on iter = '||:i, gdscode);
                     exception; 
                end
            end
            i = i +1;
        end
    end
end
^ set term ;^
commit;

Test:

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
 session #1 
SQL> update t set id=id where id=1;

 session #2 
SQL> delete from test_log;  commit; 
SQL> set transaction read committed lock timeout 1;  
SQL> execute procedure try_lock_test('t', 1);
-- ждём 5 сек, после чего получаем:

Код: plaintext
1.
2.
3.
4.
Statement failed, SQLSTATE = 40001
deadlock
-concurrent transaction number is 4772739
-At procedure 'TRY_LOCK_TEST' line: 33, col: 17
-At procedure  'TRY_LOCK_TEST' line: 56, col: 25 

Очень хорошо. А теперь смотрим в лог:
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
                      DTS MSG                                                  FB_GDSCODE
========================= ================================================== ============
2014-04-14 14:53:48.0010  smth else happens on iter = 1                                 0
2014-04-14 14:53:50.0000  smth else happens on iter = 2                                 0
2014-04-14 14:53:50.0010  smth else happens on iter = 3                                 0
2014-04-14 14:53:52.0000  smth else happens on iter = 4                                 0
2014-04-14 14:53:52.0010  can`t lock row after 5 attempts                       335544336

1) Откудова там записи с "smth else happens on iter = ..." ? Разве не должен был when any отработать ТОЛЬКО в случае, ели исключение НЕ попало в список, указанный в ПЕРВОМ when-блоке (который "when gdscode concurrent_transaction, gdscode" deadlock) ?
2) Если даже допустить, что when any отрабатывает всегда (т.е. так, как в Сишном switch при забытом break управление идёт в след. case), то почему он отрабатывал пять раз, а "красный" exception - ни одного ?
...
Рейтинг: 0 / 0
7 сообщений из 7, страница 1 из 1
Форумы / Firebird, InterBase [игнор отключен] [закрыт для гостей] / Две тр-ции хотят update одной строки: может ли быть deadlock вместо concurrent_transaction
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


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