Skip to content

Commit 1fdc272

Browse files
refactor: keep errno 1213 state on the MySQLi connection
The flags are MySQLi-only, so BaseConnection stays unchanged. _transBegin() and _transRollback() clear them after success. When InnoDB has already rolled back, _transCommit() calls transRollback() and returns false. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 84385a2 commit 1fdc272

2 files changed

Lines changed: 55 additions & 79 deletions

File tree

‎system/Database/BaseConnection.php‎

Lines changed: 1 addition & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -337,20 +337,6 @@ abstract class BaseConnection implements ConnectionInterface
337337
*/
338338
protected $transFailure = false;
339339

340-
/**
341-
* The server has already rolled this transaction back (MySQLi errno 1213).
342-
* mysqli::commit() still returns true, so transCommit() must not report success.
343-
*/
344-
protected bool $transServerRolledBack = false;
345-
346-
/**
347-
* The next statement must open a server transaction before it runs.
348-
* InnoDB ended the previous one and the connection is back in autocommit.
349-
* Errno 1205 does not set this: innodb_rollback_on_timeout decides whether
350-
* that error rolled the transaction back.
351-
*/
352-
protected bool $transNeedsServerRestart = false;
353-
354340
/**
355341
* Whether to throw exceptions during transaction
356342
*/
@@ -1060,7 +1046,6 @@ public function transBegin(bool $testMode = false): bool
10601046

10611047
if ($this->_transBegin()) {
10621048
$this->transDepth++;
1063-
$this->clearServerRolledBackTransaction();
10641049

10651050
return true;
10661051
}
@@ -1078,24 +1063,7 @@ public function transCommit(): bool
10781063
}
10791064

10801065
// When transactions are nested we only begin/commit/rollback the outermost ones
1081-
if ($this->transDepth > 1) {
1082-
$this->transDepth--;
1083-
1084-
return true;
1085-
}
1086-
1087-
// InnoDB already removed the victim transaction. mysqli::commit()
1088-
// returns true anyway, which would publish later writes and report success.
1089-
// Roll back the restarted server transaction so a false return ends the
1090-
// transaction, as transComplete() does. Otherwise a caller that retries
1091-
// without transRollback() nests inside it and nothing commits.
1092-
if ($this->transServerRolledBack) {
1093-
$this->transRollback();
1094-
1095-
return false;
1096-
}
1097-
1098-
if ($this->_transCommit()) {
1066+
if ($this->transDepth > 1 || $this->_transCommit()) {
10991067
$this->transDepth--;
11001068

11011069
return true;
@@ -1117,10 +1085,6 @@ public function transRollback(): bool
11171085
if ($this->transDepth > 1 || $this->_transRollback()) {
11181086
$this->transDepth--;
11191087

1120-
if ($this->transDepth === 0) {
1121-
$this->clearServerRolledBackTransaction();
1122-
}
1123-
11241088
return true;
11251089
}
11261090

@@ -1149,37 +1113,6 @@ public function handleTransStatus(): void
11491113
}
11501114
}
11511115

1152-
/**
1153-
* Remember that the server has rolled the open transaction back.
1154-
*/
1155-
protected function markServerRolledBackTransaction(): void
1156-
{
1157-
$this->transServerRolledBack = true;
1158-
$this->transNeedsServerRestart = true;
1159-
}
1160-
1161-
/**
1162-
* True while the next statement must BEGIN before it runs.
1163-
*/
1164-
protected function serverTransactionRestartPending(): bool
1165-
{
1166-
return $this->transNeedsServerRestart && $this->transDepth > 0;
1167-
}
1168-
1169-
/**
1170-
* BEGIN succeeded. Clear the restart only now, so a failed BEGIN keeps it pending.
1171-
*/
1172-
protected function serverTransactionRestarted(): void
1173-
{
1174-
$this->transNeedsServerRestart = false;
1175-
}
1176-
1177-
private function clearServerRolledBackTransaction(): void
1178-
{
1179-
$this->transServerRolledBack = false;
1180-
$this->transNeedsServerRestart = false;
1181-
}
1182-
11831116
/**
11841117
* Begin Transaction
11851118
*/

‎system/Database/MySQLi/Connection.php‎

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,16 @@ class Connection extends BaseConnection
9191
*/
9292
public $foundRows = false;
9393

94+
/**
95+
* InnoDB errno 1213 ended this transaction.
96+
*/
97+
private bool $transServerRolledBack = false;
98+
99+
/**
100+
* The next statement must BEGIN before it runs.
101+
*/
102+
private bool $transNeedsServerRestart = false;
103+
94104
/**
95105
* Connect to the database.
96106
*
@@ -291,20 +301,16 @@ protected function execute(string $sql)
291301
}
292302

293303
try {
294-
// Errno 1213 ends the server transaction. Begin again before this
295-
// statement so it does not autocommit. A failed BEGIN goes through
296-
// the normal error path below, and the restart stays pending.
297-
// Errno 1205 is not handled: innodb_rollback_on_timeout decides
298-
// whether InnoDB rolled that transaction back.
304+
// 1213 ended the server transaction. BEGIN again so this statement
305+
// does not autocommit. A failed BEGIN stays pending. 1205 is unchanged:
306+
// innodb_rollback_on_timeout decides that case.
299307
if ($this->connID instanceof mysqli && $this->serverTransactionRestartPending()) {
300-
// With mysqli exception reporting off, a failed BEGIN returns
301-
// false instead of throwing. Do not run the statement without
302-
// the transaction.
308+
// Exception reporting off: a failed BEGIN returns false.
303309
if (! $this->connID->begin_transaction()) {
304310
return false;
305311
}
306312

307-
$this->serverTransactionRestarted();
313+
$this->transNeedsServerRestart = false;
308314
}
309315

310316
return $this->connID->query($this->prepQuery($sql), $this->resultMode);
@@ -594,16 +600,53 @@ public function insertID(): int
594600

595601
protected function _transBegin(): bool
596602
{
597-
return $this->connID->begin_transaction();
603+
if (! $this->connID->begin_transaction()) {
604+
return false;
605+
}
606+
607+
$this->clearDeadlockTransactionState();
608+
609+
return true;
598610
}
599611

600612
protected function _transCommit(): bool
601613
{
614+
// mysqli::commit() returns true after 1213. End the transaction
615+
// and report failure instead.
616+
if ($this->transServerRolledBack) {
617+
$this->transRollback();
618+
619+
return false;
620+
}
621+
602622
return $this->connID->commit();
603623
}
604624

605625
protected function _transRollback(): bool
606626
{
607-
return $this->connID->rollback();
627+
if (! $this->connID->rollback()) {
628+
return false;
629+
}
630+
631+
$this->clearDeadlockTransactionState();
632+
633+
return true;
634+
}
635+
636+
private function markServerRolledBackTransaction(): void
637+
{
638+
$this->transServerRolledBack = true;
639+
$this->transNeedsServerRestart = true;
640+
}
641+
642+
private function serverTransactionRestartPending(): bool
643+
{
644+
return $this->transNeedsServerRestart && $this->transDepth > 0;
645+
}
646+
647+
private function clearDeadlockTransactionState(): void
648+
{
649+
$this->transServerRolledBack = false;
650+
$this->transNeedsServerRestart = false;
608651
}
609652
}

0 commit comments

Comments
 (0)