diff -u b/core/modules/mysql/mysql.install b/core/modules/mysql/mysql.install --- b/core/modules/mysql/mysql.install +++ b/core/modules/mysql/mysql.install @@ -31,39 +31,46 @@ $query = 'SELECT @@SESSION.transaction_isolation'; } - $isolation_level = $connection->query($query)->fetchField(); - - $requirements['mysql_transaction_level'] = [ - 'title' => t('Database Isolation Level'), - 'severity' => $isolation_level === 'READ-COMMITTED' ? REQUIREMENT_OK : REQUIREMENT_WARNING, - 'value' => $isolation_level, - 'description' => t('For the best performance and to minimize locking issues, the READ-COMMITTED transaction isolation level is recommended. See the setting MySQL transaction isolation level page for more information.', [ - ':performance_doc' => 'https://www.drupal.org/docs/system-requirements/setting-the-mysql-transaction-isolation-level', - ]), - ]; - $tables_missing_keys = []; + $tables_missing_key = []; $missing_primary_key = FALSE; $tables = $connection->schema()->findTables('%'); - $description = t('To insure correct operation with READ-COMMITTED, ensure all tables have a primary key.'); + $isolation_level = $connection->query($query)->fetchField(); + $severity_level = REQUIREMENT_INFO; + $description = 'This site is using the database transaction level "READ COMMITTED".'; foreach ($tables as $table) { $primary_key_column = Database::getConnection()->query("SHOW KEYS FROM {" . $table . "} WHERE Key_name = 'PRIMARY'")->fetchAllAssoc('Column_name'); if (empty($primary_key_column)) { $missing_primary_key = TRUE; - $tables_missing_keys[] = $table; + $tables_missing_key[] = $table; } } - if ($missing_primary_key) { - $tables_missing_key_text = implode(', ', $tables_missing_keys); - $description = "To insure correct operation with READ-COMMITTED, ensure all tables have a primary key. The following table(s) do not contain a primary key: $tables_missing_key_text"; + $tables_missing_key_text = 'For this to work properly all tables should have a primary key. The following table(s) do no have a primary key: '. implode(', ', $tables_missing_key); + + if ($isolation_level == 'READ-COMMITTED' && $missing_primary_key) { + $severity_level = REQUIREMENT_ERROR; + $description = 'This site is using the database transaction level "READ COMMITTED". '. $tables_missing_key_text; } + elseif ($isolation_level == 'REPEATABLE-READ') { + $severity_level = REQUIREMENT_WARNING; + $description = 'This site is using the database transaction level "REPEATABLE READ". The recommended database transaction level for Drupal is "READ COMMITTED".'; + if ($missing_primary_key) { + $description .= ' ' . $tables_missing_key_text; + } + } + elseif ($isolation_level == 'READ-UNCOMMITTED' || $isolation_level == 'SERIALIZED') { + $severity_level = REQUIREMENT_ERROR; + $description = 'This site is using the database transaction level "'.$isolation_level. '". This database transaction level is not supported by Drupal. The recommended database transaction level for Drupal is "READ COMMITTED".'; + } - $requirements['mysql_primary_key_existence'] = [ - 'title' => t('MySQL primary keys'), - 'severity' => (!$missing_primary_key) ? REQUIREMENT_OK : REQUIREMENT_WARNING, - 'value' => (!$missing_primary_key) ? t('Primary keys exist') : t('Primary keys missing'), - 'description' => $description, + $requirements['mysql_transaction_level'] = [ + 'title' => t('Database Isolation Level'), + 'severity' => $severity_level, + 'value' => $isolation_level, + 'description' => t($description. ' See the setting MySQL transaction isolation level page for more information.', [ + ':performance_doc' => 'https://www.drupal.org/docs/system-requirements/setting-the-mysql-transaction-isolation-level', + ]), ]; } } diff -u b/core/modules/mysql/src/Driver/Database/mysql/Install/Tasks.php b/core/modules/mysql/src/Driver/Database/mysql/Install/Tasks.php --- b/core/modules/mysql/src/Driver/Database/mysql/Install/Tasks.php +++ b/core/modules/mysql/src/Driver/Database/mysql/Install/Tasks.php @@ -183,7 +183,9 @@ 'DO NOT SET BY DRUPAL' => $this->t('Do not set by Drupal'), ], '#default_value' => $database['isolation_level'] ?? 'READ COMMITTED', - '#description' => $this->t('To ensure database consistency, we recommend setting the transaction isolation level to "READ COMMITTED". For existing sites, Drupal does not set this option. Instead, leave it to the database servers configuration. Sites without this option will use the database default transaction isolation level, which is typically "REPEATABLE READ".'), + '#description' => $this->t('The recommended database transaction level for Drupal is "READ COMMITTED". By selecting the option "READ COMMITTED" Drupal will set the transaction level on every page request. If possible, we recommend to set database transaction level globally. In that case select the option "DO NOT SET BY DRUPAL". For more information, see the setting MySQL transaction isolation level page.', [ + ':performance_doc' => 'https://www.drupal.org/docs/system-requirements/setting-the-mysql-transaction-isolation-level', + ]), ]; return $form; diff -u b/core/modules/mysql/tests/src/Functional/RequirementsTest.php b/core/modules/mysql/tests/src/Functional/RequirementsTest.php --- b/core/modules/mysql/tests/src/Functional/RequirementsTest.php +++ b/core/modules/mysql/tests/src/Functional/RequirementsTest.php @@ -44,6 +44,7 @@ 'access site reports', ]); $this->drupalLogin($admin_user); + $connection = Database::getConnection(); // Set the isolation level to a level that produces a warning. $this->writeIsolationLevelSettings('REPEATABLE READ'); @@ -51,12 +52,12 @@ // Check the message is not a warning. $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', + ':text' => 'This site is using the database transaction level "REPEATABLE READ". The recommended database', ]); $this->assertCount(1, $elements); $this->assertStringStartsWith('REPEATABLE-READ', $elements[0]->getParent()->getText()); // Ensure it is a warning. - $this->assertStringContainsString('warning', $elements[0]->getParent()->getParent()->find('css', 'summary')->getAttribute('class')); + $this->assertStringContainsString('warning', $elements[0]->getParent()->getParent()->find('css', 'summary')->getAttribute('class')); // Set the isolation level to a level that produces a warning. // Rollback the isolation level to read committed. $this->writeIsolationLevelSettings('READ COMMITTED'); @@ -64,12 +65,49 @@ // Check the message is not a warning. $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', + ':text' => 'This site is using the database transaction level "READ COMMITTED"', ]); $this->assertCount(1, $elements); $this->assertStringStartsWith('READ-COMMITTED', $elements[0]->getParent()->getText()); // Ensure it is a not a warning. $this->assertStringNotContainsString('warning', $elements[0]->getParent()->getParent()->find('css', 'summary')->getAttribute('class')); + + $specification = [ + 'fields' => [ + 'text' => [ + 'type' => 'text', + 'description' => 'A text field', + ], + ], + ]; + + $connection->schema()->createTable('test_table_without_primary_key', $specification); + + // Set the isolation level to a level that produces a warning. + $this->writeIsolationLevelSettings('REPEATABLE READ'); + + // Check the message is not a warning. + $this->drupalGet('admin/reports/status'); + $elements = $this->xpath('//details[@class="system-status-report__entry"]//div[contains(text(), :text)]', [ + ':text' => 'This site is using the database transaction level "REPEATABLE READ". The recommended database transaction level for Drupal is "READ COMMITTED". For this to work properly all tables should have a primary key. The following table(s) do no have a primary key:', + ]); + $this->assertCount(1, $elements); + $this->assertStringStartsWith('REPEATABLE-READ', $elements[0]->getParent()->getText()); + // Ensure it is a warning. + $this->assertStringContainsString('warning', $elements[0]->getParent()->getParent()->find('css', 'summary')->getAttribute('class')); // Set the isolation level to a level that produces a warning. + + // Rollback the isolation level to read committed. + $this->writeIsolationLevelSettings('READ COMMITTED'); + + // Check the message is not a warning. + $this->drupalGet('admin/reports/status'); + $elements = $this->xpath('//details[@class="system-status-report__entry"]//div[contains(text(), :text)]', [ + ':text' => 'This site is using the database transaction level "READ COMMITTED". For this to work properly all tables should have a primary key. The following table(s) do no have a primary key:', + ]); + $this->assertCount(1, $elements); + $this->assertStringStartsWith('READ-COMMITTED', $elements[0]->getParent()->getText()); + // Ensure it is a not a warning. + $this->assertStringContainsString('error', $elements[0]->getParent()->getParent()->find('css', 'summary')->getAttribute('class')); } /**