diff --git a/core/lib/Drupal/Core/Cache/CachetagSchema.php b/core/lib/Drupal/Core/Cache/CachetagSchema.php
new file mode 100644
index 0000000..03dd481
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/CachetagSchema.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Cache\CachetagSchema.
+ */
+
+namespace Drupal\Core\Cache;
+
+use Drupal\Core\Database\SchemaProviderInterface;
+
+/**
+ * Class providing schema for the {cachetags} table.
+ *
+ * One SchemaProviderInterface class can provide only one table but cache works
+ * with two.
+ */
+class CachetagSchema implements SchemaProviderInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSchema() {
+    return array(
+      'description' => 'Cache table for tracking cache tags related to the cache bin.',
+      'fields' => array(
+        'tag' => array(
+          'description' => 'Namespace-prefixed tag string.',
+          'type' => 'varchar',
+          'length' => 255,
+          'not null' => TRUE,
+          'default' => '',
+        ),
+        'invalidations' => array(
+          'description' => 'Number incremented when the tag is invalidated.',
+          'type' => 'int',
+          'not null' => TRUE,
+          'default' => 0,
+        ),
+        'deletions' => array(
+          'description' => 'Number incremented when the tag is deleted.',
+          'type' => 'int',
+          'not null' => TRUE,
+          'default' => 0,
+        ),
+      ),
+      'primary key' => array('tag'),
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackend.php b/core/lib/Drupal/Core/Cache/DatabaseBackend.php
index 1e24968..3d51657 100644
--- a/core/lib/Drupal/Core/Cache/DatabaseBackend.php
+++ b/core/lib/Drupal/Core/Cache/DatabaseBackend.php
@@ -9,7 +9,7 @@
 
 use Drupal\Component\Utility\Crypt;
 use Drupal\Core\Database\Connection;
-use Drupal\Core\Database\SchemaObjectExistsException;
+use Drupal\Core\Database\SchemaProviderInterface;
 
 /**
  * Defines a default cache implementation.
@@ -19,7 +19,7 @@
  *
  * @ingroup cache
  */
-class DatabaseBackend implements CacheBackendInterface {
+class DatabaseBackend implements CacheBackendInterface, SchemaProviderInterface {
 
   /**
    * @var string
@@ -151,29 +151,6 @@ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array
     $tags = array_unique($tags);
     // Sort the cache tags so that they are stored consistently in the database.
     sort($tags);
-    $try_again = FALSE;
-    try {
-      // The bin might not yet exist.
-      $this->doSet($cid, $data, $expire, $tags);
-    }
-    catch (\Exception $e) {
-      // If there was an exception, try to create the bins.
-      if (!$try_again = $this->ensureBinExists()) {
-        // If the exception happened for other reason than the missing bin
-        // table, propagate the exception.
-        throw $e;
-      }
-    }
-    // Now that the bin has been created, try again if necessary.
-    if ($try_again) {
-      $this->doSet($cid, $data, $expire, $tags);
-    }
-  }
-
-  /**
-   * Actually set the cache.
-   */
-  protected function doSet($cid, $data, $expire, $tags) {
     $deleted_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::deletedTags', array());
     $invalidated_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::invalidatedTags', array());
     // Remove tags that were already deleted or invalidated during this request
@@ -208,7 +185,7 @@ protected function doSet($cid, $data, $expire, $tags) {
     $this->connection->merge($this->bin)
       ->key('cid', $this->normalizeCid($cid))
       ->fields($fields)
-      ->execute();
+      ->executeEnsuringTable($this);
   }
 
   /**
@@ -277,7 +254,7 @@ public function setMultiple(array $items) {
         $query->values($fields);
       }
 
-      $query->execute();
+      $query->executeEnsuringTable($this);
     }
     catch (\Exception $e) {
       $transaction->rollback();
@@ -307,12 +284,7 @@ public function deleteMultiple(array $cids) {
       }
     }
     catch (\Exception $e) {
-      // Create the cache table, which will be empty. This fixes cases during
-      // core install where a cache table is cleared before it is set
-      // with {cache_render} and {cache_data}.
-      if (!$this->ensureBinExists()) {
-        $this->catchException($e);
-      }
+      $this->catchException($e);
     }
   }
 
@@ -329,16 +301,11 @@ public function deleteTags(array $tags) {
       }
       $deleted_tags[$tag] = TRUE;
       unset($tag_cache[$tag]);
-      try {
-        $this->connection->merge('cachetags')
-          ->insertFields(array('deletions' => 1))
-          ->expression('deletions', 'deletions + 1')
-          ->key('tag', $tag)
-          ->execute();
-      }
-      catch (\Exception $e) {
-        $this->catchException($e, 'cachetags');
-      }
+      $this->connection->merge('cachetags')
+        ->insertFields(array('deletions' => 1))
+        ->expression('deletions', 'deletions + 1')
+        ->key('tag', $tag)
+        ->executeEnsuringTable(new CachetagSchema());
     }
   }
 
@@ -350,12 +317,7 @@ public function deleteAll() {
       $this->connection->truncate($this->bin)->execute();
     }
     catch (\Exception $e) {
-      // Create the cache table, which will be empty. This fixes cases during
-      // core install where a cache table is cleared before it is set
-      // with {cache_render} and {cache_data}.
-      if (!$this->ensureBinExists()) {
-        $this->catchException($e);
-      }
+      $this->catchException($e);
     }
   }
 
@@ -371,17 +333,12 @@ public function invalidate($cid) {
    */
   public function invalidateMultiple(array $cids) {
     $cids = array_values(array_map(array($this, 'normalizeCid'), $cids));
-    try {
-      // Update in chunks when a large array is passed.
-      foreach (array_chunk($cids, 1000) as $cids_chunk) {
-        $this->connection->update($this->bin)
-          ->fields(array('expire' => REQUEST_TIME - 1))
-          ->condition('cid', $cids_chunk, 'IN')
-          ->execute();
-      }
-    }
-    catch (\Exception $e) {
-      $this->catchException($e);
+    // Update in chunks when a large array is passed.
+    foreach (array_chunk($cids, 1000) as $cids_chunk) {
+      $this->connection->update($this->bin)
+        ->fields(array('expire' => REQUEST_TIME - 1))
+        ->condition('cid', $cids_chunk, 'IN')
+        ->executeEnsuringTable($this);
     }
   }
 
@@ -389,25 +346,20 @@ public function invalidateMultiple(array $cids) {
    * Implements Drupal\Core\Cache\CacheBackendInterface::invalidateTags().
    */
   public function invalidateTags(array $tags) {
-    try {
-      $tag_cache = &drupal_static('Drupal\Core\Cache\CacheBackendInterface::tagCache', array());
-      $invalidated_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::invalidatedTags', array());
-      foreach ($tags as $tag) {
-        // Only invalidate tags once per request unless they are written again.
-        if (isset($invalidated_tags[$tag])) {
-          continue;
-        }
-        $invalidated_tags[$tag] = TRUE;
-        unset($tag_cache[$tag]);
-        $this->connection->merge('cachetags')
-          ->insertFields(array('invalidations' => 1))
-          ->expression('invalidations', 'invalidations + 1')
-          ->key('tag', $tag)
-          ->execute();
+    $tag_cache = &drupal_static('Drupal\Core\Cache\CacheBackendInterface::tagCache', array());
+    $invalidated_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::invalidatedTags', array());
+    foreach ($tags as $tag) {
+      // Only invalidate tags once per request unless they are written again.
+      if (isset($invalidated_tags[$tag])) {
+        continue;
       }
-    }
-    catch (\Exception $e) {
-      $this->catchException($e, 'cachetags');
+      $invalidated_tags[$tag] = TRUE;
+      unset($tag_cache[$tag]);
+      $this->connection->merge('cachetags')
+        ->insertFields(array('invalidations' => 1))
+        ->expression('invalidations', 'invalidations + 1')
+        ->key('tag', $tag)
+        ->executeEnsuringTable(new CachetagSchema());
     }
   }
 
@@ -415,14 +367,9 @@ public function invalidateTags(array $tags) {
    * Implements Drupal\Core\Cache\CacheBackendInterface::invalidateAll().
    */
   public function invalidateAll() {
-    try {
-      $this->connection->update($this->bin)
-        ->fields(array('expire' => REQUEST_TIME - 1))
-        ->execute();
-    }
-    catch (\Exception $e) {
-      $this->catchException($e);
-    }
+    $this->connection->update($this->bin)
+      ->fields(array('expire' => REQUEST_TIME - 1))
+      ->executeEnsuringTable($this);
   }
 
   /**
@@ -461,7 +408,20 @@ protected function checksumTags(array $tags) {
 
     $query_tags = array_diff($tags, array_keys($tag_cache));
     if ($query_tags) {
-      $db_tags = $this->connection->query('SELECT tag, invalidations, deletions FROM {cachetags} WHERE tag IN (:tags)', array(':tags' => $query_tags))->fetchAllAssoc('tag', \PDO::FETCH_ASSOC);
+      $query = 'SELECT tag, invalidations, deletions FROM {cachetags} WHERE tag IN (:tags)';
+      $args = array(':tags' => $query_tags);
+      try {
+        $db_tags = $this->connection->query($query, $args)->fetchAllAssoc('tag', \PDO::FETCH_ASSOC);
+      }
+      catch (\Exception $e) {
+        // If there was an exception and the table exists then the cache
+        // might be stale and the exception needs to be propagated.
+        if ($this->connection->schema()->tableExists('cachetags')) {
+          throw $e;
+        }
+        // The table does not exist yet so there are no db tags.
+        $db_tags = array();
+      }
       $tag_cache += $db_tags;
 
       // Fill static cache with empty objects for tags not found in the database.
@@ -484,33 +444,10 @@ public function removeBin() {
       $this->connection->schema()->dropTable($this->bin);
     }
     catch (\Exception $e) {
-      $this->catchException($e);
-    }
-  }
-
-  /**
-   * Check if the cache bin exists and create it if not.
-   */
-  protected function ensureBinExists() {
-    try {
-      $database_schema = $this->connection->schema();
-      if (!$database_schema->tableExists($this->bin)) {
-        $schema_definition = $this->schemaDefinition();
-        $database_schema->createTable($this->bin, $schema_definition['bin']);
-        // If the bin doesn't exist, the cache tags table may also not exist.
-        if (!$database_schema->tableExists('cachetags')) {
-          $database_schema->createTable('cachetags', $schema_definition['cachetags']);
-        }
-        return TRUE;
+      if ($this->connection->schema()->tableExists($this->bin)) {
+        throw $e;
       }
     }
-    // If another process has already created the cache table, attempting to
-    // recreate it will throw an exception. In this case just catch the
-    // exception and do nothing.
-    catch (SchemaObjectExistsException $e) {
-      return TRUE;
-    }
-    return FALSE;
   }
 
   /**
@@ -554,8 +491,8 @@ protected function normalizeCid($cid) {
   /**
    * Defines the schema for the {cache_*} bin and {cachetags} tables.
    */
-  public function schemaDefinition() {
-    $schema['bin'] = array(
+  public function getSchema() {
+    return array(
       'description' => 'Storage for the cache API.',
       'fields' => array(
         'cid' => array(
@@ -616,31 +553,6 @@ public function schemaDefinition() {
       ),
       'primary key' => array('cid'),
     );
-    $schema['cachetags'] = array(
-      'description' => 'Cache table for tracking cache tags related to the cache bin.',
-      'fields' => array(
-        'tag' => array(
-          'description' => 'Namespace-prefixed tag string.',
-          'type' => 'varchar',
-          'length' => 255,
-          'not null' => TRUE,
-          'default' => '',
-        ),
-        'invalidations' => array(
-          'description' => 'Number incremented when the tag is invalidated.',
-          'type' => 'int',
-          'not null' => TRUE,
-          'default' => 0,
-        ),
-        'deletions' => array(
-          'description' => 'Number incremented when the tag is deleted.',
-          'type' => 'int',
-          'not null' => TRUE,
-          'default' => 0,
-        ),
-      ),
-      'primary key' => array('tag'),
-    );
-    return $schema;
   }
+
 }
diff --git a/core/lib/Drupal/Core/Config/DatabaseStorage.php b/core/lib/Drupal/Core/Config/DatabaseStorage.php
index 471590a..f5818c5 100644
--- a/core/lib/Drupal/Core/Config/DatabaseStorage.php
+++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php
@@ -9,13 +9,14 @@
 
 use Drupal\Core\Database\Database;
 use Drupal\Core\Database\Connection;
-use Drupal\Core\Database\SchemaObjectExistsException;
+use Drupal\Core\Database\SchemaProviderInterface;
 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
 
 /**
  * Defines the Database storage.
  */
-class DatabaseStorage implements StorageInterface {
+class DatabaseStorage implements StorageInterface, SchemaProviderInterface {
+
   use DependencySerializationTrait;
 
   /**
@@ -124,69 +125,17 @@ public function readMultiple(array $names) {
    */
   public function write($name, array $data) {
     $data = $this->encode($data);
-    try {
-      return $this->doWrite($name, $data);
-    }
-    catch (\Exception $e) {
-      // If there was an exception, try to create the table.
-      if ($this->ensureTableExists()) {
-        return $this->doWrite($name, $data);
-      }
-      // Some other failure that we can not recover from.
-      throw $e;
-    }
-  }
-
-  /**
-   * Helper method so we can re-try a write.
-   *
-   * @param string $name
-   *   The config name.
-   * @param string $data
-   *   The config data, already dumped to a string.
-   *
-   * @return bool
-   */
-  protected function doWrite($name, $data) {
     $options = array('return' => Database::RETURN_AFFECTED) + $this->options;
     return (bool) $this->connection->merge($this->table, $options)
       ->keys(array('collection', 'name'), array($this->collection, $name))
       ->fields(array('data' => $data))
-      ->execute();
-  }
-
-  /**
-   * Check if the config table exists and create it if not.
-   *
-   * @return bool
-   *   TRUE if the table was created, FALSE otherwise.
-   *
-   * @throws \Drupal\Core\Config\StorageException
-   *   If a database error occurs.
-   */
-  protected function ensureTableExists()  {
-    try {
-      if (!$this->connection->schema()->tableExists($this->table)) {
-        $this->connection->schema()->createTable($this->table, static::schemaDefinition());
-        return TRUE;
-      }
-    }
-    // If another process has already created the config table, attempting to
-    // recreate it will throw an exception. In this case just catch the
-    // exception and do nothing.
-    catch (SchemaObjectExistsException $e) {
-      return TRUE;
-    }
-    catch (\Exception $e) {
-      throw new StorageException($e->getMessage(), NULL, $e);
-    }
-    return FALSE;
+      ->executeEnsuringTable($this);
   }
 
   /**
    * Defines the schema for the configuration table.
    */
-  protected static function schemaDefinition() {
+  public function getSchema() {
     $schema = array(
       'description' => 'The base table for configuration data.',
       'fields' => array(
diff --git a/core/lib/Drupal/Core/Database/Query/Delete.php b/core/lib/Drupal/Core/Database/Query/Delete.php
index 33727a7..42a8e96 100644
--- a/core/lib/Drupal/Core/Database/Query/Delete.php
+++ b/core/lib/Drupal/Core/Database/Query/Delete.php
@@ -18,13 +18,6 @@
 class Delete extends Query implements ConditionInterface {
 
   /**
-   * The table from which to delete.
-   *
-   * @var string
-   */
-  protected $table;
-
-  /**
    * The condition object for this query.
    *
    * Condition handling is handled via composition.
diff --git a/core/lib/Drupal/Core/Database/Query/Insert.php b/core/lib/Drupal/Core/Database/Query/Insert.php
index 392c8c2..cee545a 100644
--- a/core/lib/Drupal/Core/Database/Query/Insert.php
+++ b/core/lib/Drupal/Core/Database/Query/Insert.php
@@ -17,13 +17,6 @@
 class Insert extends Query {
 
   /**
-   * The table on which to insert.
-   *
-   * @var string
-   */
-  protected $table;
-
-  /**
    * An array of fields on which to insert.
    *
    * @var array
diff --git a/core/lib/Drupal/Core/Database/Query/Merge.php b/core/lib/Drupal/Core/Database/Query/Merge.php
index be71de3..57839e1 100644
--- a/core/lib/Drupal/Core/Database/Query/Merge.php
+++ b/core/lib/Drupal/Core/Database/Query/Merge.php
@@ -60,13 +60,6 @@ class Merge extends Query implements ConditionInterface {
   const STATUS_UPDATE = 2;
 
   /**
-   * The table to be used for INSERT and UPDATE.
-   *
-   * @var string
-   */
-  protected $table;
-
-  /**
    * The table or subquery to be used for the condition.
    */
   protected $conditionTable;
diff --git a/core/lib/Drupal/Core/Database/Query/Query.php b/core/lib/Drupal/Core/Database/Query/Query.php
index 790ed5c..9bea60d 100644
--- a/core/lib/Drupal/Core/Database/Query/Query.php
+++ b/core/lib/Drupal/Core/Database/Query/Query.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Database\Database;
 use Drupal\Core\Database\Connection;
+use Drupal\Core\Database\SchemaProviderInterface;
 
 /**
  * Base class for query builders.
@@ -19,6 +20,15 @@
 abstract class Query implements PlaceholderInterface {
 
   /**
+   * The base table.
+   *
+   * Can be a Query object for subqueries.
+   *
+   * @var string|static
+   */
+  protected $table;
+
+  /**
    * The connection object on which to run this query.
    *
    * @var \Drupal\Core\Database\Connection
@@ -110,7 +120,47 @@ public function __clone() {
    * @return \Drupal\Core\Database\StatementInterface|null
    *   A prepared statement, or NULL if the query is not valid.
    */
-  abstract protected function execute();
+  abstract public function execute();
+
+  /**
+   * Create a table if necessary and run the query against the database.
+   *
+   * @param \Drupal\Core\Database\SchemaProviderInterface $schema_provider
+   *   A class providing a schema API array.
+   *
+   * @return \Drupal\Core\Database\StatementInterface|null
+   *   A prepared statement, or NULL if the query is not valid.
+   *
+   * @see \Drupal\Core\Database\Schema::createTable()
+   */
+  public function executeEnsuringTable(SchemaProviderInterface $schema_provider) {
+    try {
+      return $this->execute();
+    }
+    catch (\Exception $e) {
+      // If there was an exception, try to create the table.
+      if ($this->connection->schema()->ensureTableExists($this->getTable(), $schema_provider)) {
+        return $this->execute();
+      }
+      // Some other failure that we can not recover from.
+      throw $e;
+    }
+  }
+
+  /**
+   * Returns the base table.
+   *
+   * @return string
+   *   The base table. If the base table is a subquery then the base table of
+   *   that query. This was even for count queries the base table is returned.
+   */
+  public function getTable() {
+    $table = $this->table;
+    while ($table instanceof Query) {
+      $table = $table->getTable();
+    }
+    return $table;
+  }
 
   /**
    * Implements PHP magic __toString method to convert the query to a string.
diff --git a/core/lib/Drupal/Core/Database/Query/Select.php b/core/lib/Drupal/Core/Database/Query/Select.php
index 3c687da..c2824e8 100644
--- a/core/lib/Drupal/Core/Database/Query/Select.php
+++ b/core/lib/Drupal/Core/Database/Query/Select.php
@@ -141,6 +141,7 @@ public function __construct($table, $alias = NULL, Connection $connection, $opti
     $conjunction = isset($options['conjunction']) ? $options['conjunction'] : 'AND';
     $this->where = new Condition($conjunction);
     $this->having = new Condition($conjunction);
+    $this->table = $table;
     $this->addJoin(NULL, $table, $alias);
   }
 
diff --git a/core/lib/Drupal/Core/Database/Query/SelectExtender.php b/core/lib/Drupal/Core/Database/Query/SelectExtender.php
index 9a378d2..1ef0bc6 100644
--- a/core/lib/Drupal/Core/Database/Query/SelectExtender.php
+++ b/core/lib/Drupal/Core/Database/Query/SelectExtender.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Database\Query;
 
 use Drupal\Core\Database\Connection;
+use Drupal\Core\Database\SchemaProviderInterface;
 
 /**
  * The base extender class for Select queries.
@@ -199,6 +200,10 @@ public function execute() {
     return $this->query->execute();
   }
 
+  public function executeEnsuringTable(SchemaProviderInterface $schema_provider) {
+    return $this->query->executeEnsuringTable($schema_provider);
+  }
+
   public function distinct($distinct = TRUE) {
     $this->query->distinct($distinct);
     return $this;
@@ -348,4 +353,5 @@ public function andConditionGroup() {
   public function orConditionGroup() {
     return $this->conditionGroupFactory('OR');
   }
+
 }
diff --git a/core/lib/Drupal/Core/Database/Query/SelectInterface.php b/core/lib/Drupal/Core/Database/Query/SelectInterface.php
index b784610..1ea0944 100644
--- a/core/lib/Drupal/Core/Database/Query/SelectInterface.php
+++ b/core/lib/Drupal/Core/Database/Query/SelectInterface.php
@@ -6,6 +6,7 @@
  */
 
 namespace Drupal\Core\Database\Query;
+use Drupal\Core\Database\SchemaProviderInterface;
 
 /**
  * Interface definition for a Select Query object.
@@ -489,6 +490,19 @@ public function preExecute(SelectInterface $query = NULL);
   public function execute();
 
   /**
+   * Create a table if necessary and run the query against the database.
+   *
+   * @param \Drupal\Core\Database\SchemaProviderInterface $schema_provider
+   *   A class providing a schema API array.
+   *
+   * @return \Drupal\Core\Database\StatementInterface|null
+   *   A prepared statement, or NULL if the query is not valid.
+   *
+   * @see \Drupal\Core\Database\Schema::createTable()
+   */
+  public function executeEnsuringTable(SchemaProviderInterface $schema_provider);
+
+  /**
    * Helper function to build most common HAVING conditional clauses.
    *
    * This method can take a variable number of parameters. If called with two
diff --git a/core/lib/Drupal/Core/Database/Query/Update.php b/core/lib/Drupal/Core/Database/Query/Update.php
index ffbf6be..2c3f446 100644
--- a/core/lib/Drupal/Core/Database/Query/Update.php
+++ b/core/lib/Drupal/Core/Database/Query/Update.php
@@ -18,13 +18,6 @@
 class Update extends Query implements ConditionInterface {
 
   /**
-   * The table to update.
-   *
-   * @var string
-   */
-  protected $table;
-
-  /**
    * An array of fields that will be updated.
    *
    * @var array
diff --git a/core/lib/Drupal/Core/Database/Schema.php b/core/lib/Drupal/Core/Database/Schema.php
index b588756..499c51d 100644
--- a/core/lib/Drupal/Core/Database/Schema.php
+++ b/core/lib/Drupal/Core/Database/Schema.php
@@ -7,7 +7,6 @@
 
 namespace Drupal\Core\Database;
 
-use Drupal\Core\Database\SchemaObjectExistsException;
 use Drupal\Core\Database\Query\Condition;
 use Drupal\Core\Database\Query\PlaceholderInterface;
 
@@ -684,6 +683,34 @@ public function createTable($name, $table) {
   }
 
   /**
+   * Checks a table for existence and creates it as necessary.
+   *
+   * @param $table_name
+   *   The name of the table to check and create.
+   * @param SchemaProviderInterface $schema_provider
+   *   An object providing the schema
+   *
+   * @return bool
+   *   TRUE if the table already existed or now exists, FALSE if there was an
+   *   error and it does not.
+   */
+  public function ensureTableExists($table_name, SchemaProviderInterface $schema_provider) {
+    try {
+      if (!$this->tableExists($table_name)) {
+        $this->createTable($table_name, $schema_provider->getSchema());
+        return TRUE;
+      }
+    }
+    catch (SchemaObjectExistsException $e) {
+      // If another process has already created the table, attempting to
+      // recreate it will throw an exception. In this case just catch the
+      // exception and do nothing.
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+  /**
    * Return an array of field names from an array of key/index column specifiers.
    *
    * This is usually an identity function but if a key/index uses a column prefix
diff --git a/core/lib/Drupal/Core/Database/SchemaProviderInterface.php b/core/lib/Drupal/Core/Database/SchemaProviderInterface.php
new file mode 100644
index 0000000..6c9ef98
--- /dev/null
+++ b/core/lib/Drupal/Core/Database/SchemaProviderInterface.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * @file
+ * Contains
+ */
+
+namespace Drupal\Core\Database;
+
+/**
+ * This interface is a helper for queries creating tables on demand.
+ *
+ * Passing objects implementing this interface to
+ * Drupal\Core\Database\Query::executeEnsuringTable() and
+ * Drupal\Core\Database\SelectInterface::executeEnsuringTable() allows these
+ * methods to create the necessary table if it is missing and re-run the query.
+ * For example, during testing this avoids creating unnecessary tables ahead of
+ * the time.
+ */
+interface SchemaProviderInterface {
+
+  /**
+   * A schema API array.
+   *
+   * @return array
+   *   A schema API array
+   *
+   * @see hook_schema()
+   */
+  public function getSchema();
+}
+
diff --git a/core/lib/Drupal/Core/Menu/MenuTreeStorage.php b/core/lib/Drupal/Core/Menu/MenuTreeStorage.php
index 2ed2fb0..6738607 100644
--- a/core/lib/Drupal/Core/Menu/MenuTreeStorage.php
+++ b/core/lib/Drupal/Core/Menu/MenuTreeStorage.php
@@ -14,13 +14,12 @@
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Database\Database;
-use Drupal\Core\Database\Query\SelectInterface;
-use Drupal\Core\Database\SchemaObjectExistsException;
+use Drupal\Core\Database\SchemaProviderInterface;
 
 /**
  * Provides a menu tree storage using the database.
  */
-class MenuTreeStorage implements MenuTreeStorageInterface {
+class MenuTreeStorage implements MenuTreeStorageInterface, SchemaProviderInterface {
 
   /**
    * The maximum depth of a menu links tree.
@@ -224,33 +223,6 @@ protected function purgeMultiple(array $ids) {
   }
 
   /**
-   * Executes a select query while making sure the database table exists.
-   *
-   * @param \Drupal\Core\Database\Query\SelectInterface $query
-   *   The select object to be executed.
-   *
-   * @return \Drupal\Core\Database\StatementInterface|null
-   *   A prepared statement, or NULL if the query is not valid.
-   *
-   * @throws \Exception
-   *   Thrown if the table could not be created or the database connection
-   *   failed.
-   */
-  protected function safeExecuteSelect(SelectInterface $query) {
-    try {
-      return $query->execute();
-    }
-    catch (\Exception $e) {
-      // If there was an exception, try to create the table.
-      if ($this->ensureTableExists()) {
-        return $query->execute();
-      }
-      // Some other failure that we can not recover from.
-      throw $e;
-    }
-  }
-
-  /**
    * {@inheritdoc}
    */
   public function save(array $link) {
@@ -337,7 +309,7 @@ protected function doSave(array $link) {
   protected function preSave(array &$link, array $original) {
     static $schema_fields, $schema_defaults;
     if (empty($schema_fields)) {
-      $schema = static::schemaDefinition();
+      $schema = $this->getSchema();
       $schema_fields = $schema['fields'];
       foreach ($schema_fields as $name => $spec) {
         if (isset($spec['default'])) {
@@ -470,7 +442,7 @@ protected function doFindChildrenRelativeDepth(array $original) {
       $query->condition("p$i", $original["p$i"]);
     }
 
-    $max_depth = $this->safeExecuteSelect($query)->fetchField();
+    $max_depth = $query->executeEnsuringTable($this)->fetchField();
 
     return ($max_depth > $original['depth']) ? $max_depth - $original['depth'] : 0;
   }
@@ -646,7 +618,7 @@ public function loadByProperties(array $properties) {
       }
       $query->condition($name, $value);
     }
-    $loaded = $this->safeExecuteSelect($query)->fetchAllAssoc('id', \PDO::FETCH_ASSOC);
+    $loaded = $query->executeEnsuringTable($this)->fetchAllAssoc('id', \PDO::FETCH_ASSOC);
     foreach ($loaded as $id => $link) {
       $loaded[$id] = $this->prepareLink($link);
     }
@@ -675,7 +647,7 @@ public function loadByRoute($route_name, array $route_parameters = array(), $men
     $query->orderBy('depth');
     $query->orderBy('weight');
     $query->orderBy('id');
-    $loaded = $this->safeExecuteSelect($query)->fetchAllAssoc('id', \PDO::FETCH_ASSOC);
+    $loaded = $query->executeEnsuringTable($this)->fetchAllAssoc('id', \PDO::FETCH_ASSOC);
     foreach ($loaded as $id => $link) {
       $loaded[$id] = $this->prepareLink($link);
     }
@@ -692,7 +664,7 @@ public function loadMultiple(array $ids) {
       $query = $this->connection->select($this->table, $this->options);
       $query->fields($this->table, $this->definitionFields());
       $query->condition('id', $missing_ids, 'IN');
-      $loaded = $this->safeExecuteSelect($query)->fetchAllAssoc('id', \PDO::FETCH_ASSOC);
+      $loaded = $query->executeEnsuringTable($this)->fetchAllAssoc('id', \PDO::FETCH_ASSOC);
       foreach ($loaded as $id => $link) {
         $this->definitions[$id] = $this->prepareLink($link);
       }
@@ -738,7 +710,7 @@ protected function loadFullMultiple(array $ids) {
     $query = $this->connection->select($this->table, $this->options);
     $query->fields($this->table);
     $query->condition('id', $ids, 'IN');
-    $loaded = $this->safeExecuteSelect($query)->fetchAllAssoc('id', \PDO::FETCH_ASSOC);
+    $loaded = $query->executeEnsuringTable($this)->fetchAllAssoc('id', \PDO::FETCH_ASSOC);
     foreach ($loaded as &$link) {
       foreach ($this->serializedFields() as $name) {
         $link[$name] = unserialize($link[$name]);
@@ -766,7 +738,7 @@ public function getRootPathIds($id) {
       $query->condition('mlid', $ids, 'IN');
       // @todo Cache this result in memory if we find it is being used more
       //   than once per page load. https://www.drupal.org/node/2302185
-      return $this->safeExecuteSelect($query)->fetchAllKeyed(0, 0);
+      return $query->executeEnsuringTable($this)->fetchAllKeyed(0, 0);
     }
     return array();
   }
@@ -786,7 +758,7 @@ public function getExpanded($menu_name, array $parents) {
       $query->condition('enabled', 1);
       $query->condition('parent', $parents, 'IN');
       $query->condition('id', $parents, 'NOT IN');
-      $result = $this->safeExecuteSelect($query)->fetchAllKeyed(0, 0);
+      $result = $query->executeEnsuringTable($this)->fetchAllKeyed(0, 0);
       $parents += $result;
     } while (!empty($result));
     return $parents;
@@ -944,7 +916,7 @@ protected function loadLinks($menu_name, MenuTreeParameters $parameters) {
       }
     }
 
-    $links = $this->safeExecuteSelect($query)->fetchAllAssoc('id', \PDO::FETCH_ASSOC);
+    $links = $query->executeEnsuringTable($this)->fetchAllAssoc('id', \PDO::FETCH_ASSOC);
 
     return $links;
   }
@@ -1011,7 +983,7 @@ public function menuNameInUse($menu_name) {
     $query->addField($this->table, 'mlid');
     $query->condition('menu_name', $menu_name);
     $query->range(0, 1);
-    return (bool) $this->safeExecuteSelect($query);
+    return (bool) $query->executeEnsuringTable($this);
   }
 
   /**
@@ -1021,7 +993,7 @@ public function getMenuNames() {
     $query = $this->connection->select($this->table, $this->options);
     $query->addField($this->table, 'menu_name');
     $query->distinct();
-    return $this->safeExecuteSelect($query)->fetchAllKeyed(0, 0);
+    return $query->executeEnsuringTable($this)->fetchAllKeyed(0, 0);
   }
 
   /**
@@ -1032,7 +1004,7 @@ public function countMenuLinks($menu_name = NULL) {
     if ($menu_name) {
       $query->condition('menu_name', $menu_name);
     }
-    return $this->safeExecuteSelect($query->countQuery())->fetchField();
+    return $query->countQuery()->executeEnsuringTable($this)->fetchField();
   }
 
   /**
@@ -1051,7 +1023,7 @@ public function getAllChildIds($id) {
     }
     // The next p column should not be empty. This excludes the root link.
     $query->condition("p$i", 0, '>');
-    return $this->safeExecuteSelect($query)->fetchAllKeyed(0, 0);
+    return $query->executeEnsuringTable($this)->fetchAllKeyed(0, 0);
   }
 
   /**
@@ -1131,34 +1103,6 @@ protected function treeDataRecursive(array &$links, array $parents, $depth) {
   }
 
   /**
-   * Checks if the tree table exists and create it if not.
-   *
-   * @return bool
-   *   TRUE if the table was created, FALSE otherwise.
-   *
-   * @throws \Drupal\Component\Plugin\Exception\PluginException
-   *   If a database error occurs.
-   */
-  protected function ensureTableExists() {
-    try {
-      if (!$this->connection->schema()->tableExists($this->table)) {
-        $this->connection->schema()->createTable($this->table, static::schemaDefinition());
-        return TRUE;
-      }
-    }
-    catch (SchemaObjectExistsException $e) {
-      // If another process has already created the config table, attempting to
-      // recreate it will throw an exception. In this case just catch the
-      // exception and do nothing.
-      return TRUE;
-    }
-    catch (\Exception $e) {
-      throw new PluginException($e->getMessage(), NULL, $e);
-    }
-    return FALSE;
-  }
-
-  /**
    * Determines serialized fields in the storage.
    *
    * @return array
@@ -1166,7 +1110,7 @@ protected function ensureTableExists() {
    */
   protected function serializedFields() {
     if (empty($this->serializedFields)) {
-      $schema = static::schemaDefinition();
+      $schema = $this->getSchema();
       foreach ($schema['fields'] as $name => $field) {
         if (!empty($field['serialize'])) {
           $this->serializedFields[] = $name;
@@ -1192,7 +1136,7 @@ protected function definitionFields() {
    * @return array
    *   The schema API definition for the SQL storage table.
    */
-  protected static function schemaDefinition() {
+  public function getSchema() {
     $schema = array(
       'description' => 'Contains the menu tree hierarchy.',
       'fields' => array(
diff --git a/core/modules/system/src/Tests/Cache/DatabaseBackendTagTest.php b/core/modules/system/src/Tests/Cache/DatabaseBackendTagTest.php
index c832ded..7d4410d 100644
--- a/core/modules/system/src/Tests/Cache/DatabaseBackendTagTest.php
+++ b/core/modules/system/src/Tests/Cache/DatabaseBackendTagTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\system\Tests\Cache;
 
 use Drupal\Core\Cache\Cache;
+use Drupal\Core\Database\Database;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\simpletest\DrupalUnitTestBase;
 use Symfony\Component\DependencyInjection\Reference;
@@ -48,7 +49,7 @@ public function testTagInvalidations() {
       $this->assertTrue($bin->get('test'), 'Cache item was set in bin.');
     }
 
-    $invalidations_before = intval(db_select('cachetags')->fields('cachetags', array('invalidations'))->condition('tag', 'test_tag:2')->execute()->fetchField());
+    $this->assertFalse(Database::getConnection()->schema()->tableExists('cachetags'));
     Cache::invalidateTags(array('test_tag:2'));
 
     // Test that cache entry has been invalidated in multiple bins.
@@ -59,7 +60,7 @@ public function testTagInvalidations() {
 
     // Test that only one tag invalidation has occurred.
     $invalidations_after = intval(db_select('cachetags')->fields('cachetags', array('invalidations'))->condition('tag', 'test_tag:2')->execute()->fetchField());
-    $this->assertEqual($invalidations_after, $invalidations_before + 1, 'Only one addition cache tag invalidation has occurred after invalidating a tag used in multiple bins.');
+    $this->assertEqual($invalidations_after, 1, 'Only one addition cache tag invalidation has occurred after invalidating a tag used in multiple bins.');
   }
 
   public function testTagDeletions() {
@@ -72,7 +73,7 @@ public function testTagDeletions() {
       $this->assertTrue($bin->get('test'), 'Cache item was set in bin.');
     }
 
-    $deletions_before = intval(db_select('cachetags')->fields('cachetags', array('deletions'))->condition('tag', 'test_tag:2')->execute()->fetchField());
+    $this->assertFalse(Database::getConnection()->schema()->tableExists('cachetags'));
     Cache::deleteTags(array('test_tag:2'));
 
     // Test that cache entry has been deleted in multiple bins.
@@ -83,7 +84,7 @@ public function testTagDeletions() {
 
     // Test that only one tag deletion has occurred.
     $deletions_after = intval(db_select('cachetags')->fields('cachetags', array('deletions'))->condition('tag', 'test_tag:2')->execute()->fetchField());
-    $this->assertEqual($deletions_after, $deletions_before + 1, 'Only one addition cache tag deletion has occurred after deleting a tag used in multiple bins.');
+    $this->assertEqual($deletions_after, 1, 'Only one addition cache tag deletion has occurred after deleting a tag used in multiple bins.');
   }
 
 }
diff --git a/core/modules/system/src/Tests/Database/SchemaTest.php b/core/modules/system/src/Tests/Database/SchemaTest.php
index 9009e72..5f4993b 100644
--- a/core/modules/system/src/Tests/Database/SchemaTest.php
+++ b/core/modules/system/src/Tests/Database/SchemaTest.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Database\Database;
 use Drupal\Core\Database\SchemaObjectDoesNotExistException;
 use Drupal\Core\Database\SchemaObjectExistsException;
+use Drupal\Core\Database\SchemaProviderInterface;
 use Drupal\simpletest\KernelTestBase;
 use Drupal\Component\Utility\Unicode;
 
@@ -18,7 +19,7 @@
  *
  * @group Database
  */
-class SchemaTest extends KernelTestBase {
+class SchemaTest extends KernelTestBase implements SchemaProviderInterface {
 
   /**
    * A global counter for table and field creation.
@@ -135,6 +136,8 @@ function testSchema() {
     // Try to drop the table.
     db_drop_table('test_table2');
     $this->assertFalse(db_table_exists('test_table2'), 'The dropped table does not exist.');
+    // Try to ensure the table exists.
+    $this->assertTrue(Database::getConnection()->schema()->ensureTableExists('test_table2', $this));
 
     // Recreate the table.
     db_create_table('test_table', $table_specification);
@@ -440,4 +443,11 @@ protected function assertFieldCharacteristics($table_name, $field_name, $field_s
 
     db_drop_field($table_name, $field_name);
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSchema() {
+    return drupal_get_schema_unprocessed('database_test', 'test');
+  }
 }
diff --git a/core/modules/system/src/Tests/Database/SelectTest.php b/core/modules/system/src/Tests/Database/SelectTest.php
index 26dd109..b6bc2e7 100644
--- a/core/modules/system/src/Tests/Database/SelectTest.php
+++ b/core/modules/system/src/Tests/Database/SelectTest.php
@@ -7,13 +7,14 @@
 
 namespace Drupal\system\Tests\Database;
 use Drupal\Core\Database\InvalidQueryException;
+use Drupal\Core\Database\SchemaProviderInterface;
 
 /**
  * Tests the Select query builder.
  *
  * @group Database
  */
-class SelectTest extends DatabaseTestBase {
+class SelectTest extends DatabaseTestBase implements SchemaProviderInterface {
 
   /**
    * Tests rudimentary SELECT statements.
@@ -28,6 +29,29 @@ function testSimpleSelect() {
   }
 
   /**
+   * Tests rudimentary SELECT statements with table dropping and recreation.
+   */
+  function testSimpleSelectWithEnsuringTable() {
+    db_drop_table('test');
+    $query = db_select('test');
+    $query->addField('test', 'name');
+    $query->addField('test', 'age', 'age');
+    $num_records = $query->countQuery()->executeEnsuringTable($this)->fetchField();
+    // The real test is not getting a fatal error from the exception over the
+    // missing table.
+    $this->assertEqual($num_records, 0, 'Returned the correct number of rows.');
+    // The table should be recreated, add back the sample data and re-run the
+    // test.
+    try {
+      static::addSampleData();
+    }
+    catch (\Exception $e) {
+      // Ignore adding rows to tables already filled.
+    }
+    $this->testSimpleSelect();
+  }
+
+  /**
    * Tests rudimentary SELECT statement with a COMMENT.
    */
   function testSimpleComment() {
@@ -494,4 +518,12 @@ function testEmptyInCondition() {
     }
   }
 
+  /**
+   * A schema API array.
+   *
+   * @return array
+   */
+  public function getSchema() {
+    return drupal_get_schema_unprocessed('database_test', 'test');
+  }
 }
