MySql: транзакции не обнаруживают взаимоблокировки? - PullRequest
2 голосов
/ 21 декабря 2009

Рассмотрим следующий код Perl:

$schema->txn_begin();

my $r = $schema->resultset('test1')->find({id=>20});

my $n = $r->num;
$r->num($n+1);
print("updating for $$\n");
$r->update();

print("$$ val: ".$r->num."\n");

sleep(4);

$schema->txn_commit();

Я ожидаю, что, поскольку обновление защищено транзакцией, то, если два процесса пытаются обновить поле «num», второй должен завершиться с ошибкой, поскольку он проиграл гонку. Interbase называет это «тупиковой» ошибкой. MySQL, однако, приостановит вызов update (), но продолжит работу после того, как первый вызов вызвал commit. Второй процесс тогда имеет «старое» значение num, в результате чего приращение будет неправильным. Обратите внимание:

$ perl trans.pl  & sleep 1 ; perl trans.pl 
[1] 5569
updating for 5569
5569 val: 1015
updating for 5571
5571 val: 1015
[1]+  Done                    perl trans.pl

значение результата равно «1015» в обоих случаях. Как это может быть правильно?

Ответы [ 3 ]

5 голосов
/ 22 декабря 2009

Предполагая, что вы используете InnoDB в качестве механизма хранения, я бы ожидал такого поведения. Уровень изоляции транзакции по умолчанию для InnoDB: REPEATABLE READ. Это означает, что когда вы выполняете SELECT, транзакция получает снимок базы данных в это конкретное время. Снимок будет , а не , включая обновленные данные из других транзакций, которые еще не совершены. Поскольку SELECT в каждом процессе происходит до того, как кто-либо из них выполнит коммит, каждый из них увидит базу данных в одном и том же состоянии (с num = 1014).

Чтобы получить ожидаемое поведение, вы должны следовать предложению Луиса и выполнить SELECT ... FOR UPDATE, чтобы заблокировать интересующую вас строку. Для этого измените эту строку

my $r = $schema->resultset('test1')->find({id=>20});

к этому

my $r = $schema->resultset('test1')->find({id=>20}, {for=>'update'});

и повторите тест.

Если вы не знакомы со сложностями транзакций в MySQL, я настоятельно рекомендую вам прочитать раздел в документации о Модель транзакций InnoDB и блокировка . Кроме того, если вы еще этого не сделали, прочитайте Замечания по использованию DBIC относительно транзакций и AutoCommit также очень внимательно. Поведение txn_ методов, когда AutoCommit включен или выключен, немного сложнее. Если вы готовы, я бы также предложил прочитать источник. Лично мне нужно было прочитать исходный код, чтобы полностью понять, что делает DBIC, чтобы получить точное поведение, которое я хотел.

0 голосов
/ 21 декабря 2009

Это не тупик, тупик что-то вроде этого:

Tx1

1 - обновляет R1 => блокировка записи на R1 2 - обновления R2 => блокировка записи на R2

Tx 2

1 - обновления R2 2 - обновления R1

Если tx1 и tx2 выполняются одновременно, может случиться, что tx1 ожидает освобождения блокировки на R2, а tx2 ожидает блокировки на R1.

В вашем случае вам нужно заблокировать строку с id = 20 (используя select для обновления). Tx, прибывающий «поздно», будет ожидать определенное количество времени (определяемое механизмом БД), чтобы следовать.

0 голосов
/ 21 декабря 2009

Попробуйте сохранить $ r-> num в переменной mysql вместо perl. Извините, я не знаю Perl, но в основном вы хотите

START TRANSACTION;
SELECT num INTO @a FROM test1 where id = 20;
UPDATE test1 set num=(@a+1) WJERE id=20;
COMMIT;
...