diff --git a/core/includes/database.inc b/core/includes/database.inc
index 15ffcc3..24ce03a 100644
--- a/core/includes/database.inc
+++ b/core/includes/database.inc
@@ -124,10 +124,11 @@
  * databases that do not support transactions. To start a new transaction,
  * simply call $txn = db_transaction(); in your own code. The transaction will
  * remain open for as long as the variable $txn remains in scope.  When $txn is
- * destroyed, the transaction will be committed.  If your transaction is nested
- * inside of another then Drupal will track each transaction and only commit
- * the outer-most transaction when the last transaction object goes out out of
- * scope, that is, all relevant queries completed successfully.
+ * destroyed, the transaction will be rolled back, if it has not already been
+ * committed or rolled back. If your transaction is nested inside of another
+ * then Drupal will track each transaction and only commit the outer-most
+ * transaction when the last transaction object is committed, that is, all
+ * relevant queries completed successfully.
  *
  * Example:
  * @code
@@ -145,17 +146,25 @@
  *
  *     my_other_function($id);
  *
+ *     // We need to commit our changes before returning; otherwise the
+ *     // transaction is automatically rolled back when $txn goes out of scope.
+ *     $txn->commit();
+ *
  *     return $id;
  *   }
  *   catch (Exception $e) {
- *     // Something went wrong somewhere, so roll back now.
+ *     // Something went wrong somewhere, so roll back now. If we do not call
+ *     // rollback() explicitly, the transaction will automatically be rolled
+ *     // back when $txn goes out of scope. This feature also works without a
+ *     // try/catch statement. However, in this case we need to call rollback()
+  *    // in order to log the exception (see below).
  *     $txn->rollback();
- *     // Log the exception to watchdog.
+ *
+ *     // Log the exception to watchdog. This needs to happen *after* the
+ *     // rollback (if we are logging to the database) - otherwise the log entry
+ *     // will also be rolled back.
  *     watchdog_exception('type', $e);
  *   }
- *
- *   // $txn goes out of scope here.  Unless the transaction was rolled back, it
- *   // gets automatically committed here.
  * }
  *
  * function my_other_function($id) {
diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index 3ef5442..ba07efb 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -2674,6 +2674,7 @@ function menu_router_rebuild() {
     _menu_clear_page_cache();
     // Indicate that the menu has been successfully rebuilt.
     state()->delete('menu_rebuild_needed');
+    $transaction->commit();
   }
   catch (Exception $e) {
     $transaction->rollback();
diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php
index abda556..df8c0f3 100644
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php
+++ b/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php
@@ -319,9 +319,9 @@ class Connection extends DatabaseConnection {
         ':existing_id' => $existing_id,
       ));
     }
-    // The transaction gets committed when the transaction object gets destroyed
-    // because it gets out of scope.
-    return $this->query('SELECT value FROM {sequences}')->fetchField();
+    $id = $this->query('SELECT value FROM {sequences}')->fetchField();
+    $transaction->commit();
+    return $id;
   }
 
   public function rollback($savepoint_name = 'drupal_transaction') {
diff --git a/core/lib/Drupal/Core/Database/Query/Insert.php b/core/lib/Drupal/Core/Database/Query/Insert.php
index b3c4c0e..e6317ca 100644
--- a/core/lib/Drupal/Core/Database/Query/Insert.php
+++ b/core/lib/Drupal/Core/Database/Query/Insert.php
@@ -214,23 +214,15 @@ class Insert extends Query {
     // databases, such as SQLite, this is also a notable performance boost.
     $transaction = $this->connection->startTransaction();
 
-    try {
-      $sql = (string) $this;
-      foreach ($this->insertValues as $insert_values) {
-        $last_insert_id = $this->connection->query($sql, $insert_values, $this->queryOptions);
-      }
-    }
-    catch (Exception $e) {
-      // One of the INSERTs failed, rollback the whole batch.
-      $transaction->rollback();
-      // Rethrow the exception for the calling code.
-      throw $e;
+    $sql = (string) $this;
+    foreach ($this->insertValues as $insert_values) {
+      $last_insert_id = $this->connection->query($sql, $insert_values, $this->queryOptions);
     }
 
     // Re-initialize the values array so that we can re-use this query.
     $this->insertValues = array();
 
-    // Transaction commits here where $transaction looses scope.
+    $transaction->commit();
 
     return $last_insert_id;
   }
diff --git a/core/lib/Drupal/Core/Database/Transaction.php b/core/lib/Drupal/Core/Database/Transaction.php
index 2dceb24..68b2ea1 100644
--- a/core/lib/Drupal/Core/Database/Transaction.php
+++ b/core/lib/Drupal/Core/Database/Transaction.php
@@ -11,16 +11,14 @@
  * A wrapper class for creating and managing database transactions.
  *
  * Not all databases or database configurations support transactions. For
- * example, MySQL MyISAM tables do not. It is also easy to begin a transaction
- * and then forget to commit it, which can lead to connection errors when
- * another transaction is started.
+ * example, MySQL MyISAM tables do not. This wrapper will check to see if the
+ * specified connection supports transactions. If not, it will simply skip any
+ * transaction commands, allowing user-space code to proceed normally. The only
+ * difference is that rollbacks will not actually do anything.
  *
  * This class acts as a wrapper for transactions. To begin a transaction,
  * simply instantiate it. When the object goes out of scope and is destroyed
- * it will automatically commit. It also will check to see if the specified
- * connection supports transactions. If not, it will simply skip any transaction
- * commands, allowing user-space code to proceed normally. The only difference
- * is that rollbacks won't actually do anything.
+ * it will automatically roll back.
  *
  * In the vast majority of cases, you should not instantiate this class
  * directly. Instead, call ->startTransaction(), from the appropriate connection
@@ -36,11 +34,17 @@ class Transaction {
   protected $connection;
 
   /**
-   * A boolean value to indicate whether this transaction has been rolled back.
+   * A boolean value to indicate whether the transaction is active.
    *
-   * @var Boolean
+   * This does not reflect if a nested transaction is no longer active, because
+   * the outer transaction has been rolled back, or if the transaction has been
+   * implicitly committed (on MySQL, DDL statements cause an implicit commit).
+   *
+   * @var bool
+   *   TRUE if the transaction has been explicitly been committed or rolled
+   *   back; FALSE otherwise.
    */
-  protected $rolledBack = FALSE;
+  protected $isActive = TRUE;
 
   /**
    * The name of the transaction.
@@ -68,10 +72,20 @@ class Transaction {
     $this->connection->pushTransaction($this->name);
   }
 
+  /**
+   * Destructs a Transaction object.
+   *
+   * If the transaction is still active, it is rolled back.
+   */
   public function __destruct() {
-    // If we rolled back then the transaction would have already been popped.
-    if (!$this->rolledBack) {
-      $this->connection->popTransaction($this->name);
+    if ($this->isActive) {
+      try {
+        $this->rollback();
+      }
+      catch (TransactionNoActiveException $e) {
+        // If this is a nested transaction, and the outer transaction was rolled
+        // back, this transaction has already been rolled back implicitly.
+      }
     }
   }
 
@@ -83,19 +97,34 @@ class Transaction {
   }
 
   /**
-   * Rolls back the current transaction.
+   * Commits the current transaction.
    *
-   * This is just a wrapper method to rollback whatever transaction stack we are
+   * This is just a wrapper method to commit whatever transaction stack we are
    * currently in, which is managed by the connection object itself. Note that
-   * logging (preferable with watchdog_exception()) needs to happen after a
+   * logging (preferably with watchdog_exception()) needs to happen after a
    * transaction has been rolled back or the log messages will be rolled back
    * too.
    *
-   * @see Drupal\Core\Database\Connection::rollback()
+   * @see Drupal\Core\Database\Transaction::rollback()
+   * @see Drupal\Core\Database\Connection::popTransaction()
    * @see watchdog_exception()
    */
+  public function commit() {
+    $this->connection->popTransaction($this->name);
+    $this->isActive = FALSE;
+  }
+
+  /**
+   * Rolls back the current transaction.
+   *
+   * This is just a wrapper method to rollback whatever transaction stack we are
+   * currently in, which is managed by the connection object itself.
+   *
+   * @see Drupal\Core\Database\Transaction::commit()
+   * @see Drupal\Core\Database\Connection::rollback()
+   */
   public function rollback() {
-    $this->rolledBack = TRUE;
     $this->connection->rollback($this->name);
+    $this->isActive = FALSE;
   }
 }
diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
index 9092a13..0fbea8b 100644
--- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
+++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
@@ -494,6 +494,7 @@ class DatabaseStorageController implements EntityStorageControllerInterface {
       }
       // Ignore slave server temporarily.
       db_ignore_slave();
+      $transaction->commit();
     }
     catch (\Exception $e) {
       $transaction->rollback();
@@ -548,6 +549,7 @@ class DatabaseStorageController implements EntityStorageControllerInterface {
       // Ignore slave server temporarily.
       db_ignore_slave();
       unset($entity->original);
+      $transaction->commit();
 
       return $return;
     }
diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
index e8998f8..83be7af 100644
--- a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
+++ b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
@@ -322,6 +322,7 @@ class DatabaseStorageControllerNG extends DatabaseStorageController {
       // Ignore slave server temporarily.
       db_ignore_slave();
       unset($entity->original);
+      $transaction->commit();
 
       return $return;
     }
diff --git a/core/lib/Drupal/Core/Routing/MatcherDumper.php b/core/lib/Drupal/Core/Routing/MatcherDumper.php
index 33f8801..c9fe4a6 100644
--- a/core/lib/Drupal/Core/Routing/MatcherDumper.php
+++ b/core/lib/Drupal/Core/Routing/MatcherDumper.php
@@ -130,7 +130,7 @@ class MatcherDumper implements MatcherDumperInterface {
     // the queued routes.
     $this->routes = NULL;
 
-    // Transaction ends here.
+    $txn->commit();
   }
 
   /**
diff --git a/core/modules/system/lib/Drupal/system/Tests/Database/TransactionTest.php b/core/modules/system/lib/Drupal/system/Tests/Database/TransactionTest.php
index 4d5ecf1..5cfbf30 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Database/TransactionTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Database/TransactionTest.php
@@ -92,6 +92,9 @@ class TransactionTest extends DatabaseTestBase {
       $txn->rollback();
       $this->assertTrue(($connection->transactionDepth() == $depth), 'Transaction has rolled back to the last savepoint after calling rollback().');
     }
+    else {
+      $txn->commit();
+    }
   }
 
   /**
@@ -152,6 +155,9 @@ class TransactionTest extends DatabaseTestBase {
       $txn->rollback();
       $this->assertTrue(($connection->transactionDepth() == $depth), 'Transaction has rolled back to the last savepoint after calling rollback().');
     }
+    else {
+      $txn->commit();
+    }
   }
 
   /**
@@ -237,7 +243,7 @@ function testTransactionWithDdlStatement() {
     $transaction = db_transaction();
     $this->insertRow('row');
     $this->executeDDLStatement();
-    unset($transaction);
+    $transaction->commit();
     $this->assertRowPresent('row');
 
     // Even in different order.
@@ -245,7 +251,7 @@ function testTransactionWithDdlStatement() {
     $transaction = db_transaction();
     $this->executeDDLStatement();
     $this->insertRow('row');
-    unset($transaction);
+    $transaction->commit();
     $this->assertRowPresent('row');
 
     // Even with stacking.
@@ -253,11 +259,11 @@ function testTransactionWithDdlStatement() {
     $transaction = db_transaction();
     $transaction2 = db_transaction();
     $this->executeDDLStatement();
-    unset($transaction2);
+    $transaction2->commit();
     $transaction3 = db_transaction();
     $this->insertRow('row');
-    unset($transaction3);
-    unset($transaction);
+    $transaction3->commit();
+    $transaction->commit();
     $this->assertRowPresent('row');
 
     // A transaction after a DDL statement should still work the same.
@@ -265,12 +271,12 @@ function testTransactionWithDdlStatement() {
     $transaction = db_transaction();
     $transaction2 = db_transaction();
     $this->executeDDLStatement();
-    unset($transaction2);
+    $transaction2->commit();
     $transaction3 = db_transaction();
     $this->insertRow('row');
     $transaction3->rollback();
-    unset($transaction3);
-    unset($transaction);
+    $transaction3->commit();
+    $transaction->commit();
     $this->assertRowAbsent('row');
 
     // The behavior of a rollback depends on the type of database server.
@@ -282,7 +288,6 @@ function testTransactionWithDdlStatement() {
       $this->insertRow('row');
       $this->executeDDLStatement();
       $transaction->rollback();
-      unset($transaction);
       $this->assertRowAbsent('row');
 
       // Including with stacking.
@@ -290,12 +295,12 @@ function testTransactionWithDdlStatement() {
       $transaction = db_transaction();
       $transaction2 = db_transaction();
       $this->executeDDLStatement();
-      unset($transaction2);
+      $transaction2->commit();
       $transaction3 = db_transaction();
       $this->insertRow('row');
-      unset($transaction3);
+      $transaction3->commit();
       $transaction->rollback();
-      unset($transaction);
+      $transaction->commit();
       $this->assertRowAbsent('row');
     }
     else {
@@ -308,7 +313,6 @@ function testTransactionWithDdlStatement() {
       // Rollback the outer transaction.
       try {
         $transaction->rollback();
-        unset($transaction);
         // @TODO: an exception should be triggered here, but is not, because
         // "ROLLBACK" fails silently in MySQL if there is no transaction active.
         // $this->fail(t('Rolling back a transaction containing DDL should fail.'));
@@ -406,26 +410,26 @@ function testTransactionStacking() {
     $transaction2 = db_transaction();
     $this->insertRow('inner');
     // Pop the inner transaction.
-    unset($transaction2);
+    $transaction2->commit();
     $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the inner transaction');
     // Pop the outer transaction.
-    unset($transaction);
+    $transaction->commit();
     $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the outer transaction');
     $this->assertRowPresent('outer');
     $this->assertRowPresent('inner');
 
-    // Pop the transaction in a different order they have been pushed.
+    // Pop the transaction in a different order than they have been pushed.
     $this->cleanUp();
     $transaction = db_transaction();
     $this->insertRow('outer');
     $transaction2 = db_transaction();
     $this->insertRow('inner');
     // Pop the outer transaction, nothing should happen.
-    unset($transaction);
+    $transaction->commit();
     $this->insertRow('inner-after-outer-commit');
     $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction');
     // Pop the inner transaction, the whole transaction should commit.
-    unset($transaction2);
+    $transaction2->commit();
     $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction');
     $this->assertRowPresent('outer');
     $this->assertRowPresent('inner');
@@ -439,11 +443,10 @@ function testTransactionStacking() {
     $this->insertRow('inner');
     // Now rollback the inner transaction.
     $transaction2->rollback();
-    unset($transaction2);
     $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction');
     // Pop the outer transaction, it should commit.
     $this->insertRow('outer-after-inner-rollback');
-    unset($transaction);
+    $transaction->commit();
     $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction');
     $this->assertRowPresent('outer');
     $this->assertRowAbsent('inner');
@@ -456,11 +459,10 @@ function testTransactionStacking() {
     $transaction2 = db_transaction();
     $this->insertRow('inner');
     // Pop the outer transaction, nothing should happen.
-    unset($transaction);
+    $transaction->commit();
     $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction');
     // Now rollback the inner transaction, it should rollback.
     $transaction2->rollback();
-    unset($transaction2);
     $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction');
     $this->assertRowPresent('outer');
     $this->assertRowAbsent('inner');
@@ -478,7 +480,6 @@ function testTransactionStacking() {
     // Rollback the outer transaction.
     try {
       $transaction->rollback();
-      unset($transaction);
       $this->fail('Rolling back the outer transaction while the inner transaction is active resulted in an exception.');
     }
     catch (TransactionOutOfOrderException $e) {
@@ -486,12 +487,11 @@ function testTransactionStacking() {
     }
     $this->assertFalse($database->inTransaction(), 'No more in a transaction after rolling back the outer transaction');
     // Try to commit one inner transaction.
-    unset($transaction3);
+    $transaction3->commit();
     $this->pass('Trying to commit an inner transaction resulted in an exception.');
     // Try to rollback one inner transaction.
     try {
-      $transaction->rollback();
-      unset($transaction2);
+      $transaction2->rollback();
       $this->fail('Trying to commit an inner transaction resulted in an exception.');
     }
     catch (TransactionNoActiveException $e) {
