【问题标题】:DBI database handle with AutoCommit set to 0 not returning proper data with SELECT?将 AutoCommit 设置为 0 的 DBI 数据库句柄未使用 SELECT 返回正确的数据?
【发布时间】:2011-04-26 12:27:22
【问题描述】:

这是一个难以解释的问题(而且非常奇怪),请耐心等待。我会解释这个问题,并解决它,但我想看看是否有人能解释为什么它的工作方式:)

我有一个使用 mod_perl 的 Web 应用程序。它使用 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 语句没有影响。但它似乎确实如此,我不明白为什么。

有谁知道为什么当AutoCommit 设置为0 时SELECT 语句不会从数据库返回“更新”的行,以及为什么当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 上)。

【问题讨论】:

  • 在您的命令行 mysql 客户端(您在其中执行 UPDATE 操作)中的 auto_commit 设置是打开还是关闭?
  • 开启(默认开启,我没改过)。我可以从 mysql 客户端或任何其他“会话”(连接的新 DBI 会话或连接到 DB 的任何其他客户端)中看到“新”更新数据 - 只有具有 AutoCommit 0 的会话无法访问更新数据.

标签: mysql perl select dbi autocommit


【解决方案1】:

我想您使用的是 InnoDB 表而不是 MyISAM 表。正如 InnoDB transaction model 中所述,所有您的查询(包括 SELECT)都发生在事务中。

AutoCommit 开启时,会为每个查询启动一个事务,如果成功,则隐式提交(如果失败,行为可能会有所不同,但保证会结束事务)。您可以在 MySQL 的 binlog 中看到隐式提交。通过将AutoCommit 设置为false,您需要自行管理事务。

默认事务隔离级别为REPEATABLE READ,这意味着所有SELECT 查询将读取同一个快照(事务启动时建立的快照)。

除了其他答案中给出的解决方案(开始阅读之前ROLLBACK)之外,还有一些解决方案:

您可以选择其他事务隔离级别,例如READ COMMITTED,这会使您的SELECT 查询每次都读取一个新的快照。

您也可以将AutoCommit 保留为true(默认设置)并通过发出BEGIN WORK 开始您自己的事务。这将暂时禁用AutoCommit 行为,直到您发出COMMITROLLBACK 语句,之后每个查询再次获得自己的事务(或者您使用BEGIN WORK 开始另一个事务)。

我个人会选择后一种方法,因为它看起来更优雅。

【讨论】:

  • 这真是一个了不起的答案,我非常感谢您花时间来解释这一点。我确实阅读了文档并试图弄清楚,但真的没有遇到你在这里提到的内容(可能是阅读错误的文档,然后;)。再次感谢,这真的解释了很多。
  • 另一个问题(假设你又读了一遍:)。我们在 MySQL 端使用存储过程,所以我实际上并没有在 Perl 代码中执行任何事务。我可以使用 AutoCommit = 1,并在我从 Perl 代码调用存储过程之前发出“BEGIN WORK”吗?
  • 是的,你可以这样做。您还可以将事务放在存储过程中(但不是在存储函数中),无论哪种方式更适合您的工作流程。
【解决方案2】:

我认为当你关闭自动提交时,你也会开始一个事务。而且,当您开始一个事务时,在您提交或回滚之前,您可能会受到其他人的更改的保护。因此,如果我的半知情猜测是正确的,并且由于您只是查询数据,请在睡眠操作之前添加回滚(没有必要持有您不使用的锁等):

$dbh->rollback;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-29
    • 2013-02-20
    • 1970-01-01
    • 2017-06-17
    • 1970-01-01
    相关资源
    最近更新 更多