Дескриптор базы данных DBI с AutoCommit, установленным на 0, не возвращает правильные данные с помощью SELECT?

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

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

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

Проблема, с которой я сталкиваюсь:

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

Например, у меня есть 3 строки, а столбец «Имя» имеет значения «a», «b» и «c» для каждой записи. Если я обновлю одну из строк (например, с помощью клиента mysql из командной строки) и изменю имя с «c» на «x», мой автономный демон не получит эти данные — он все равно получит a/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).


person sentinel    schedule 17.10.2010    source источник
comment
Включен или выключен параметр auto_commit в вашем клиенте mysql командной строки (где вы выполняли операцию UPDATE)?   -  person Ether    schedule 17.10.2010
comment
Он включен (он включен по умолчанию, и я его не менял). Я вижу «новые» обновленные данные от клиента mysql или любого другого «сеанса» (новый сеанс DBI, который подключается, или любой другой клиент, который подключается к БД) — это только сеанс с AutoCommit 0, который не может получить доступ к обновленным данным .   -  person sentinel    schedule 17.10.2010


Ответы (2)


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

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

Уровень изоляции транзакций по умолчанию — ПОВТОРЯЕМОЕ ЧТЕНИЕ, что означает, что все SELECT запросы будут считывать один и тот же снимок (тот, который был установлен при запуске транзакции).

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

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

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

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

person kixx    schedule 17.10.2010
comment
Это действительно удивительный ответ, и я очень благодарен вам за то, что вы нашли время, чтобы объяснить это. Я читал документацию и пытался понять это, но на самом деле не наткнулся на то, что вы здесь упомянули (тогда, вероятно, читал не те документы;). Еще раз спасибо, это действительно многое объясняет. - person sentinel; 17.10.2010
comment
Другой вопрос (при условии, что вы даже читаете это снова :). Мы используем хранимые процедуры на стороне MySQL, поэтому я фактически не делаю никаких транзакций в коде Perl. Могу ли я использовать AutoCommit = 1 и ввести команду BEGIN WORK непосредственно перед вызовом хранимой процедуры из кода Perl? - person sentinel; 17.10.2010
comment
Да, вы можете это сделать. Вы также можете поместить транзакцию в хранимую процедуру (но не в хранимую функцию), что лучше подходит для вашего рабочего процесса. - person kixx; 17.10.2010

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

$dbh->rollback;
person Jonathan Leffler    schedule 17.10.2010