logo

额外区块类型 (EBT) - 全新的布局构建器体验❗

额外区块类型 (EBT) - 样式化、可定制的区块类型:幻灯片、标签页、卡片、手风琴等更多类型。内置背景、DOM Box、JavaScript 插件的设置。立即体验布局构建的未来。

演示 EBT 模块 下载 EBT 模块

❗额外段落类型 (EPT) - 全新的 Paragraphs 体验

额外段落类型 (EPT) - 类似的基于 Paragraph 的模块集合。

演示 EPT 模块 滚动

滚动
01/10/2025, by Ivan

Drupal 也支持事务,包括对不支持事务的数据库提供透明的备用方案。然而,当你尝试同时运行两个事务时,事务可能会变得相当复杂。在这种情况下的行为也依赖于具体的数据库。

在 C / C++ 中嵌套锁会出现类似的问题。如果代码已经获取了锁 A,然后再次尝试获取锁 A,代码将被阻塞。如果你编写的代码会检查它是否已经持有锁,并且不会再次尝试获取它,你可以避免死锁,但可能会过早释放锁。

在 SQL 中我们遇到了相同的问题。如果你的代码已经在一个事务中,再次启动一个新事务会带来意想不到且不理想的后果——即提交当前事务并启动新的事务。

Java 使用其锁机制通过实现类似嵌套结构的支持来解决嵌套问题,正如我们下面测试的那样。Java 允许将函数标记为“同步”,这会强制函数在启动前等待获取锁,并在不再需要时释放锁。如果一个同步函数调用了同一个类中的另一个同步函数,Java 会追踪锁的嵌套。外层函数获取锁,内层函数不会执行额外的加锁操作,而外层函数在返回时释放锁。

虽然我们不能在 PHP 中声明函数为“transactional”,但我们可以通过带有构造函数和析构函数的对象来模拟 Java 的嵌套逻辑。函数只需在开始时调用“$transaction = $connection->startTransaction()”,即可将自身设为事务型。如果一个事务型函数调用另一个事务型函数,我们的事务抽象层会嵌套它们,而不会在内层嵌套中执行数据库可见的事务操作。

要启动一个新的事务,只需在你自己的代码中调用 $transaction = $connection->startTransaction();。事务会保持开启状态,直到变量 $transaction 仍然在作用域中。当 $transaction 被销毁时,事务会被提交。如果你的事务嵌套在另一个事务中,Drupal 会追踪每一个事务,并且只会在最后一个事务对象离开作用域时提交最外层事务,即所有相关的查询都成功完成时。

你必须将 $connection->startTransaction(); 的返回值赋给一个变量,如示例所示。如果你调用该方法但没有将返回值赋给变量,你的事务将会立即提交,从而失去意义。

在 Drupal 8 中,事务的回滚由连接对象 ($connection->rollBack()) 控制,但通常应通过事务包装对象的方法 ($action->rollBack()) 来完成。之所以应该使用事务对象的 rollBack() 方法,是因为它会根据事务的名称来回滚该事务,而 $connection->rollBack() 默认会使用名称 drupal_transaction,这在嵌套事务中可能会导致不期望的结果。

 示例:

function my_transaction_function() {

  // 在这里开启事务。
  $transaction = $connection->startTransaction();

  try {
    $id = $connection->insert('example')
      ->fields([
        'field1' => 'mystring',
        'field2' => 5,
      ])
      ->execute();

    my_other_function($id);

    return $id;
  }
  catch (Exception $e) {
    $transaction->rollBack();
    watchdog_exception('my_type', $e);
  }

  // 你可以在这里让 $transaction 超出作用域,如果事务还没有回滚,它会被自动提交。
  // 然而,如果你还有更多操作需要完成,你可能需要手动提交事务,如下所示:
  $transaction->commit();

  // 这里的更多代码在事务之外。
}

function my_other_function($id) {
  // 此时事务仍然是开启的。

  if ($id % 2 == 0) {
    $connection->update('example')
      ->condition('id', $id)
      ->fields(['field2' => 10])
      ->execute();
  }
}