Конкурентные обновления одной записи - deadlock update...

Access Violation, некорректное выполнение запросов или вызовов API, ошибки утилит командной строки, в общем все, что вам мешает работать

Модераторы: kdv, dimitr

Ответить
iamhere
Сообщения: 21
Зарегистрирован: 27 дек 2005, 09:45

Конкурентные обновления одной записи - deadlock update...

Сообщение iamhere » 27 дек 2005, 09:57

Hi, All!

Есть у нас сайт, который работает с FB:
Server Version: LI-V1.5.2.4731 Firebird 1.5
Server Implementation: Firebird/linux Intel

Скрипты на php. С них идут обновления (счетчики увеличиваются) и часто имеется следующее:
deadlock update conflicts with concurrent update

Я это дело как увидел, очень удивился, потому что вроде не должно быть этого (http://ibase.ru/devinfo/norecver.htm).
Транзакция на обновление пускается с параметрами:
IBASE_WRITE | IBASE_COMMITTED | IBASE_WAIT |
IBASE_REC_NO_VERSION

Сформировал минимальный тест (база в 1 табличку, скрипт в 100 строчек), начал гонять его с помощью апачевской утилиты нагрузочного тестирования (ab). И получил, что ИНОГДА действительно эта ошибка выскакивает. А иногда - нет. В общем, очень неустойчивое поведение, но все-таки воспроизводится.

Табличка такая:

Код: Выделить всё

CREATE TABLE TEST (
    ID   INTEGER NOT NULL,
    CNT  INTEGER DEFAULT 0 NOT NULL
);
Скрипт такой (PHP):

Код: Выделить всё

<HTML>
<?
	function WriteLog($msg)
	{
		$LogFileName = '/tmp/test.log';
		$msg = date('d-m-Y H:i:s') . " > " . $msg . "\r\n";

                $f = fopen($LogFileName, "a+");
                fwrite($f, $msg); 
                fclose($f);
	}

	function DieError($msg)
	{
		$msg .= ": " . ibase_errmsg();
		echo nl2br($msg);
		WriteLog($msg);
		die;
	}

	$charset = "win1251";
	$buffers = 0;
	$dialect = 3;
	$role = "";
	$dbname = "localhost:/db/test.gdb";
	$user = "sysdba";
	$password = "********";

	$WriteTransParams = IBASE_WRITE | IBASE_COMMITTED | IBASE_WAIT | IBASE_NO_REC_VERSION;
	
	// Соединяемся
	$db = @ibase_connect($dbname, $user, $password, $charset, $buffers, $dialect, $role) or DieError("Connection failed");
	echo "\$db = $db<br>\r\n";
	
	// Стартуем пишущую транзакцию
	$tran = @ibase_trans($WriteTransParams) or DieError("Transaction start failed") ;
	echo "\$tran = $tran<br>\r\n";
	
	// Выполняем запрос
	$q = @ibase_query("UPDATE TEST SET CNT = CNT + 1 WHERE ID = ?", 1) or DieError("Query execution failed");
	echo "\$q = $q<br>\r\n";

	// Немножко ждем (мкс)
	usleep((int)(0.1 * 1000000));
	
	// Фиксируем транзакцию
	@ibase_commit($tran) or DieError("Commit failed");
	echo "Committed.<br>\r\n";

?>
</HTML>
Тестируем так:
ab -n 3000 -c 64 "http://testserver/test.php"

И смотрим лог, который пишет скрипт.
Иногда видим там пачку deadlock update conflicts with concurrent update.

Что делать???

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

Но вообще - непорядок...

Ivan_Pisarevsky
Заслуженный разработчик
Сообщения: 644
Зарегистрирован: 15 фев 2005, 11:34

Сообщение Ivan_Pisarevsky » 27 дек 2005, 10:16

Что мешает использовать генератор?

dimitr
Разработчик Firebird
Сообщения: 888
Зарегистрирован: 26 окт 2004, 16:20

Сообщение dimitr » 27 дек 2005, 11:34

1. транзакция А изменила запись
2. транзакция Б собирается сделать тоже самое, но на чтении нарвалась на незакомиченную версию и ожидает
3. транзакция А завершается
4. транзакция Б перечитывает изменения
5. транзакция Б изменяет запись

Фишка в том, что если между точками 4 и 5 (очень маленький интервал) какая-либо транзакция снова обновит нашу запись, то update conflict все-таки будет. Такое можно выловить только под нагрузкой, причем чаще на классике.

Вывод - no_rec_version значительно уменьшает вероятность конфликтов, но не гарантирует их полное отсутствие. Не могу сказать, что это баг - никто бесконфликтный read committed и не обещал. Это скорее просто побочный эффект блокировок на чтении.

Обходить это надо обработкой конфликтов в клиенте, т.е. в данном случае банальным перезапуском оператора.

iamhere
Сообщения: 21
Зарегистрирован: 27 дек 2005, 09:45

Сообщение iamhere » 27 дек 2005, 11:46

Ivan_Pisarevsky писал(а):Что мешает использовать генератор?
Это плохая замена.
Отсутствие транзакционности еще можно пережить (в конкретной нашей задаче), но генератор - это DDL-объект, а нам надо разные записи обновлять, их может быть больше, меньше, они должны хранить несколько значений....

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

P.S. рекомендовал бы подправить статью про no_rec_version на тот счет, что все-таки бесконфликтный update не гарантируется и нужно обеспечивать проверку результатов и повтор.
Во избежание. :?

P.P.S. И почему ЭТО называется deadlock???

Ivan_Pisarevsky
Заслуженный разработчик
Сообщения: 644
Зарегистрирован: 15 фев 2005, 11:34

Сообщение Ivan_Pisarevsky » 27 дек 2005, 12:03

Это плохая замена.
На мой взгляд на генераторах вполне можно обойти все дедлоки.

UPDATE TEST SET CNT = CNT + 1

select gen_id(some_generator,1) as some_number from rdb$database приращение
select gen_id(some_generator,0) as some_number from rdb$database значение

Или иметь не одну запись, а вести лог и в определенный момент (напиример по ночам) подчищать старые записи, а для работы брать с максимальным id. т.е новое значение делать инсертом, а не апдейтом.

iamhere
Сообщения: 21
Зарегистрирован: 27 дек 2005, 09:45

Сообщение iamhere » 27 дек 2005, 12:17

Ivan_Pisarevsky
Поясню еще раз, почему генераторы тут не катят.
Исходный запрос не такой:

Код: Выделить всё

UPDATE TEST SET CNT = CNT + 1

а такой:

Код: Выделить всё

UPDATE SomeTable SET <fieldName> = <fieldName> + 1 WHERE <condition>
Т.е записей много разных (n). В каждой - несколько значений (m). Т.е на генераторах придется делать n * m генераторов, динамически их создавать, прибивать, именовать и заниматься прочими извращениями. Не, это бред... не та задача.



[/code]

Ivan_Pisarevsky
Заслуженный разработчик
Сообщения: 644
Зарегистрирован: 15 фев 2005, 11:34

Сообщение Ivan_Pisarevsky » 27 дек 2005, 12:51

Как насчет второй части моего поста?

iamhere
Сообщения: 21
Зарегистрирован: 27 дек 2005, 09:45

Сообщение iamhere » 27 дек 2005, 12:58

Ну вторую часть твоего поста я обзывал условно "очередь на обновление" и рассматривал как возможный вариант обхода проблемы с начала топика ;)

Просто я думал, что это все-таки баг.

dimitr
Разработчик Firebird
Сообщения: 888
Зарегистрирован: 26 окт 2004, 16:20

Сообщение dimitr » 27 дек 2005, 14:29

iamhere писал(а):P.P.S. И почему ЭТО называется deadlock???
Потому что у некоторых понятие дедлока не совпадает с общепринятым :-) И они сделали isc_deadlock первичным кодом любых конфликтов, который уже конкретизируется тем же isc_lock_conflict, например. Я уже бодался на эту тему, но безуспешно.

kdv
Forum Admin
Сообщения: 6595
Зарегистрирован: 25 окт 2004, 18:07

Сообщение kdv » 27 дек 2005, 14:57

счетчики в базе? плохо это, чую. версий будет немеряно.

Ivan_Pisarevsky
Заслуженный разработчик
Сообщения: 644
Зарегистрирован: 15 фев 2005, 11:34

Сообщение Ivan_Pisarevsky » 27 дек 2005, 16:11

Т.е на генераторах придется делать n * m генераторов...
А какое планируется "m" и "n" ? Они более менее фиксированы или имеют тенденцию к росту?

Merlin
Динозавр IB/FB
Сообщения: 1502
Зарегистрирован: 27 окт 2004, 11:44

Сообщение Merlin » 27 дек 2005, 16:14

dimitr писал(а): Не могу сказать, что это баг - никто бесконфликтный read committed и не обещал. Это скорее просто побочный эффект блокировок на чтении.
Я на той неделе распинался как раз по этому поводу на sql.ru. И всё-таки это имхо неправильно. По меньшей мере нелогично и бессмыссленно. Неужели действительно выстроить претендентов на запись (record) в очередь так сложно?

Merlin
Динозавр IB/FB
Сообщения: 1502
Зарегистрирован: 27 окт 2004, 11:44

Сообщение Merlin » 27 дек 2005, 16:54

kdv писал(а):счетчики в базе? плохо это, чую. версий будет немеряно.
Насчёт счётчиков именно такого плана - наверное да, но он-лайн хранимые агрегаты более узкого профиля - штука, как мы с тобой оба многократно утверждали, в общем случае полезная. Можно, можно всё раскручивать через клиента. Да и характер модификаций у меня лично в основном таков, что их, вместе с агрегатами, надо делать в снапшоте, так что глубокого личного интереса у меня к теме нет :) Просто криво как-то это. Снаружи выглядит как ловля блох во время выполнения оператора, хотя его тут же с успехом можно повторить в той же r_c транзакции. Я-то понимаю, что специально никто этого не делает, просто так уж получилось и очень давно. Но опыт мне подсказывает, что если в инструменте есть нелогичность, то обязательно должны быть области применения где она выходит боком. Даже если в смысле просто неудобства, и то неприятно, но тут же ещё и трафик возрастает. Если упорядочивание приведёт к заметному снижению борзодействия, то баба-Яга первая против, но если нет...

dimitr
Разработчик Firebird
Сообщения: 888
Зарегистрирован: 26 окт 2004, 16:20

Сообщение dimitr » 27 дек 2005, 18:04

Merlin писал(а):Я на той неделе распинался как раз по этому поводу на sql.ru.
Я помню.
Merlin писал(а):И всё-таки это имхо неправильно. По меньшей мере нелогично и бессмыссленно.
С логикой тут все нормально. Указанный режим реализует ожидание на чтении. Запись тут вообще не причем. А вот насчет бессмысленности соглашусь. Вот только ожидание на записи - это вещь, относящаяся в том числе и к rec_version и постановку в очередь надо делать для RC вообще. Для no_rec_version - принудительно, для rec_version - возможно, опционально.
Merlin писал(а):Неужели действительно выстроить претендентов на запись (record) в очередь так сложно?
Просто нужно таковую логику писать с нуля, готовых заделов нет.

Ответить