为什么更新查询无效?

时间:2016-08-30 01:12:56

标签: php mysql sql pdo

我有这个问题:

START TRANSACTION;
UPDATE users SET events = 0 WHERE id = 10;
UPDATE events SET seen = 1 WHERE author_id = 10 AND seen is NULL;
COMMIT;

当我在PHPMyadmin执行它时,它也可以。但是当我想通过PHP执行它时:

$stm = $dbh->prepare("START TRANSACTION;
                        UPDATE users SET events = 0 WHERE id = ?;
                        UPDATE events SET seen = 1 WHERE author_id = ? AND seen is NULL;
                        COMMIT;");
$stm->execute(array($user_id, $user_id));

它会抛出错误:

  

致命错误:未捕获的异常' PDOException'与消息   ' SQLSTATE [42000]:语法错误或访问冲突:1064您有   SQL语法错误;查看与您的手册相对应的手册   MySQL服务器版本,用于使用“UPDATE用户”附近的正确语法   SET events = 0 WHERE id =?; UPDATE事件SET见= 1'在第2行'   在C:\ xampp \ htdocs \ myweb \ really_test.php:306堆栈跟踪:#0   C:\ xampp \ htdocs \ myweb \ really_test.php(306):PDO->准备(' START   TRANSACTI ...')#1 {main}投入   第306行的C:\ xampp \ htdocs \ myweb \ really_test.php

我该如何解决?

2 个答案:

答案 0 :(得分:5)

PDO::ATTR_EMULATE_PREPARES关闭时,PDO不支持多个查询。在PHP 5.5.21+中,有一个驱动程序特定的常量用于在PDO::queryPDO::prepare PDO::MYSQL_ATTR_MULTI_STATEMENTS中打开/关闭多个查询。

...然而

因为您想要事务,所以依靠接口方法(PDO::beingTrasaction()PDO::commit()PDO::rollBack())更安全,而不是通过SQL代码这样做。首先让我通过使用多个语句对象进行演示,以防您仿真准备已关闭。

使用多个PDO语句对象

// prepare the statements for the transaction
$stm1 = $dbh->prepare("UPDATE users SET events = 0 WHERE id = ?");
$stm2 = $dbh->prepare("UPDATE events SET seen = 1 WHERE author_id = ? AND seen is NULL");
// Make sure PDO is in exception mode
$dbh->setAttribute (PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
try {
    // begin the transaction here you can rollback from the catch block
    $dbh->beginTransaction();
    $stmt1->execute([$user_id]);
    $stmt2->execute([$user_id]);
    $dbh->commit(); // all went OK commit
} catch(PDOException $e) {
    // something went wrong: roll back and handle errors here
    $dbh->rollBack();
}

有关详细信息,请参阅PHP manual on PDO Transactions and auto-commit

使用单个PDO语句对象

要使用多个查询使用单个预准备语句,我们需要使用模拟准备...

// These driver options need to be set in the constructor
$opts = [
    PDO::ATTR_EMULATE_PREPARES => true,
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::MYSQL_ATTR_MULTI_STATEMENTS => true,
];
$dbh = new PDO("mysql:host=localhost;dbname=$dbname", $username, $pass, $opts);

// prepare the statements for the transaction
$stmt = $dbh->prepare(
    "UPDATE users SET events = 0 WHERE id = ?;" .
    "UPDATE events SET seen = 1 WHERE author_id = ? AND seen is NULL;"
);


try {
    // begin the transaction here you can rollback from the catch block
    $dbh->beginTransaction();
    $stmt->execute([$user_id, $user_id]);
    $stmt->nextRowSet(); // move to the next rowset
    $dbh->commit(); // all went OK commit
} catch(PDOException $e) {
    // something went wrong: roll back and handle errors here
    $dbh->rollBack();
}

答案 1 :(得分:1)

There are two ways to solve your problem.

One is like in the other answer, but with less fuss about it. If emulation is turned off, then you can't run multiple queries at once and therefore have to run them one by one. That's just a general rule for any multiple query set.

Although it is recommended to use PDO's built-in commands for transactions, you still can do it using raw SQL. Just split your bulk statement into separate queries:

try {
    $dbh->query("START TRANSACTION");
    $dbh->prepare("UPDATE users SET events = 0 WHERE id = ?")->execute([$user_id]);
    $dbh->prepare("UPDATE events SET seen = 1 WHERE author_id = ? AND seen is NULL")->execute([$user_id]);
    $dbh->query("COMMIT");
} catch(PDOException $e) {
    // something went wrong: roll back and handle errors here
    $dbh->rollBack();
    // ALWAYS re-throw an exception or you will never know what went wrong
    throw $e;
}

Another way is just turning emulation on:

$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, TRUE);

This way your statement of multiple queries will be executed all right.