可以在整个交易过程中使用准备好的语句吗?

时间:2009-05-19 11:19:49

标签: php postgresql transactions pdo prepared-statement

我在LAPP环境(linux apache postgresql php)上工作,我只是想知道如何在事务中使用预准备语句(如果可能的话)。

我希望代码能更好地解释单词:

示例1,简单事务:

BEGIN;
INSERT INTO requests (user_id, description, date) VALUES ('4', 'This dont worth anything', NOW());
UPDATE users SET num_requests = (num_requests + 1) WHERE id = '4';
--something gone wrong, cancel the transaction
ROLLBACK;
UPDATE users SET last_activity = NOW() WHERE id = '4'
COMMIT;

在上面的例子中,如果我对交易感到不满意,那么数据库中唯一的影响就是last_activity的更新...你呢?

如果我尝试在php中使用该事务(使用PDO或pg_方法),代码应如下所示(示例2):

/* skip the connection */
pg_query($pgConnection, "BEGIN");
pg_query($pgConnection, "INSERT INTO requests (user_id, description, date) VALUES ('$id_user', 'This dont worth anything', NOW())");
pg_query($pgConnection, "UPDATE users SET num_requests = (num_requests + 1) WHERE id = '$id_user'");
//something gone wrong, cancel the transaction
pg_query($pgConnection, "ROLLBACK");
pg_query($pgConnection, "UPDATE users SET last_activity = NOW() WHERE id = '$id_user'");
pg_query($pgConnection, "COMMIT");

这很好用。也许看起来很难看,但似乎工作(建议随时欢迎)

无论如何,当我尝试用准备好的语句包含示例2时,我的问题就来了(我知道在示例2中使用预处理语句并不是很有用)

示例3:

/* skip the connection */
pg_prepare($pgConnection, 'insert_try', "INSERT INTO requests (user_id, description, date) VALUES ('$1', '$2', $3)");
pg_query($pgConnection, "BEGIN");
pg_execute($pgConnection, 'insert_try', array($user_id, 'This dont worth anything', date("Y-m-d")));
/* and so on ...*/

好吧,示例3根本不起作用,如果事务在回滚时到期,则准备好的语句将有效。

那么,准备好的陈述不能在交易中使用,还是我采取了错误的方式?

修改

经过PDO尝试后,我到了这一点:

<?php
$dbh = new PDO('pgsql:host=127.0.0.1;dbname=test', 'myuser', 'xxxxxx');

$rollback = false;

$dbh->beginTransaction();

//create the prepared statements
$insert_order = $dbh->prepare('INSERT INTO h_orders (id, id_customer, date, code) VALUES (?, ?, ?, ?)');
$insert_items = $dbh->prepare('INSERT INTO h_items (id, id_order, descr, price) VALUES (?, ?, ?, ?)');
$delete_order = $dbh->prepare('DELETE FROM p_orders WHERE id = ?');

//move the orders from p_orders to h_orders (history)
$qeOrders = $dbh->query("SELECT id, id_customer, date, code FROM p_orders LIMIT 1");
while($rayOrder = $qeOrders->fetch(PDO::FETCH_ASSOC)){
    //h_orders already contain a row with id 293
    //lets make the query fail
    $insert_order->execute(array('293', $rayOrder['id_customer'], $rayOrder['date'], $rayOrder['code'])) OR var_dump($dbh->errorInfo());
    //this is the real execute
    //$insert_order->execute(array($rayOrder['id'], $rayOrder['id_customer'], $rayOrder['date'], $rayOrder['code'])) OR die(damnIt('insert_order'));
    //for each order, i move the items too
    $qeItems = $dbh->query("SELECT id, id_order, descr, price FROM p_items WHERE id_order = '" . $rayOrder['id'] . "'") OR var_dump($dbh->errorInfo());
    while($rayItem = $qeItems->fetch(PDO::FETCH_ASSOC)){
        $insert_items->execute(array($rayItem['id'], $rayItem['id_order'], $rayItem['descr'], $rayItem['price'])) OR var_dump($dbh->errorInfo());
    }
    //if everything is ok, delete the order from p_orders
    $delete_order->execute(array($rayOrder['id'])) OR var_dump($dbh->errorInfo());
}
//in here i'll use a bool var to see if anythings gone wrong and i need to rollback,
//or all good and commit
$dbh->rollBack();
//$dbh->commit();
?>

上面的代码失败并输出:

  

array(3){[0] =&gt; string(5)“00000”[1] =&gt; int(7)[2] =&gt; string(62)“ERROR:重复键违反了唯一约束”id_h_orders“”}

     

array(3){[0] =&gt; string(5)“25P02”[1] =&gt; int(7)[2] =&gt; string(87)“错误:当前事务被中止,命令被忽略,直到事务块结束”}

     

致命错误:在第23行的/srv/www/test-db/test-db-pgsql-08.php中调用非对象的成员函数fetch()

所以,看起来当第一次执行失败(id为293的那个)时,事务会自动中止... PDO会自动回滚,还是别的?

我的目标是完成第一个big while循环,最后使用bool var作为标志,决定是回滚还是提交事务。

2 个答案:

答案 0 :(得分:1)

您应该使用

pdo_obj->beginTransaction()
pdo_obj->commit()
pdo_obj->prepare()

此外,您在第一个示例的末尾有一个随机提交。

begin
// do all your stuff
// check for errors through interface
commit OR not

pg_query($pgConnection, "ROLLBACK"); // end of tx(1)
// start new transaction after last rollback = tx(2)
pg_query($pgConnection, "UPDATE users SET last_activity = NOW() WHERE id = '$id_user'");
// commit tx(2) or don't here
// this isn't needed pg_query($pgConnection, "COMMIT");

如果您没有提交,并且需要手动调整内容,请使用其他事务。准备您的查询(如果我记得)是交易的一部分,因为它可能会失败。您实际上不能只手动获取SQL语句并将其转换为查询。 PDO接口有抽象的原因。 :)

http://uk3.php.net/pdo&lt; - 使用PDO的PHP / Postgre的实例

祝你好运

答案 1 :(得分:0)

使用PostgreSQL,如果任何语句在事务期间产生服务器错误,则该事务被标记为中止。这并不意味着它实际上已经回滚 - 只是你几乎无法做任何事情除了回滚。我假设PDO不会自动发出回滚,它等待你调用“回滚”方法。

要实现我想要的功能,您可以使用保存点。您可以只回滚到保存点,然后继续执行事务,而不是回滚整个事务。我将举一个从psql中使用它的例子:

srh@srh@[local] =# begin;
BEGIN
srh@srh@[local] *=# insert into t values(9,6,1,true);
INSERT 0 1
srh@srh@[local] *=# savepoint xyzzy;
SAVEPOINT
srh@srh@[local] *=# insert into t values(9,6,2,true);
ERROR:  duplicate key value violates unique constraint "t_pkey"
srh@srh@[local] !=# insert into t values(10,6,2,true);
ERROR:  current transaction is aborted, commands ignored until end of transaction block
srh@srh@[local] !=# rollback to savepoint xyzzy;
ROLLBACK
srh@srh@[local] *=# insert into t values(10,6,2,true);
INSERT 0 1
srh@srh@[local] *=# commit;
COMMIT
srh@srh@[local] =# 

所以在这个例子中,t的第一列是主键。我试图将两行插入到id为9的t中,并获得唯一性约束。我不能只用正确的值重做插入,因为现在任何语句都会得到“当前事务被中止...”错误。但我可以做“回滚到保存点”,这让我回到了当我做“保存点”时所处的状态(“xyzzy”是保存点的名称)。然后我可以发出正确的insert命令,最后提交事务(提交两个插入)。

因此,在您的情况下,我怀疑您需要做的是在UPDATE语句之前创建一个保存点:如果它发出错误,请执行“回滚到保存点”并设置您的标志。您需要为保存点生成唯一的名称:例如,使用计数器。

我不完全确定我理解你为什么这么做。当你知道要回滚交易时,你肯定想停止处理吗?或者是否还有一些其他处理在循环中发生?