PHP 使用 mysqli 實現資料庫事務時,常因細節疏忽導致「事務不生效、部分 SQL 提交成功」的假象。本文通過一個真實排查案例,揭示變量拼寫錯誤、缺少顯式開啟事務、表引擎非 InnoDB 三大核心坑點。
public static function exec_sql_tran($arr_sql) {
$result = true;
$con = new MySQLi(DBHOST, DB_USER, DB_PASSWD, DB_NAME);
$con->autocommit(FALSE);
foreach ($arr_sql as $key => $sql) {
if (!$con->query($sql)) {
echo $con->error . "\n";
$resulf = false; // ★ 拼寫錯誤:$resulf ≠ $result
$con->rollback();
break;
}
}
if ($result) { $con->commit(); }
else { $con->rollback(); }
$con->close();
}
現象: 兩條 INSERT,第一條正確、第二條報錯,但第一條數據仍入庫。
根因: $resulf = false 未修改 $result,導致後續判斷走入 commit() 分支。
$result = false; // 已修正拼寫
break;
// ...
if ($result) { $con->commit(); } else { $con->rollback(); }
現象: SQL1 正確、SQL2 錯誤 → SQL1 仍入庫;SQL1 錯誤、SQL2 正確 → 兩條均不入庫(因 break 終止循環)。
分析: break 只是跳出循環,並未阻止已執行的 SQL 被隱式提交。mysqli 默認 autocommit=FALSE 只影響後續語句,若未顯式 BEGIN,首條語句可能仍以自動提交模式執行。
$con->autocommit(false);
$con->begin_transaction(); // PHP 5.5+ 專用
// 或兼容寫法:$con->query('START TRANSACTION');
若 PHP 版本 < 5.5,begin_transaction() 不存在,必須用 query('START TRANSACTION') 或 query('BEGIN')。
經反複驗證代碼邏輯無誤後,排查發現目標表引擎為 MyISAM。
SHOW TABLE STATUS LIKE 'target_table'\G
-- Engine: MyISAM → 不支持事務!
解決: ALTER TABLE target_table ENGINE=InnoDB; 後事務行為恢復正常。
public static function exec_sql_tran(array $arr_sql): bool {
$con = new MySQLi(DBHOST, DB_USER, DB_PASSWD, DB_NAME);
if ($con->connect_errno) {
error_log("MySQLi Connect Error: {$con->connect_error}");
return false;
}
$con->autocommit(false);
$con->query('START TRANSACTION'); // 兼容所有 PHP 版本
try {
foreach ($arr_sql as $sql) {
if (!$con->query($sql)) {
throw new Exception($con->error);
}
}
$con->commit();
return true;
} catch (Exception $e) {
$con->rollback();
error_log("Transaction Rolled Back: {$e->getMessage()}");
return false;
} finally {
$con->close();
}
}
$result 而非 $resulf)START TRANSACTION / BEGIN / begin_transaction()SHOW TABLE STATUS)rollback() 並 close() 連接mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT) 拋出異常事務控制看似簡單,實則暗藏「語法陷阱、版本差異、引擎前提」三重門檻。逐項驗證清單,方能確保 ACID 特性真正落地。