如何让MySQL的启动事务查询失败?

How to make MySQL's start transaction query fail?

提问人:user21266319 提问时间:2/22/2023 最后编辑:user21266319 更新时间:2/23/2023 访问量:126

问:

我可以在PHP文档中看到begin_transaction可能会失败。我怎样才能让它失败?(我所有的表都使用 InnoDB 引擎。MySQL 版本 5.7.38-log。

我为什么在乎?我正在调查系统中的编程错误,该错误不检查启动事务的失败。添加一些日志记录后,我发现系统认为---我有主键 ID ---发生了许多插入,但这些记录从未进入数据库,因此系统的扣除不会继续进行。由于系统不会检查启动事务语句中的失败,这可以解释我所拥有的日志记录证据。

更多背景信息。尽管系统不会检查 start-transaction 语句是否可能失败,但会检查提交结果。我有兴趣发现当 start-transaction 语句不成功时会发生什么,但是(使用相同的连接)我继续进行插入和提交。(这个插入和提交应该成功吗?由于我不知道如何使 start-transaction 语句失败,因此我无法尝试查看结果。

这是一个以 Apache 为前端---的 PHP 7.4 Web 系统。

我尝试了 Apache 程序,希望我的 Web 系统用请求压倒我,我可以得到一个 start-transaction 语句失败。我以为他们会失败。他们没有失败。ab

(*)日志

以下日志证明已运行一系列 SQL INSERT 语句,但未进入数据库。(我假设没有人在我背上删除东西---我是数据库的唯一所有者。代码(生成这些日志的位置)如下。

1675716327901,"{""message"":""New Order for user ID: 21473"",""AWN"":""some-aws-tag"",""log_level"":6}"
1675716327942,"{""message"":""Order created: 4328077174"",""AWN"":""some-aws-tag"",""log_level"":6}"
1675716327971,"{""message"":""Attendee created: 3156"",""AWN"":""some-aws-tag"",""log_level"":6}"
1675716327988,"{""message"":""Invoice created: 336845"",""AWN"":""some-aws-tag"",""log_level"":6}"
1675716331883,"{""message"":""Committed fine"",""AWN"":""some-aws-tag"",""log_level"":6}"

(*)代码

public function create() {
  $this->sendCloudWatchLog("New Order for user ID: $this->userId");
  $oSql->start_transaction();
  foreach ($this->orders as $aCourseOrder) {
    $oRegistration = new ProgramRegistration($aCourseOrder->iProgramID, $aCourseOrder->iCourseID);
    $iRegID = $oRegistration->createRegistration('unpaid', $iRegistrarUserId);
    if ($iRegID > 0) {
      $this->sendCloudWatchLog("Order created: {$oRegistration->registrationNumber}");
    } else {
      $error = 'Registration order creation failed';
      break;
    }
  
    foreach ($this->attendes as $aAttendeeInfo) {
      $newAttendeeId = $oRegistration->createAttendee($aAttendeeData);
      if ($newAttendeeId > 0) {
        $this->sendCloudWatchLog("Attendee created: {$newAttendeeId}");
      } else {
        $error = "Attendee creation failed for user id: $this->userId";
        break;
      }
    }
  }
  
  if ($error != '') {
    $oSql->rollback_transaction();
    $this->sendCloudWatchLog("Order error");
    throw new \Exception("Order Error" . $error);
  }
  
  try {
    $this->sendCloudWatchLog("Invoice created: {$this->getInvoiceId()}");
    $statementID = $this->createInvoiceStatement();
    if (intval($statementID) <= 0) {
      throw new \Exception('Invoice Error - Statement failed to be created');
    }
    /* credit card charge, possible exception thrown */
    $this->processPayment();
  } catch (\Exception $e) {
    $error = handleError($e->getMessage());
  }
  
  if ($error == '') {
    $bCommit = $oSql->commit_transaction();
    if ($bCommit === false) {
      $error = 'Commit transaction failed.';
    } else {
      $this->sendCloudWatchLog("Committed fine");
    }
  }
  
  if ($error !== '') {
    $oSql->rollback_transaction();
    $this->sendCloudWatchLog("Rollback due to non-empty: {$error}");
  }
  return true;
}```
php mysql mysqli innodb sqltransaction

评论

0赞 ADyson 2/22/2023
从您的描述来看,事务中的某个查询可能更有可能以某种方式失败,因此事务被回滚而不是提交。如果我们能看到相关的 PHP/SQL 代码,可能会更容易提出建议。
0赞 Will 2/23/2023
找出所发生情况的一种方法是激活常规查询日志。它应该允许你从数据库的角度确切地看到已经做了什么。回答你的问题:丑陋的为什么失败是将你的表转换为MyIsam表。假设您正在一个允许您这样做的环境中工作。
0赞 user21266319 2/23/2023
@ADyson,感谢您的关注---我添加了日志和代码。
0赞 user21266319 2/23/2023
@Will,我认为您是说 MyIsam 表在我调用时会产生错误。好主意。如果可能的话,我会研究一下。(稍后会详细介绍!start transaction
0赞 Will 2/23/2023
确切地说,MyIsam引擎不支持事务,因此我预计它会失败:)

答:

1赞 Dharman 2/23/2023 #1

mysqli_begin_transaction()只是一个包装器,所以它可能会因为失败的相同原因而失败。但总的来说,它永远不会失败。潜在的故障仅限于一些一般错误,例如连接断开。mysqli_query('START TRANSACTION')mysqli_query()

如果由于任何原因开始事务失败,那么 PHP 将抛出异常,就像任何其他 mysqli 错误一样(除非出于某种原因,您将 mysqli 错误报告设置为静默)。因此,只要启用了错误报告并且不捕获错误,就不必担心不一致的事务。如果忽略该错误,但该错误不是由于连接断开造成的,则以下查询将执行,就好像事务从未打开过一样。这可能会导致数据库中的结果不一致(一个查询失败,另一个查询成功)。这就是为什么始终完全启用错误报告很重要的原因!mysqli_begin_transaction()

正确的方法是在尝试捕获之外。mysqli_begin_transaction()

$mysqli->begin_transaction();
try {
    // All your prepared statements go here
    $mysqli->commit();
} catch (mysqli_sql_exception $exception) {
    $mysqli->rollback();
    throw $exception;
}

但是,将其放在 try-catch 中不会有什么区别。当它失败时,它只会不必要地调用 ,这将回滚任何内容。rollback()

只要确保你不使用语句和错误检查。这非常容易出错!ifmysqli_error()

评论

0赞 user21266319 2/23/2023
谢谢你的信息!我不同意PHP抛出异常。文档指出该过程返回 true 或 false。(我怎么能证明你是对的?mysqli_begin_transaction
1赞 Dharman 2/23/2023
@user21266319 如果您将错误报告静音,则情况确实如此。请阅读 php.net/manual/en/mysqli-driver.report-mode.php如何在MySQLi中获取错误消息?
1赞 user21266319 2/23/2023
你是对的---我相信我们根本没有设置错误报告,而且似乎默认值是静音的。因此,似乎如果我由于死锁或其他原因而隐式回滚,那么我不会有异常,我会继续,就好像什么都没发生过一样---所以我的提交语句会认为一切都已提交。(所以,我的系统策略被破坏了。