Дескриптор базы данных DBI с AutoCommit, установленным в 0, не возвращает правильные данные с помощью SELECT? - PullRequest
11 голосов
/ 17 октября 2010

Это сложно объяснить (и очень странно), так что терпите меня. Я объясню проблему и ее решение, но я хотел бы посмотреть, сможет ли кто-нибудь объяснить, почему она работает так, как она работает:)

У меня есть веб-приложение, которое использует mod_perl. Он использует базу данных MySQL, и я регулярно записываю данные в базу данных. Он является модульным, поэтому он также имеет свой собственный тип модуля «база данных», где я обрабатываю соединение, обновления и т. Д. Подпрограмма database :: db_connect () используется для подключения к базе данных, а AutoCommit имеет значение 0.

Я создал другое приложение Perl (автономный демон), которое периодически извлекает данные из базы данных и выполняет различные задачи в зависимости от того, какие данные возвращаются. Я включил в него модуль database.pm, поэтому мне не нужно все переписывать / дублировать.

Проблема, с которой я столкнулся:

Приложение подключается к базе данных при запуске, а затем зацикливается, извлекая данные из базы данных каждые X секунд. Однако, если данные в базе данных обновляются, моему приложению все еще возвращаются «старые» данные, которые я получил при первоначальном подключении / запросе к базе данных.

Например - у меня есть 3 строки, а столбец «Имя» имеет значения «a», «b» и «c» - для каждой записи. Если я обновлю одну из строк (например, с помощью клиента mysql из командной строки) и изменит имя с 'c' на 'x', мой автономный демон не получит эти данные - он все равно получит / b / c, возвращенный из MySQL. Я перехватил трафик базы данных с помощью tcpdump и определенно мог видеть, что MySQL действительно возвращает эти данные. Я также пытался использовать SQL_NO_CACHE с SELECT (поскольку я не был уверен, что происходит), но это тоже не помогло.

Затем я изменил строку подключения к БД в своем автономном демоне и установил AutoCommit в 1. Внезапно приложение начало получать правильные данные.

Я озадачен, потому что я думал, что AutoCommit влияет только на операторы типа INSERT / UPDATE и не влияет на оператор SELECT. Но, похоже, это так, и я не понимаю, почему.

Кто-нибудь знает, почему оператор SELECT не будет возвращать «обновленные» строки из базы данных, когда AutoCommit установлен в 0, и почему он будет возвращать обновленные строки, когда AutoCommit установлен в 1?

Вот упрощенный (удаленный контроль ошибок и т. Д.) Код, который я использую в автономном демоне и который не возвращает обновленные строки.

#!/usr/bin/perl

use strict;
use warnings;
use DBI;
use Data::Dumper;
$|=1;

my $dsn = "dbi:mysql:database=mp;mysql_read_default_file=/etc/mysql/database.cnf";
my $dbh = DBI->connect($dsn, undef, undef, {RaiseError => 0, AutoCommit => 0});
$dbh->{mysql_enable_utf8} = 1;

while(1)
{
    my $sql = "SELECT * FROM queue";
    my $stb = $dbh->prepare($sql);
    my $ret_hashref = $dbh->selectall_hashref($sql, "ID");
    print Dumper($ret_hashref);
    sleep(30);
}

exit;

Изменение AutoCommit на 1 исправляет это. Почему?

Спасибо:)

P.S: Не уверен, что кому-то все равно, но версия DBI - 1.613, DBD :: mysql - 4.017, perl - 5.10.1 (в Ubuntu 10.04).

Ответы [ 2 ]

15 голосов
/ 17 октября 2010

Полагаю, вы используете таблицы InnoDB, а не таблицы MyISAM. Как описано в модели транзакций InnoDB , все ваши запросы (включая SELECT) выполняются внутри транзакции.

Когда AutoCommit включено, транзакция запускается для каждого запроса, и если он успешен, он неявно фиксируется (в случае сбоя поведение может отличаться, но транзакция гарантированно завершится). Вы можете увидеть неявные коммиты в binlog MySQL. Если для AutoCommit установлено значение false, вам необходимо самостоятельно управлять транзакциями.

Уровень изоляции транзакции по умолчанию: REPEATABLE READ , что означает, что все запросы SELECT будут считывать один и тот же моментальный снимок (тот, который был создан при запуске транзакции).

В дополнение к решению, приведенному в другом ответе (ROLLBACK перед началом чтения), есть пара решений:

Вы можете выбрать другой уровень изоляции транзакции, например READ COMMITTED , который заставляет ваши SELECT запросы читать новый снимок каждый раз.

Вы также можете оставить для AutoCommit значение true (настройка по умолчанию) и начать свои собственные транзакции, введя BEGIN WORK. Это временно отключит поведение AutoCommit до тех пор, пока вы не выполните оператор COMMIT или ROLLBACK, после которого каждый запрос снова получает свою собственную транзакцию (или вы начинаете другой с BEGIN WORK).

Я бы лично выбрал последний способ, так как он кажется более элегантным.

4 голосов
/ 17 октября 2010

Я думаю, что когда вы выключаете автокоммит, вы также начинаете транзакцию. И когда вы начинаете транзакцию, вы можете быть защищены от изменений других людей, пока вы не совершите ее или не откатите. Итак, если мое полуинформированное предположение верно и поскольку вы только запрашиваете данные, добавьте откат перед операцией ожидания (нет смысла удерживать блокировки, которые вы не используете, и т. Д.):

$dbh->rollback;
...