From feae261b053410ca878598efbdb3ee8aaf0d3897 Mon Sep 17 00:00:00 2001 From: catch Date: Fri, 17 Jun 2022 11:00:43 +0100 Subject: [PATCH] Issue #2733675 by smccabe, murilohp, andregp, Johnny Santos, ankithashetty, mglaman, jonathanshaw, daffie, alexpott, catch, froboy: Warning when mysql is not set to READ-COMMITTED (cherry picked from commit 70d480ab7fac0380464ffb4e67bcbb7e43ea4950) --- .../scaffold/files/default.settings.php | 15 ++++ core/modules/mysql/mysql.install | 45 ++++++++++ .../tests/src/Functional/RequirementsTest.php | 88 +++++++++++++++++++ sites/default/default.settings.php | 15 ++++ 4 files changed, 163 insertions(+) create mode 100644 core/modules/mysql/mysql.install create mode 100644 core/modules/mysql/tests/src/Functional/RequirementsTest.php diff --git a/core/assets/scaffold/files/default.settings.php b/core/assets/scaffold/files/default.settings.php index 72be7750b93..53977afb991 100644 --- a/core/assets/scaffold/files/default.settings.php +++ b/core/assets/scaffold/files/default.settings.php @@ -138,6 +138,21 @@ $databases = []; * request as needed. The fourth line creates a new database with a name of * "extra". * + * For MySQL, MariaDB or equivalent databases the 'isolation_level' option can + * be set. The recommended transaction isolation level for Drupal sites is + * 'READ COMMITTED'. The 'REPEATABLE READ' option is supported but can result + * in deadlocks, the other two options are 'READ UNCOMMITTED' and 'SERIALIZABLE'. + * They are available but not supported; use them at your own risk. For more + * info: + * https://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html + * + * On your settings.php, change the isolation level: + * @code + * $databases['default']['default']['init_commands'] = [ + * 'isolation_level' => 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', + * ]; + * @endcode + * * You can optionally set a prefix for all database table names by using the * 'prefix' setting. If a prefix is specified, the table name will be prepended * with its value. Be sure to use valid database characters only, usually diff --git a/core/modules/mysql/mysql.install b/core/modules/mysql/mysql.install new file mode 100644 index 00000000000..a585abc5517 --- /dev/null +++ b/core/modules/mysql/mysql.install @@ -0,0 +1,45 @@ +isMariaDb() && version_compare($connection->version(), '8.0.0-AnyName', '>')) { + $query = 'SELECT @@SESSION.transaction_isolation'; + } + + $isolation_level = $connection->query($query)->fetchField(); + + if ($isolation_level !== 'READ-COMMITTED') { + $requirements['mysql_transaction_level'] = [ + 'title' => t('Database Isolation Level'), + 'severity' => REQUIREMENT_WARNING, + 'value' => t('Transaction Isolation Level: @value', ['@value' => $isolation_level]), + 'description' => t('For the best performance and to minimize locking issues, the READ-COMMITTED transaction isolation level is recommended.', [ + ':performance_doc' => 'https://www.drupal.org/docs/system-requirements/setting-the-mysql-transaction-isolation-level', + ]), + ]; + } + } + } + + return $requirements; +} diff --git a/core/modules/mysql/tests/src/Functional/RequirementsTest.php b/core/modules/mysql/tests/src/Functional/RequirementsTest.php new file mode 100644 index 00000000000..e10c5f04156 --- /dev/null +++ b/core/modules/mysql/tests/src/Functional/RequirementsTest.php @@ -0,0 +1,88 @@ +markTestSkipped("This test does not support the {$connectionInfo['default']['driver']} database driver."); + } + } + + /** + * Test the isolation level warning message on status page. + */ + public function testIsolationLevelWarningNotDisplaying() { + $admin_user = $this->drupalCreateUser([ + 'administer site configuration', + 'access site reports', + ]); + $this->drupalLogin($admin_user); + + // Change the isolation level to force the warning message. + $this->writeIsolationLevelSettings('REPEATABLE READ'); + + // Check if the warning message is being displayed. + $this->drupalGet('admin/reports/status'); + $elements = $this->xpath('//details[@class="system-status-report__entry"]//div[contains(text(), :text)]', [ + ':text' => 'For the best performance and to minimize locking issues, the READ-COMMITTED', + ]); + $this->assertCount(1, $elements); + $this->assertStringStartsWith('Transaction Isolation Level', $elements[0]->getParent()->getText()); + + // Rollback the isolation level to read committed. + $this->writeIsolationLevelSettings('READ COMMITTED'); + + // Check if the warning message is gone. + $this->drupalGet('admin/reports/status'); + $this->assertSession()->pageTextNotContains('Database Isolation Level'); + $elements = $this->xpath('//details[@class="system-status-report__entry"]//div[contains(text(), :text)]', [ + ':text' => 'For the best performance and to minimize locking issues, the READ-COMMITTED', + ]); + + $this->assertCount(0, $elements); + } + + /** + * Writes the isolation level in settings.php. + * + * @param string $isolation_level + * The isolation level. + */ + private function writeIsolationLevelSettings(string $isolation_level) { + $settings['databases']['default']['default']['init_commands'] = (object) [ + 'value' => [ + 'isolation' => "SET SESSION TRANSACTION ISOLATION LEVEL {$isolation_level}", + ], + 'required' => TRUE, + ]; + $this->writeSettings($settings); + } + +} diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php index 72be7750b93..53977afb991 100644 --- a/sites/default/default.settings.php +++ b/sites/default/default.settings.php @@ -138,6 +138,21 @@ $databases = []; * request as needed. The fourth line creates a new database with a name of * "extra". * + * For MySQL, MariaDB or equivalent databases the 'isolation_level' option can + * be set. The recommended transaction isolation level for Drupal sites is + * 'READ COMMITTED'. The 'REPEATABLE READ' option is supported but can result + * in deadlocks, the other two options are 'READ UNCOMMITTED' and 'SERIALIZABLE'. + * They are available but not supported; use them at your own risk. For more + * info: + * https://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html + * + * On your settings.php, change the isolation level: + * @code + * $databases['default']['default']['init_commands'] = [ + * 'isolation_level' => 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', + * ]; + * @endcode + * * You can optionally set a prefix for all database table names by using the * 'prefix' setting. If a prefix is specified, the table name will be prepended * with its value. Be sure to use valid database characters only, usually