Может ли подготовленное заявление быть использовано через транзакцию, из php? - PullRequest
0 голосов
/ 19 мая 2009

Я работаю в среде 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 ... ye?

Если я попытаюсь использовать эту транзакцию в 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 просто не работает, подготовленный оператор будет эффективен, если транзакция должна быть выполнена при откате.

Итак, подготовленные выписки нельзя использовать в транзакции, или я поступаю неправильно?

EDIT:

После некоторой попытки с 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] => string (5) "00000" [1] => int (7) [2] => string (62) "ОШИБКА: дубликат ключа нарушает уникальное ограничение" id_h_orders "" }

array (3) {[0] => string (5) "25P02" [1] => int (7) [2] => string (87) "ОШИБКА: текущая транзакция отменена, команды игнорируются до конца блока транзакций "}

Неустранимая ошибка: вызов функции-члена fetch () для необъекта в /srv/www/test-db/test-db-pgsql-08.php в строке 23

Итак, похоже, что при первом сбое выполнения (с идентификатором 293) транзакция автоматически прерывается ... происходит ли автоматический откат PDO или что-то еще?

Моя цель - завершить первый большой цикл while, а в конце, используя bool var в качестве флага, решить, следует ли откатить или зафиксировать транзакцию.

Ответы [ 2 ]

1 голос
/ 19 мая 2009

Вы должны использовать

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 <- надежные примеры PHP / Postgre с использованием PDO </p>

удачи

0 голосов
/ 20 мая 2009

В 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 является первичным ключом. Я попытался вставить две строки в t с идентификатором 9, и получил ограничение уникальности. Я не могу просто повторить вставку с правильными значениями, потому что теперь любой оператор получит ошибку «текущая транзакция прервана ...». Но я могу выполнить «откат к точке сохранения», что возвращает меня в состояние, в котором я находился, когда делал «точку сохранения» («xyzzy» - это имя точки сохранения). Затем я могу выполнить правильную команду вставки и, наконец, зафиксировать транзакцию (которая фиксирует обе вставки).

Так что в вашем случае, я подозреваю, что вам нужно создать точку сохранения перед вашим оператором UPDATE: если он выдаст ошибку, выполните «откат к точке сохранения» и установите свой флаг. Вам нужно сгенерировать уникальные имена для точек сохранения: например, с помощью счетчика.

Я не совсем уверен, что понимаю, зачем ты все это делаешь. Конечно, вы хотите прекратить обработку, как только узнаете, что собираетесь откатить транзакцию? Или в цикле также должна происходить какая-то другая обработка?

...