PHP Conference Japan 2024

PDO::beginTransaction

(PHP 5 >= 5.1.0, PHP 7, PHP 8, PECL pdo >= 0.1.0)

PDO::beginTransaction 啟動事務

描述

public PDO::beginTransaction(): bool

關閉自動提交模式。當自動提交模式關閉時,透過 PDO 物件實例對資料庫所做的變更不會提交,直到您透過呼叫 PDO::commit() 來結束事務。呼叫 PDO::rollBack() 將會回滾對資料庫的所有變更,並將連線返回至自動提交模式。

某些資料庫(包括 MySQL)在事務中發出資料庫定義語言 (DDL) 語句(例如 DROP TABLE 或 CREATE TABLE)時,會自動發出隱含的 COMMIT。隱含的 COMMIT 會阻止您回滾事務範圍內的任何其他變更。

參數

此函數沒有任何參數。

傳回值

成功時傳回 true,失敗時傳回 false

錯誤/例外

如果已經啟動事務,或驅動程式不支援事務,則會拋出 PDOException

注意即使 PDO::ATTR_ERRMODE 屬性不是 PDO::ERRMODE_EXCEPTION,也會引發例外。

範例

範例 #1 回滾事務

以下範例會開始一個事務,並發出兩個修改資料庫的語句,然後回滾這些變更。但是,在 MySQL 上,DROP TABLE 語句會自動提交事務,因此事務中的任何變更都不會回滾。

<?php
/* 開始一個事務,關閉自動提交 */
$dbh->beginTransaction();

/* 變更資料庫綱要與資料 */
$sth = $dbh->exec("DROP TABLE fruit");
$sth = $dbh->exec("UPDATE dessert
SET name = 'hamburger'"
);

/* 辨識錯誤並回滾變更 */
$dbh->rollBack();

/* 資料庫連線現在回到自動提交模式 */
?>

參見

新增註記

使用者貢獻的註記 12 個註記

steve at fancyguy dot com
9 年前
此處的巢狀事務範例很棒,但它遺漏了關鍵的部分。Commit 將提交所有內容,我只希望在最外層的 Commit 完成時才真正提交。這可以在 InnoDB 中使用儲存點完成。

<?php

class Database extends PDO
{

protected
$transactionCount = 0;

public function
beginTransaction()
{
if (!
$this->transactionCounter++) {
return
parent::beginTransaction();
}
$this->exec('SAVEPOINT trans'.$this->transactionCounter);
return
$this->transactionCounter >= 0;
}

public function
commit()
{
if (!--
$this->transactionCounter) {
return
parent::commit();
}
return
$this->transactionCounter >= 0;
}

public function
rollback()
{
if (--
$this->transactionCounter) {
$this->exec('ROLLBACK TO trans'.$this->transactionCounter + 1);
return
true;
}
return
parent::rollback();
}

}
bitluni
12 年前
您可能會因為巢狀的 beginTransaction 與 commit 呼叫產生問題。
範例

beginTransaction()
執行重要操作
呼叫方法
beginTransaction()
基本操作 1
基本操作 2
commit()
執行最重要操作
commit()

這不會有效,而且很危險,因為您可能會過早使用巢狀的 commit() 關閉您的事務。

沒有必要搞亂您的程式碼並像傳遞布林值一樣指出事務是否已經在執行。您可以像這樣在您的 PDO 包裝器中覆寫 beginTransaction() 和 commit():

<?php
class Database extends \\PDO
{
protected
$transactionCounter = 0;
function
beginTransaction()
{
if(!
$this->transactionCounter++)
return
parent::beginTransaction();
return
$this->transactionCounter >= 0;
}

function
commit()
{
if(!--
$this->transactionCounter)
return
parent::commit();
return
$this->transactionCounter >= 0;
}

function
rollback()
{
if(
$this->transactionCounter >= 0)
{
$this->transactionCounter = 0;
return
parent::rollback();
}
$this->transactionCounter = 0;
return
false;
}
//...
}
?>
drm at melp dot nl
16 年前
回應「Anonymous / 20-Dec-2007 03:04」

你也可以擴展 PDO 類別,並持有一個私有旗標來檢查是否已經開始了交易。

class MyPDO extends PDO {
protected $hasActiveTransaction = false;

function beginTransaction () {
if ( $this->hasActiveTransaction ) {
return false;
} else {
$this->hasActiveTransaction = parent::beginTransaction ();
return $this->hasActiveTransaction;
}
}

function commit () {
parent::commit ();
$this->hasActiveTransaction = false;
}

function rollback () {
parent::rollback ();
$this->hasActiveTransaction = false;
}

}
kesler dot alwin at gmail dot com
8 年前
請修正回答 #116669

$this->exec('ROLLBACK TO trans'.$this->transactionCounter + 1);

改為

$this->exec('ROLLBACK TO trans'.($this->transactionCounter + 1));
rjohnson at intepro dot us
15 年前
如果你正在使用 PDO::SQLITE 並且需要支援高併發的鎖定,請嘗試在呼叫 beginTransaction() 之前準備你的語句,而且你可能也需要在 SELECT 語句上呼叫 closeCursor(),以防止驅動程式認為有開啟的交易。

這是一個範例(Windows,PHP 版本 5.2.8)。我們透過開啟兩個瀏覽器分頁到這個腳本並同時執行它們來測試。如果我們在 prepare 之前放置 beginTransaction,第二個瀏覽器分頁將會命中 catch 區塊,而 commit 會拋出另一個 PDOException,表示交易仍然開啟。

<?php
$conn
= new PDO('sqlite:C:\path\to\file.sqlite');
$stmt = $conn->prepare('INSERT INTO my_table(my_id, my_value) VALUES(?, ?)');
$waiting = true; // 設定一個迴圈條件來測試
while($waiting) {
try {
$conn->beginTransaction();
for(
$i=0; $i < 10; $i++) {
$stmt->bindValue(1, $i, PDO::PARAM_INT);
$stmt->bindValue(2, 'TEST', PDO::PARAM_STR);
$stmt->execute();
sleep(1);
}
$conn->commit();
$waiting = false;
} catch(
PDOException $e) {
if(
stripos($e->getMessage(), 'DATABASE IS LOCKED') !== false) {
// 這應該是 SQLite 特有的,睡眠 0.25 秒
// 然後再試一次。我們確實需要先提交開啟的交易
$conn->commit();
usleep(250000);
} else {
$conn->rollBack();
throw
$e;
}
}
}

?>
cristian at crishk dot com
8 年前
好的,我正在為 MySQL 中的「巢狀」交易尋找解決方案,而且如你所知,MySQL 文件說明在交易內有交易是不可能的。我嘗試使用這裡在 https://php.dev.org.tw/manual/en/pdo.begintransaction.php 中提出的資料庫類別,但不幸的是,這對於我已使用以下程式碼解決的許多與控制流程相關的事情是錯誤的(請看結尾的範例,CarOwner)

<?php

class TransactionController extends \\PDO {
public static
$warn_rollback_was_thrown = false;
public static
$transaction_rollbacked = false;
public function
__construct()
{
parent :: __construct( ... connection info ... );
}
public static
$nest = 0;
public function
reset()
{
TransactionController :: $transaction_rollbacked = false;
TransactionController :: $warn_rollback_was_thrown = false;
TransactionController :: $nest = 0;
}
function
beginTransaction()
{
$result = null;
if (
TransactionController :: $nest == 0) {
$this->reset();
$result = $this->beginTransaction();
}
TransactionController :: $nest++;
return
$result;
}

public function
commit()
{

$result = null;

if (
TransactionController :: $nest == 0 &&
!
TransactionController :: $transaction_rollbacked &&
!
TransactionController :: $warn_rollback_was_thrown) {
$result = parent :: commit();
}
TransactionController :: $nest--;
return
$result;
}

public function
rollback()
{
$result = null;
if (
TransactionController :: $nest >= 0) {
if (
TransactionController :: $nest == 0) {
$result = parent :: rollback();
TransactionController :: $transaction_rollbacked = true;
}
else {
TransactionController :: $warn_rollback_was_thrown = true;
}
}
TransactionController :: $nest--;
return
$result;
}

public function
transactionFailed()
{
return
TransactionController :: $warn_rollback_was_thrown === true;
}
// 要強制回滾,你只能從 $nest = 0 來做
public function forceRollback()
{
if (
TransactionController :: $nest === 0) {
throws new \PDOException();
}
}
}

?>
ludwig dot green at gmail dot com
14 年前
請注意,您也不能使用 TRUNCATE TABLE,因為這個陳述式會像 CREATE TABLE 或 DROP TABLE 一樣觸發提交。

最好只在交易中使用 SELECT、UPDATE 和 DELETE,所有其他陳述式都可能導致提交,從而破壞交易的原子性及其回滾的能力。

顯然你可以使用 DELETE FROM <table> 而不是 TRUNCATE TABLE,但請注意這兩個陳述式之間存在差異,例如 TRUNCATE 會重置 auto_increment 值,而 DELETE 不會。
Sbastien
3 年前
一種使用交易和預備語句加速批次 INSERT 的方法

<?php

// ...

$insert = $pdo->prepare('INSERT INTO table (c1, c2, c3) VALUES (?, ?, ?)');
$bulk = 3_000; // 根據您的資料/系統調整
$rows = 0;

$pdo->beginTransaction();
while (
$entry = fgetcsv($fp)) {
$insert->execute($entry);
if (++
$rows % $bulk === 0) {
$pdo->commit();
$pdo->beginTransaction();
}
}
if (
$pdo->inTransaction()) { // 剩餘列的插入
$pdo->commit();
}
dbeecher at tekops dot com
16 年前
// 如果您需要設定隔離層級 (ISOLATION level) 或鎖定模式 (LOCK MODE),則必須在呼叫 BeginTransaction() 之前完成...
//
// **注意** 您應該始終檢查操作的結果代碼並進行錯誤處理。此範例程式碼
// 假設所有呼叫都有效,以便操作順序準確且易於查看
//
// 這是使用 PECL PDO::INFORMIX 模組,在 fedora core 6 上執行,php 5.2.4
//
// 這是解決 Informix -243 錯誤(無法在表格內定位)的正確方法,當沒有
// ISAM 錯誤指示表格損壞時。如果行被鎖定,則可能會發生 -243(如果表格/索引等都正常)。
// 下面的程式碼將鎖定模式設定為等待 2 分鐘(120 秒),然後
// 放棄。在此範例中,您會取得「讀取已提交」的列,如果您不需要「讀取已提交」,
// 而只需要取得存在的任何資料(忽略鎖定的列等),而不是
// 「SET LOCK MODE TO WAIT 120」,您可以使用「SET ISOLATION TO DIRTY READ」。
//
// 在 Informix 中,您*必須*管理讀取的方式,因為如果您有大量的列、使用聯結
// 並且發生許多更新,則很容易觸發
// 鎖定表格溢位(導致執行個體關閉)。
//

// 例如:

$sql= "SELECT FIRST 50 * FROM mytable WHERE mystuff=1 ORDER BY myid"; /* 定義 SQL 查詢 */

try /* 建立例外處理器 */
{
$dbh = new PDO("informix:host=......");

if ($dbh) /* 我們連線了嗎? */
{
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$dbh->query("SET LOCK MODE TO WAIT 120")

# ----------------
# 開啟交易指標
# ----------------
if ( $dbh->beginTransaction() ) # 明確開啟指標
{
try /* 開啟例外處理器 */
{
$stmt = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL));

$stmt->execute();

while ($row = $stmt->fetch(PDO::FETCH_NUM, PDO::FETCH_ORI_NEXT))
{
$data = $row[0] . "\t" . $row[1] . "\t" . $row[2] . "\t" . $row[3] . "\t" . $row[4] . "\t" . $row[5] . "\t" . $row[6] . "\t" . $row[7] . "\n" . $row[8] ;
//print $data;
print_r($row);
};

$stmt = null;
}
catch (PDOException $e)
{
print "查詢失敗!\n\n";

print "DBA 失敗:" . $e->getMessage();
};

$dbh->rollback(); # 中止任何變更 (即 $dbh->commit())
$dbh = null; # 關閉連線
}
else
{
# 我們永遠不應該到這裡,它應該會跳到例外處理器
print "無法建立連線...\n\n";
};
};
}
catch (Exception $e)
{
$dbh->rollback();
echo "失敗: " . $e->getMessage();
};
geompse at gmail dot com
14 年前
在 Oracle 中,任何結構陳述式都會執行隱含的提交。

所以:ALTER TABLE "my_table" DROP COLUMN "my_column";
無法回滾!

希望這能為其他人節省時間
eddi13
10 年前
在 TRUNCATE TABLE `table` 之後,就像 DELETE FROM `table` 一樣,因此如果刪除了整個表格,則會中止交易。且無法執行回滾。
Steel Brain
10 年前
此範例具有誤導性,通常,資料定義語言子句 (DDL) 將觸發資料庫引擎自動提交。這表示,如果您刪除表格,無論交易如何,都將執行該查詢。
參考 - MySQL
https://mysqldev.dev.org.tw/doc/refman/5.0/en/implicit-commit.html
To Top