diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Insert.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Insert.php
index 084856a..fab1d08 100644
--- a/core/lib/Drupal/Core/Database/Driver/pgsql/Insert.php
+++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Insert.php
@@ -99,12 +99,25 @@ public function execute() {
     elseif ($options['return'] == Database::RETURN_INSERT_ID) {
       $options['return'] = Database::RETURN_NULL;
     }
-    // Only use the returned last_insert_id if it is not already set.
-    if (!empty($last_insert_id)) {
-      $this->connection->query($stmt, array(), $options);
+
+    // Create a savepoint so we can rollback a failed query. This is so we can
+    // mimic MySQL and SQLite transactions which don't fail if a single query
+    // fails. This is important for tables that are created on demand. For
+    // example, \Drupal\Core\Cache\DatabaseBackend.
+    $this->connection->addSavepoint();
+    try {
+      // Only use the returned last_insert_id if it is not already set.
+      if (!empty($last_insert_id)) {
+        $this->connection->query($stmt, array(), $options);
+      }
+      else {
+        $last_insert_id = $this->connection->query($stmt, array(), $options);
+      }
+      $this->connection->releaseSavepoint();
     }
-    else {
-      $last_insert_id = $this->connection->query($stmt, array(), $options);
+    catch (\Exception $e) {
+      $this->connection->rollbackSavepoint();
+      throw $e;
     }
 
     // Re-initialize the values array so that we can re-use this query.
diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/NativeUpsert.php b/core/lib/Drupal/Core/Database/Driver/pgsql/NativeUpsert.php
index 1f1c94b..ef8e559 100644
--- a/core/lib/Drupal/Core/Database/Driver/pgsql/NativeUpsert.php
+++ b/core/lib/Drupal/Core/Database/Driver/pgsql/NativeUpsert.php
@@ -76,11 +76,23 @@ public function execute() {
       $options['sequence_name'] = $table_information->sequences[0];
     }
 
-    $this->connection->query($stmt, [], $options);
-
     // Re-initialize the values array so that we can re-use this query.
     $this->insertValues = [];
 
+    // Create a savepoint so we can rollback a failed query. This is so we can
+    // mimic MySQL and SQLite transactions which don't fail if a single query
+    // fails. This is important for tables that are created on demand. For
+    // example, \Drupal\Core\Cache\DatabaseBackend.
+    $this->connection->addSavepoint();
+    try {
+      $this->connection->query($stmt, [], $options);
+      $this->connection->releaseSavepoint();
+    }
+    catch (\Exception $e) {
+      $this->connection->rollbackSavepoint();
+      throw $e;
+    }
+
     return TRUE;
   }
 
diff --git a/core/modules/system/src/Tests/Database/TransactionTest.php b/core/modules/system/src/Tests/Database/TransactionTest.php
index 1137b21..3386acb 100644
--- a/core/modules/system/src/Tests/Database/TransactionTest.php
+++ b/core/modules/system/src/Tests/Database/TransactionTest.php
@@ -492,5 +492,115 @@ function testTransactionStacking() {
     $this->assertRowAbsent('inner');
     $this->assertRowAbsent('inner2');
   }
-}
 
+  /**
+   * Tests that transactions can continue to be used if a query fails.
+   */
+  public function testQueryFailureInTransaction() {
+    $connection = Database::getConnection();
+    $transaction = $connection->startTransaction('test_transaction');
+    $connection->schema()->dropTable('test');
+
+    // Test a failed select query.
+    try {
+      $connection->select('test')
+        ->fields('test', ['name'])
+        ->execute();
+
+      $this->fail('Select query failed.');
+    }
+    catch (\Exception $e) {
+      $this->pass('Select query failed.');
+    }
+
+    // Test a failed insert query.
+    try {
+      $connection->insert('test')
+        ->fields([
+          'name' => 'David',
+          'age' => '24',
+        ])
+        ->execute();
+
+      $this->fail('Insert query failed.');
+    }
+    catch (\Exception $e) {
+      $this->pass('Insert query failed.');
+    }
+
+    // Test a failed update query.
+    try {
+      $connection->update('test')
+        ->fields(['name' => 'Tiffany'])
+        ->condition('id', 1)
+        ->execute();
+
+      $this->fail('Update query failed.');
+    }
+    catch (\Exception $e) {
+      $this->pass('Update query failed.');
+    }
+
+    // Test a failed delete query.
+    try {
+      $connection->delete('test')
+        ->condition('id', 1)
+        ->execute();
+
+      $this->fail('Delete query failed.');
+    }
+    catch (\Exception $e) {
+      $this->pass('Delete query failed.');
+    }
+
+    // Test a failed merge query.
+    try {
+      $connection->merge('test')
+        ->key('job', 'Presenter')
+        ->fields([
+          'age' => '31',
+          'name' => 'Tiffany',
+        ])
+        ->execute();
+
+      $this->fail('Merge query failed.');
+    }
+    catch (\Exception $e) {
+      $this->pass('Merge query failed.');
+    }
+
+    // Test a failed upsert query.
+    try {
+      $connection->upsert('test')
+        ->key('job')
+        ->fields(['job', 'age', 'name'])
+        ->values([
+          'job' => 'Presenter',
+          'age' => 31,
+          'name' => 'Tiffany',
+        ])
+        ->execute();
+
+      $this->fail('Upset query failed.');
+    }
+    catch (\Exception $e) {
+      $this->pass('Upset query failed.');
+    }
+
+    // Create the missing schema and insert a row.
+    $this->installSchema('database_test', ['test']);
+    $connection->insert('test')
+      ->fields(array(
+        'name' => 'David',
+        'age' => '24',
+      ))
+      ->execute();
+
+    // Commit the transaction.
+    unset($transaction);
+
+    $saved_age = $connection->query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'David'))->fetchField();
+    $this->assertEqual('24', $saved_age);
+  }
+
+}
