【问题标题】:PDO, multiple statements, and race conditionsPDO、多条语句和竞争条件
【发布时间】:2013-08-26 20:43:56
【问题描述】:

我有一个 PHP 脚本,它生成一个类似于下面的 MySQL 查询:

INSERT INTO `dailydb`.`probact_actionstaken`(`dispatcher`) VALUES ('kryan');

SET @actionTakenId = LAST_INSERT_ID();

INSERT INTO `dailydb`.`probact_actionstaken_types`(`actionTaken`,`type`)
VALUES
    (@actionTakenId, 2);

INSERT INTO `dailydb`.`probact_actionstaken_problems`(`actionTaken`,`problem`)
VALUES
    (@actionTakenId, 2);

INSERT INTO `dailydb`.`probact_actionstaken_actions`(`actionTaken`,`action`)
VALUES
    (@actionTakenId, 3);

INSERT INTO `dailydb`.`probact_actionstaken_text`(`actionTaken`,`input`,`value`)
VALUES
    (@actionTakenId, 3, '7576'),
    (@actionTakenId, 4, 'B61'),
    (@actionTakenId, 5, '4'),
    (@actionTakenId, 6, 'kryan'),
    (@actionTakenId, 7, 'test'),
    (@actionTakenId, 8, 'testing new debug');

SELECT `index`, `timestamp`
FROM `dailydb`.`probact_actionstaken`
WHERE `index`=@actionTakenId;

(请注意,为了显示此查询,所有值都是在 :someId 上手动搜索并替换为相应值的结果,因为我使用的是准备好的语句)

(还要注意probact_actionstaken.index是一个自动递增的主键,probact_actionstaken.timestamp默认为CURRENT_TIMESTAMP

查询的目标是插入大量相关数据(基于@actionTakenId中存储的probact_actionstaken.index的自动值),然后返回自动生成的indextimestamp 供使用(在使用此输出的 AJAX 调用中调用脚本)。

此查询在大多数情况下都正确执行:五个INSERT 语句都在数据库中插入了正确的值。我的问题是最后一个SELECT 声明。我尝试使用以下 PHP 代码查看查询结果:

$statement = $dbh->prepare($query);
$statement->execute($params);

$output['results'] = array();
do
{
    $output['results'][] = array();
    while ( $result = $statement->fetch(PDO::FETCH_ASSOC) )
    {
        $output['results'][count($output['results'])-1][] = $result;
        if ( isset($result['index']) )
        {
            $output['index'] = intval($result['index']);
        }
        if ( isset($result['timestamp']) )
        {
            $output['timestamp'] = $result['timestamp'];
        }
    }
} while ( $statement->nextRowset() );

echo json_encode($output);

这个想法是使用do...while 块来获取来自INSERT 语句的结果,然后获取最终的SELECT 语句的结果,该语句存储在我的$output 中。

在我的本地机器上(运行 PHP 5.4.4),这可以完美运行。在具有 PHP 5.3.10 的服务器上,$statement->nextRowset() 每次都返回 false,所以我只得到第一个行集(它是空的,因为它来自 INSERT 语句)。请注意,无论如何,实际插入都可以正常工作,并且当我在 phpMyAdmin 的 SQL 功能中运行上述手动填写的查询时,它会返回正确的结果(由插入的索引和时间戳组成的表)。

阅读这些问题后,似乎像这样的多个语句是一个坏主意,并且实际上不适用于真正的准备语句,而且我仍在使用默认的模拟准备语句。所以我可以切换到 $dbh->prepare$statement->execute 的单独调用,这将解决我的问题,但我担心竞争条件。服务器正在使用 Nginx,并且可能有许多用户同时输入条目。我不希望 LAST_INSERT_ID() 返回其他用户的条目并将这些字段与错误的操作相关联。

在 MySQL 或 PHP 中是否有某些原因可以防止出现竞争条件问题?我可以采取一些步骤吗?或者有没有一种安全的方法来使用多个准备好的语句?如果是后者,它是否适用于 PHP 3.5.10?

【问题讨论】:

    标签: php mysql pdo prepared-statement race-condition


    【解决方案1】:

    我不希望 LAST_INSERT_ID() 返回一些其他用户的条目并将这些字段与错误的操作相关联。

    LAST_INSERT_ID() 函数仅返回在 当前 会话中生成的最新 ID,而不是另一个 MySQL 会话。

    http://dev.mysql.com/doc/refman/5.6/en/information-functions.html#function_last-insert-id

    生成的 ID 在每个连接的基础上在服务器中维护。这意味着函数返回给给定客户端的值是为影响该客户端的 AUTO_INCREMENT 列的最新语句生成的第一个 AUTO_INCREMENT 值。此值不受其他客户端的影响,即使它们生成自己的 AUTO_INCREMENT 值。这种行为确保每个客户端都可以检索自己的 ID,而无需担心其他客户端的活动,也不需要锁或事务。

    由于竞争条件的可能性,如果不以这种方式限制该函数的范围,该函数将毫无用处。

    我不知道您在处理 PHP 5.3 和 PHP 5.4 之间的多结果查询时看到的差异的答案。避免多重查询被认为是最佳实践。

    在您将部署到生产环境中的开发环境中使用相同版本的 PHP(和所有软件)也被认为是明智的。

    【讨论】:

    • 很高兴听到我在比赛条件方面什么都不担心。我会将其切换为单个语句。我会在几分钟后检查你是否接受,因为这看起来很明确,不太可能有另一个答案会比这个更好。
    猜你喜欢
    • 1970-01-01
    • 2020-07-12
    • 1970-01-01
    • 2017-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-08
    相关资源
    最近更新 更多