|
Параллельная работа с таблицей
|
|||
---|---|---|---|
#18+
Столкнулся с такой проблемой: требуется обеспечить выборку токенов (записей) из таблицы и их удаление (после какой-то обработки, в условия задачи не входит) двумя и более сессиями. Идеально было бы получить рост производительности при увеличении числа сессий. Токены имеют серийный номер, номер владельца, и тип операции (создать/добавить телефонный номер владельца/удалить). Токены для одного и того же владельца должны выдаваться в порядке возрастания номеров, даже если они выдаются разными сессиями. Пример. Таблица 1 Влад_1 Создать 2 Влад_1 Добавить номер 3 Влад_2 Создать 4 Влад_1 Удалить Имея 2 сессии, допустимый порядок выполнения Сес1 Сес2 1 3 2 4 Недопустимо, чтобы Сес2 выдала 2 или 4 до того как Сес1 выдаст (и удалит) 1. Выдачу токенов написал в хранимой процедуре, ее вызов и удаление токена после получения - в перле, но это не принципиально. Оказалось на удивление нетривиально, и что самое неприятное - производительность сильно падает при добавлении сессий. Пришлось добавлять поля locked для анализа "захвачен ли токен другой сессией" и session_id для предотвращения появления "сирот" после внепланового убийства сессии, задействовать SAVEPOINT Самый неприятный сюрприз от информикса - курсоры для UPDATE не могут иметь внутри "ORDER BY" Удивительно, что такая казалось бы популярная и простая задача не имеет легко находимого в гугле решения :) . Если кто-то хочет заучаствовать в ее решении, то вот структура таблицы токенов: create table test_token ( token_id serial not null , action_id CHAR(5), owner_number integer, locked smallint default 0, sid int8 ) mode row; И заголовок процедуры: CREATE PROCEDURE get_test2( p_num int default 0) RETURNING INT AS token_id, CHAR(5) AS t_action_id, INTEGER AS t_owner_number, SMALLINT AS eot; где p_num - Номер сессии ( используется для создания уникального имени отладочного файла) eot - 1 - признак конца транзакции, последняя запись для данного владельца. Поскольку из-за конфликтов блокировок транзакции могут откатываться, и ранее блокированные записи становиться свободными, то процедура проверяет наличие свободных токенов для текущего владельца и если они есть, сначала выдает их, с eot установленным в 0. Предлагаю всем желающим попробовать свои силы в этом элегантном программистском этюде и окунуться в чарующий мир блокировок Informix ! :) ---------- P.S. Внешняя программа : $dbh_ace = DBI->connect("dbi:Informix:$config{'dbname_ace'}", $config{'user_ace'}, $config{'pass_ace'}, { AutoCommit => 0, PrintError => 1, RaiseError => 0, ChopBlanks => 1 } ) or oops(" Connect failed $config{dbname_ace}", $DBI::errstr, 1); $dbh_ace->do("SET LOCK MODE TO WAIT 60"); -- разлочить токены - сироты $dbh_ace->do("UPDATE test_token SET locked = 0 WHERE locked = 1 AND (sid NOT IN (SELECT sid from sysmaster:syssessions) OR sid IS NULL)") or oops(" UPDATE test_token failed $config{dbname_ace}", $DBI::errstr, 0); $dbh_ace->do("SET LOCK MODE TO NOT WAIT"); my $get_test_tokens = $dbh_ace->prepare(" EXECUTE PROCEDURE get_test2()") or oops(" Prepare for EXECUTE PROC failed $config{dbname_ace}", $DBI::errstr, 1); my $del_test_token = $dbh_ace->prepare("DELETE FROM test_token WHERE token_id = ?") or oops(" Prepare for delete failed $config{dbname_ace}", $DBI::errstr, 1) ; $get_test_tokens->bind_columns( \$l_token_id, \$l_action, \$l_owner_number, \$eot); my $l_continue = 1; my $rand_sleep = 1; while ($l_continue) { $dbh_ace->do('BEGIN WORK'); $dbh_ace->do('SET ISOLATION CURSOR STABILITY RETAIN UPDATE LOCKS'); $get_test_tokens ->execute($num, $start_id) or oops("EXECUTE get_test2 failed", $DBI::errstr, 1); $l_continue = 0; while ( $get_test_tokens->fetch or ( $l_exception == 1) ) { $l_continue = 1; if ( $l_exception ) { $l_exception = 0; oops("FETCH failed $config{dbname_ace}", $DBI::errstr, 0); print "$num:ROLLBACK TRANSACTION AFTER FETCH\n" ; $| = 1; $dbh_ace->do("ROLLBACK WORK") or oops("ROLLBACK failed $config{dbname_ace}", $DBI::errstr, 1); last; } print "$num: Get token $l_token_id, action $l_action, owner_number $l_owner_number eot $eot \n"; $del_test_token ->execute( $l_token_id) or ( $l_rollback = 1 ); if ( $l_rollback ) { $dbh_ace->do("ROLLBACK WORK"); last; } else { if ( $eot == 1 ) { $dbh_ace->do('COMMIT WORK'); last; } } } } В таком вот аксепте ... |
|||
:
Нравится:
Не нравится:
|
|||
18.12.2019, 01:25 |
|
Параллельная работа с таблицей
|
|||
---|---|---|---|
#18+
А почему бы изначально не развести потоки обработки? Каждый поток берет свою запись и никаких конфликтов. Я в аналогичных случаях ввожу простое правило. Каждый поток обрабатывает записи у которых остаток от деления сериального номера на количество потоков равен номеру потока. Производительность растет практически линейно . ... |
|||
:
Нравится:
Не нравится:
|
|||
05.02.2020, 12:00 |
|
Параллельная работа с таблицей
|
|||
---|---|---|---|
#18+
cpr А почему бы изначально не развести потоки обработки? Каждый поток берет свою запись и никаких конфликтов. Я в аналогичных случаях ввожу простое правило. Каждый поток обрабатывает записи у которых остаток от деления сериального номера на количество потоков равен номеру потока. Производительность растет практически линейно . потому что записи принадлежащие одному владельцу должны выдаваться последовательно. А с вашим алгоритмом нет никакой гарантии что запись "удалить владельца" появится ПОСЛЕ записи "создать владельца". В результате мы останемся с неудаленным владельцем. ... |
|||
:
Нравится:
Не нравится:
|
|||
07.02.2020, 23:22 |
|
|
start [/forum/topic.php?fid=44&msg=39924099&tid=1606705]: |
0ms |
get settings: |
10ms |
get forum list: |
14ms |
check forum access: |
2ms |
check topic access: |
2ms |
track hit: |
31ms |
get topic data: |
11ms |
get forum data: |
3ms |
get page messages: |
43ms |
get tp. blocked users: |
2ms |
others: | 14ms |
total: | 132ms |
0 / 0 |