AutoCommit 设置为 0 的 DBI 数据库句柄未通过 SELECT 返回正确的数据?

发布于 2024-09-28 03:56:24 字数 1673 浏览 4 评论 0原文

这是一个很难解释的问题(而且很奇怪),所以请耐心等待。我将解释这个问题及其修复方法,但我想看看是否有人可以解释为什么它会这样工作:)

我有一个使用 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 可以修复此问题。为什么?

谢谢:)

PS: 不确定是否有人关心,但 DBI 版本是 1.613,DBD::mysql 是 4.017,perl 是 5.10.1(在 Ubuntu 10.04 上)。

This is a tricky one to explain (and very weird), so bear with me. I will explain the problem, and the fix for it, but I would like to see if anyone can explain why it works the way it works :)

I have a web application that uses mod_perl. It uses MySQL database, and I am writing data to a database on regular basis. It is modular, so it also has its own 'database' type of a module, where I handle connection, updates, etc. database::db_connect() subroutine is used to connect to database, and AutoCommit is set to 0.

I made another Perl application (standalone daemon), that periodically fetches data from the database, and performs various tasks depending on what data is returned. I am including database.pm module in it, so I don't have to rewrite/duplicate everything.

Problem I am experiencing is:

Application connects to the database on startup, and then loops forever, fetching data from database every X seconds. However, if data in the database is updated, my application is still being returned 'old' data, that I got on the initial connection/query to the database.

For example - I have 3 rows, and column "Name" has values 'a', 'b' and 'c' - for each record. If I update one of the rows (using mysql client from command line, for example) and change Name from 'c' to 'x', my standalone daemon will not get that data - it will still get a/b/c returned from MySQL. I captured the db traffic with tcpdump, and I could definitely see that MySQL was really returning that data. I have tried using SQL_NO_CACHE with SELECT as well (since I wasn't sure what was going on), but that didn't help either.

Then, I have modified the DB connection string in my standalone daemon, and set AutoCommit to 1. Suddenly, application started getting proper data.

I am puzzled, because I thought AutoCommit only affects INSERT/UPDATE types of statements, and had no affect on SELECT statement. But it seemingly does, and I don't understand why.

Does anyone know why SELECT statement will not return 'updated' rows from the database when AutoCommit is set to 0, and why it will return updated rows when AutoCommit is set to 1?

Here is a simplified (taken out error checking, etc) code that I am using in standalone daemon, and that doesn't return updated rows.

#!/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;

Changing AutoCommit to 1 fixes this. Why?

Thanks :)

P.S: Not sure if it anyone cares, but DBI version is 1.613, DBD::mysql is 4.017, perl is 5.10.1 (on Ubuntu 10.04).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

静若繁花 2024-10-05 03:56:24

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

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

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

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

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

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

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

I suppose you are using InnoDB tables and not MyISAM ones. As described in the InnoDB transaction model, all your queries (including SELECT) are taking place inside a transaction.

When AutoCommit is on, a transaction is started for each query and if it is successful, it is implicitly committed (if it fails, the behavior may vary, but the transaction is guaranteed to end). You can see the implicit commits in MySQL's binlog. By setting AutoCommit to false, you are required to manage the transactions on your own.

The default transaction isolation level is REPEATABLE READ, which means that all SELECT queries will read the same snapshot (the one established when the transaction started).

In addition to the solution given in the other answer (ROLLBACK before starting to read) here are a couple of solutions:

You can choose another transaction isolation level, like READ COMMITTED, which makes your SELECT queries read a fresh snapshot every time.

You could also leave AutoCommit to true (the default setting) and start your own transactions by issuing BEGIN WORK. This will temporarily disable the AutoCommit behavior until you issue a COMMIT or ROLLBACK statement after which each query gets its own transaction again (or you start another with BEGIN WORK).

I, personally, would choose the latter method, as it seems more elegant.

一梦等七年七年为一梦 2024-10-05 03:56:24

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

$dbh->rollback;

I think that when you turn autocommit off, you also start a transaction. And, when you start a transaction, you may be protected from other people's changes until you commit it, or roll it back. So, if my semi-informed guess is correct, and since you're only querying the data, add a rollback before the sleep operation (no point in holding locks that you aren't using, etc):

$dbh->rollback;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文