Skip to content

Migrations are piling up bogus savepoints on MySQL/MariaDB with PDO_MySQL #1426

Open
@derrabus

Description

@derrabus

Bug Report

Q A
BC Break no
Version 3.4.7

Summary

I've run into this issue in multiple projects that connect to a MySQL or MariaDB through the PDO_MySQL driver with nested transactions (aka savepoints) enabled. MySQL has this bad habit of silently committing a transaction when receiving a DDL statement. This is a problem because we wrap all migrations into a transaction by default.

Since PDO throws when we attempt to commit a transaction although none is active, we politely ask PDO if a transaction is active and if that's not the case, we don't commit.

private static function inTransaction(Connection $connection): bool
{
$innermostConnection = self::getInnerConnection($connection);
/* Attempt to commit or rollback while no transaction is running
results in an exception since PHP 8 + pdo_mysql combination */
return ! $innermostConnection instanceof PDO || $innermostConnection->inTransaction();
}

That however is a problem because DBAL tracks the transaction nesting level and is unable to detect the silent commit. Thus, after the first migration was executed, the DBAL connection still believes we're in a transaction.

Now, when the second migration is started, we open a transaction again. Since DBAL believes we're already in a transaction, it will attempt to create a savepoint instead of starting a transaction. Creating a savepoint outside of a transaction of course doesn't make much sense, so MySQL (you guessed it) silently ignores that savepoint. So, DBAL believes we're at nesting level 2 while we're actually still at zero. Fun times.

At the end of the second migration, we ask PDO again if a transaction is active. That's not the case, so we don't commit.

As you can tell, this goes on an on for each migration, and after 200 migrations, the DBAL connection believes we're inside an active transaction with 199 savepoints.

The consequence is that if you attempt to use an actual transaction inside a migration or reuse that connection after the migrations have run and attempt to open and commit a transaction, DBAL will raise an exception similar to this:

SQLSTATE[42000]: Syntax error or access violation: 1305 SAVEPOINT DOCTRINE_43 does not exist

This issues goes away if in each migration (that issues DDL statements) I override the isTransactional() method like this:

public function isTransactional(): bool
{
    return false;
}

On a MySQL database, this is 100% correct because executing a migration with DDL statements inside a transaction is futile. However, this fix is absolutely not obvious and people will keep forgetting to do so in future migrations.

The issue also goes away if I switch to the MySQLi driver: Unlike PDO, MySQLi does not complain about a transaction not being active, so we ask the DBAL connection to commit although that's a no-op after a DDL statement. It does however make sure, the DBAL connection's transaction nesting level is in sync.

I don't know how a good solution to this problem looks like, we need to revisit #1131 once again.

cc @ostrolucky @greg0ire

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions