PHP事务处理:程序员生死簿,你敢忽略吗?

PHP事务处理:程序员生死簿,你敢忽略吗?

说到PHP事务处理,这东西简直就是程序员的“生死簿”。你要是处理得好,数据一致性稳稳的;处理得不好,轻则数据错乱,重则系统瘫痪。今天就来聊聊我在项目中用PHP处理事务的那些事,顺便把坑都给你踩一遍。

你得知道什么是事务。简单来说,事务就是一组SQL操作,要么全部成功,要么全部失败。想象一下你在银行转账,A给B转100块,A账户减100,B账户加100。这两步操作必须同时成功或失败,不然就会出现A钱没了,B钱没到账的尴尬局面。

在PHP中,事务处理通常是通过PDO来实现的。PDO是PHP Data Objects的缩写,是一个数据库访问抽象层,支持多种数据库。先来段代码热热身:

if ($pdo->beginTransaction()) {

try {

$stmt1 = $pdo->prepare("UPDATE accounts SET balance = balance - 100 WHERE id = 1");

$stmt1->execute();

$pdo->commit();

} catch (Exception $e) {

$pdo->rollBack();

die("Transaction failed: " . $e->getMessage());

}

}

这段代码就是典型的事务处理流程。beginTransaction开启事务,commit提交事务,rollBack回滚事务。如果在这个过程中有任何一条SQL语句执行失败,catch块会捕获异常并回滚事务,保证数据一致性。

看起来很简单对?但别高兴得太早,坑多着。

坑1:事务嵌套

有些数据库支持事务嵌套,比如MySQL。事务嵌套的意思是在一个事务中还可以开启另一个事务。听起来挺高级的,但如果你不小心用错了,后果很严重。

try {

$stmt2->execute();

$pdo->commit();

} catch (Exception $e) {

$pdo->rollBack();

die("Inner transaction failed: " . $e->getMessage());

}

}

}

}

这段代码在MySQL中执行,内层事务会和外层事务一起提交或回滚。但如果你切换到PostgreSQL,内层事务的提交或回滚不会影响外层事务,这就会导致数据不一致。所以,事务嵌套一定要谨慎使用,最好避免。

坑2:自动提交模式

PDO默认是自动提交模式,也就是说每一条SQL语句都会立即提交到数据库。如果你不小心在事务中执行了自动提交的SQL语句,那事务就失效了。

// 这里不小心执行了自动提交的SQL

$pdo->exec("INSERT INTO log (message) VALUES ('Transaction started')");

}

}

在这段代码中,$pdo->exec("INSERT INTO log (message) VALUES ('Transaction started')");这条语句会立即提交到数据库,即使事务后面失败了,这条日志记录依然存在。

为了避免这个问题,你可以在事务开始前关闭自动提交模式:

$pdo->setAttribute(PDO::ATTR_AUTOCOMMIT, false);

}

}

坑3:死锁

死锁是事务处理中最头疼的问题之一。简单来说,死锁就是两个或多个事务互相等待对方释放资源,导致谁都动不了。

举个例子,有两个事务A和B:

事务A先锁定了账户1,然后尝试锁定账户2。

如果这两个操作同时发生,事务A在等事务B释放账户2,事务B在等事务A释放账户1,结果就卡住了。

解决死锁的办法有很多,比如调整SQL执行顺序、减少事务执行时间、增加重试机制等。

function retryTransaction($pdo, $retries = 3) {

for ($i = 0; $i < $retries; $i++) {

try {

if ($pdo->beginTransaction()) {

$stmt1 = $pdo->prepare("UPDATE accounts SET balance = balance - 100 WHERE id = 1");

$stmt1->execute();

return true;

} catch (Exception $e) {

$pdo->rollBack();

if (strpos($e->getMessage(), 'Deadlock found') !== false && $i < $retries - 1) {

// 如果是死锁,等待一会儿再重试

usleep(100000);

} else {

throw $e;

}

return false;

}

在这个例子中,如果发生死锁,程序会等待0.1秒再重试,最多重试3次。

坑4:事务隔离级别

事务隔离级别决定了事务之间的可见性。MySQL有四种隔离级别:读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。

默认情况下,MySQL的隔离级别是可重复读,但这可能会导致幻读问题。所谓幻读,就是一个事务中两次查询的结果集不一致。

举个例子:

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

$pdo->exec("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ");

$stmt1 = $pdo->query("SELECT * FROM accounts WHERE balance > 100");

$result1 = $stmt1->fetchAll(PDO::FETCH_ASSOC);

// 另一个事务在这时插入了一条新记录

$pdo->exec("INSERT INTO accounts (id, balance) VALUES (3, 200)");

print_r($result1);

}

}

在这个例子中,result1和result2的结果可能会不一致,因为另一个事务在中间插入了一条新记录。为了避免幻读,你可以将隔离级别提升到串行化:

$pdo->exec("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE");

但要注意的是,串行化会严重影响并发性能,所以要根据实际情况权衡。

坑5:长事务

长事务是指执行时间较长的事务。长事务会占用数据库资源,增加死锁风险,甚至导致数据库性能下降。

解决长事务的办法有很多,比如将大事务拆分成多个小事务、减少事务中的业务逻辑等。

php

function updateAccount($pdo, $fromId, $toId, $amount) {

if ($pdo->beginTransaction()) {

$stmt1 = $pdo->prepare("UPDATE accounts SET balance = balance - ? WHERE id = ?");

$stmt1->execute([$amount, $fromId]);

$pdo->commit();

💎 相关推荐

烟名大全列表,烟名字大全集及价格
365平台官方版下载

烟名大全列表,烟名字大全集及价格

📅 07-20 👁️ 7379
问道手游法玲珑怎样使用?(问道法玲珑怎么获得)
365平台官方版下载

问道手游法玲珑怎样使用?(问道法玲珑怎么获得)

📅 07-02 👁️ 8680