diff --git a/core/includes/common.inc b/core/includes/common.inc
index 654a0f35dd3f36469c7adb690375f4f69475c1fa..5841fc2ace0d780722c34a64ab734c49a294c334 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -1527,6 +1527,14 @@ function drupal_flush_all_caches() {
   // actually loaded.
   $module_handler->loadAll();
 
+  // Rebuild the schema and cache a fully-built schema based on new module data.
+  // This is necessary for any invocation of index.php, because setting cache
+  // table entries requires schema information and that occurs during bootstrap
+  // before any modules are loaded, so if there is no cached schema,
+  // drupal_get_schema() will try to generate one, but with no loaded modules,
+  // it will return nothing.
+  drupal_get_schema(NULL, TRUE);
+
   // Rebuild all information based on new module data.
   $module_handler->invokeAll('rebuild');
 
diff --git a/core/includes/schema.inc b/core/includes/schema.inc
index 3cf522ac6dbd8cf75a6e49e29c8c39842f2df882..fdd0df5931225ac575b533af40332be70e99658d 100644
--- a/core/includes/schema.inc
+++ b/core/includes/schema.inc
@@ -5,6 +5,7 @@
  * Schema API handling functions.
  */
 
+use Drupal\Core\Cache\Cache;
 use Drupal\Core\Database\Database;
 
 /**
@@ -17,6 +18,83 @@
  */
 const SCHEMA_UNINSTALLED = -1;
 
+/**
+ * Gets the schema definition of a table, or the whole database schema.
+ *
+ * The returned schema will include any modifications made by any
+ * module that implements hook_schema_alter().
+ *
+ * @param string $table
+ *   The name of the table. If not given, the schema of all tables is returned.
+ * @param bool $rebuild
+ *   If TRUE, the schema will be rebuilt instead of retrieved from the cache.
+ */
+function drupal_get_schema($table = NULL, $rebuild = FALSE) {
+  static $schema;
+
+  if ($rebuild || !isset($schema)) {
+    $schema = drupal_get_complete_schema($rebuild);
+  }
+
+  if (!isset($table)) {
+    return $schema;
+  }
+  if (isset($schema[$table])) {
+    return $schema[$table];
+  }
+  else {
+    return FALSE;
+  }
+}
+
+/**
+ * Gets the whole database schema.
+ *
+ * The returned schema will include any modifications made by any
+ * module that implements hook_schema_alter().
+ *
+ * @param bool $rebuild
+ *   If TRUE, the schema will be rebuilt instead of retrieved from the cache.
+ */
+function drupal_get_complete_schema($rebuild = FALSE) {
+  static $schema;
+
+  if (!isset($schema) || $rebuild) {
+    // Try to load the schema from cache.
+    if (!$rebuild && $cached = \Drupal::cache()->get('schema')) {
+      $schema = $cached->data;
+    }
+    // Otherwise, rebuild the schema cache.
+    else {
+      $schema = array();
+      // Load the .install files to get hook_schema.
+      \Drupal::moduleHandler()->loadAllIncludes('install');
+
+      require_once __DIR__ . '/common.inc';
+      // Invoke hook_schema for all modules.
+      foreach (\Drupal::moduleHandler()->getImplementations('schema') as $module) {
+        // Cast the result of hook_schema() to an array, as a NULL return value
+        // would cause array_merge() to set the $schema variable to NULL as well.
+        // That would break modules which use $schema further down the line.
+        $current = (array) \Drupal::moduleHandler()->invoke($module, 'schema');
+        // Set 'module' and 'name' keys for each table, and remove descriptions,
+        // as they needlessly slow down \Drupal::cache()->get() for every single request.
+        _drupal_schema_initialize($current, $module);
+        $schema = array_merge($schema, $current);
+      }
+      \Drupal::moduleHandler()->alter('schema', $schema);
+
+      // If the schema is empty, avoid saving it: some database engines require
+      // the schema to perform queries, and this could lead to infinite loops.
+      if (!empty($schema)) {
+        \Drupal::cache()->set('schema', $schema, Cache::PERMANENT);
+      }
+    }
+  }
+
+  return $schema;
+}
+
 /**
  * Returns an array of available schema versions for a module.
  *
diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Schema.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Schema.php
index c5ed6060b0992168d3e7a0e3d0276e3948397383..6673d1fbaede785a97ed189bfc49f8d0c40c3340 100644
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Schema.php
+++ b/core/lib/Drupal/Core/Database/Driver/sqlite/Schema.php
@@ -417,6 +417,7 @@ protected function alterTable($table, $old_schema, $new_schema, array $mapping =
    *   Name of the table.
    * @return
    *   An array representing the schema, from drupal_get_schema().
+   * @see drupal_get_schema()
    */
   protected function introspectSchema($table) {
     $mapped_fields = array_flip($this->getFieldTypeMap());
diff --git a/core/lib/Drupal/Core/Extension/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
index 156a16cc48249e116a3668c4fde71694a4c4df34..caddbad05ad7fdcb846f277e9081c432f867a95f 100644
--- a/core/lib/Drupal/Core/Extension/ModuleInstaller.php
+++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
@@ -203,6 +203,9 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
         // Update the kernel to include it.
         $this->updateKernel($module_filenames);
 
+        // Refresh the schema to include it.
+        drupal_get_schema(NULL, TRUE);
+
         // Allow modules to react prior to the installation of a module.
         $this->moduleHandler->invokeAll('module_preinstall', array($module));
 
diff --git a/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php b/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php
index 669d63df8c250b30a37ed4cd8782a55fba159a7e..0354e25491fc902094c744bb133fa33b1215177e 100644
--- a/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php
+++ b/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php
@@ -136,6 +136,15 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     // work during installation.
     $form['#attached']['drupalSettings']['copyFieldValue']['edit-site-mail'] = ['edit-account-mail'];
 
+    // Cache a fully-built schema. This is necessary for any invocation of
+    // index.php because: (1) setting cache table entries requires schema
+    // information, (2) that occurs during bootstrap before any module are
+    // loaded, so (3) if there is no cached schema, drupal_get_schema() will
+    // try to generate one but with no loaded modules will return nothing.
+    //
+    // @todo Move this to the 'install_finished' task?
+    drupal_get_schema(NULL, TRUE);
+
     $form['site_information'] = array(
       '#type' => 'fieldgroup',
       '#title' => $this->t('Site information'),
diff --git a/core/modules/simpletest/src/KernelTestBase.php b/core/modules/simpletest/src/KernelTestBase.php
index ab79c820274b91de2d50a3abc7bf84972cbee7d3..bd611f482e4801b111b13f8c9119ac2e8cbc112f 100644
--- a/core/modules/simpletest/src/KernelTestBase.php
+++ b/core/modules/simpletest/src/KernelTestBase.php
@@ -425,6 +425,10 @@ protected function installSchema($module, $tables) {
       }
       $this->container->get('database')->schema()->createTable($table, $schema);
     }
+    // We need to refresh the schema cache, as any call to drupal_get_schema()
+    // would not know of/return the schema otherwise.
+    // @todo Refactor Schema API to make this obsolete.
+    drupal_get_schema(NULL, TRUE);
     $this->pass(format_string('Installed %module tables: %tables.', array(
       '%tables' => '{' . implode('}, {', $tables) . '}',
       '%module' => $module,
diff --git a/core/modules/simpletest/src/Tests/KernelTestBaseTest.php b/core/modules/simpletest/src/Tests/KernelTestBaseTest.php
index 233f0506597f532119efd089d8232bdc7c473cff..6d9225569b9efaa65a6316244fcd94a72def45d8 100644
--- a/core/modules/simpletest/src/Tests/KernelTestBaseTest.php
+++ b/core/modules/simpletest/src/Tests/KernelTestBaseTest.php
@@ -108,8 +108,8 @@ function testEnableModulesInstall() {
     $this->assertFalse(in_array($module, $list), "{$module}_hook_info() in \Drupal::moduleHandler()->getImplementations() not found.");
 
     $this->assertFalse(db_table_exists($table), "'$table' database table not found.");
-    $schema = drupal_get_schema_unprocessed($module, $table);
-    $this->assertTrue($schema, "'$table' table schema found.");
+    $schema = drupal_get_schema($table, TRUE);
+    $this->assertFalse($schema, "'$table' table schema not found.");
 
     // Install the module.
     \Drupal::service('module_installer')->install(array($module));
@@ -122,7 +122,7 @@ function testEnableModulesInstall() {
     $this->assertTrue(in_array($module, $list), "{$module}_hook_info() in \Drupal::moduleHandler()->getImplementations() found.");
 
     $this->assertTrue(db_table_exists($table), "'$table' database table found.");
-    $schema = drupal_get_schema_unprocessed($module, $table);
+    $schema = drupal_get_schema($table);
     $this->assertTrue($schema, "'$table' table schema found.");
   }
 
@@ -154,7 +154,9 @@ function testInstallSchema() {
     $this->assertTrue(db_table_exists($table), "'$table' database table found.");
 
     // Verify that the schema is known to Schema API.
-    $schema = drupal_get_schema_unprocessed($module, $table);
+    $schema = drupal_get_schema();
+    $this->assertTrue($schema[$table], "'$table' table found in schema.");
+    $schema = drupal_get_schema($table);
     $this->assertTrue($schema, "'$table' table schema found.");
 
     // Verify that a unknown table from an enabled module throws an error.
@@ -167,7 +169,7 @@ function testInstallSchema() {
       $this->pass('Exception for non-retrievable schema found.');
     }
     $this->assertFalse(db_table_exists($table), "'$table' database table not found.");
-    $schema = drupal_get_schema_unprocessed($module, $table);
+    $schema = drupal_get_schema($table);
     $this->assertFalse($schema, "'$table' table schema not found.");
 
     // Verify that a table from a unknown module cannot be installed.
@@ -181,14 +183,14 @@ function testInstallSchema() {
       $this->pass('Exception for non-retrievable schema found.');
     }
     $this->assertFalse(db_table_exists($table), "'$table' database table not found.");
-    $schema = drupal_get_schema_unprocessed($module, $table);
-    $this->assertTrue($schema, "'$table' table schema found.");
+    $schema = drupal_get_schema($table);
+    $this->assertFalse($schema, "'$table' table schema not found.");
 
     // Verify that the same table can be installed after enabling the module.
     $this->enableModules(array($module));
     $this->installSchema($module, $table);
     $this->assertTrue(db_table_exists($table), "'$table' database table found.");
-    $schema = drupal_get_schema_unprocessed($module, $table);
+    $schema = drupal_get_schema($table);
     $this->assertTrue($schema, "'$table' table schema found.");
   }
 
diff --git a/core/modules/system/src/Tests/Database/InsertDefaultsTest.php b/core/modules/system/src/Tests/Database/InsertDefaultsTest.php
index 88bc85364c7b250530ac09a021cc5d92f2064dc7..d5f60ddd2673b77218197521551e066bcb9c52f8 100644
--- a/core/modules/system/src/Tests/Database/InsertDefaultsTest.php
+++ b/core/modules/system/src/Tests/Database/InsertDefaultsTest.php
@@ -23,7 +23,7 @@ function testDefaultInsert() {
     $query = db_insert('test')->useDefaults(array('job'));
     $id = $query->execute();
 
-    $schema = drupal_get_schema_unprocessed('database_test', 'test');
+    $schema = drupal_get_schema('test');
 
     $job = db_query('SELECT job FROM {test} WHERE id = :id', array(':id' => $id))->fetchField();
     $this->assertEqual($job, $schema['fields']['job']['default'], 'Default field value is set.');
@@ -56,7 +56,7 @@ function testDefaultInsertWithFields() {
       ->useDefaults(array('job'));
     $id = $query->execute();
 
-    $schema = drupal_get_schema_unprocessed('database_test', 'test');
+    $schema = drupal_get_schema('test');
 
     $job = db_query('SELECT job FROM {test} WHERE id = :id', array(':id' => $id))->fetchField();
     $this->assertEqual($job, $schema['fields']['job']['default'], 'Default field value is set.');
diff --git a/core/modules/system/src/Tests/Entity/EntityFieldTest.php b/core/modules/system/src/Tests/Entity/EntityFieldTest.php
index c8ea080b273126be3c8f1c4e9e11735abbb04657..4f562a92b8e8c45f1e84ba604f18d27cae64c0da 100644
--- a/core/modules/system/src/Tests/Entity/EntityFieldTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityFieldTest.php
@@ -58,7 +58,6 @@ protected function setUp() {
     }
 
     // Create the test field.
-    module_load_install('entity_test');
     entity_test_install();
 
     // Install required default configuration for filter module.
diff --git a/core/modules/system/src/Tests/Entity/EntityLanguageTestBase.php b/core/modules/system/src/Tests/Entity/EntityLanguageTestBase.php
index 666ea03a0c524d32e0fad37ad5684b196b1c225a..48eac845127c3866f132263775d7457e6a4f41c4 100644
--- a/core/modules/system/src/Tests/Entity/EntityLanguageTestBase.php
+++ b/core/modules/system/src/Tests/Entity/EntityLanguageTestBase.php
@@ -61,7 +61,6 @@ protected function setUp() {
     $this->installConfig(array('language'));
 
     // Create the test field.
-    module_load_install('entity_test');
     entity_test_install();
 
     // Enable translations for the test entity type.
diff --git a/core/modules/system/src/Tests/Entity/EntityValidationTest.php b/core/modules/system/src/Tests/Entity/EntityValidationTest.php
index 440ec450194b7fd03c27753d278b8f9e1725178b..07823fbbbe0b1dda5820071b6140d1e25740b7f5 100644
--- a/core/modules/system/src/Tests/Entity/EntityValidationTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityValidationTest.php
@@ -43,7 +43,6 @@ protected function setUp() {
     parent::setUp();
 
     // Create the test field.
-    module_load_install('entity_test');
     entity_test_install();
 
     // Install required default configuration for filter module.
diff --git a/core/modules/system/tests/modules/module_test/module_test.install b/core/modules/system/tests/modules/module_test/module_test.install
index 0d4dc8bbf68784b1c172f7e3beeefbd49585fa9e..53dfa33d57a500ce2d28f348e839c64baed18b2a 100644
--- a/core/modules/system/tests/modules/module_test/module_test.install
+++ b/core/modules/system/tests/modules/module_test/module_test.install
@@ -28,7 +28,7 @@ function module_test_schema() {
  * Implements hook_install().
  */
 function module_test_install() {
-  $schema = drupal_get_schema_unprocessed('module_test', 'module_test');
+  $schema = drupal_get_schema('module_test');
   db_insert('module_test')
     ->fields(array(
       'data' => $schema['fields']['data']['type'],