diff --git a/core/modules/migrate/src/MigrateExecutable.php b/core/modules/migrate/src/MigrateExecutable.php
index c0b3537..ef5b86c 100644
--- a/core/modules/migrate/src/MigrateExecutable.php
+++ b/core/modules/migrate/src/MigrateExecutable.php
@@ -329,6 +329,12 @@ public function rollback() {
         // We're now done with this row, so remove it from the map.
         $id_map->deleteDestination($destination_key);
       }
+      else {
+        // If there is no destination key the import probably failed and we can
+        // remove the row without further action.
+        $source_key = $id_map->currentSource();
+        $id_map->delete($source_key);
+      }
 
       // Check for memory exhaustion.
       if (($return = $this->checkStatus()) != MigrationInterface::RESULT_COMPLETED) {
diff --git a/core/modules/migrate/src/Plugin/MigrateIdMapInterface.php b/core/modules/migrate/src/Plugin/MigrateIdMapInterface.php
index d3bdb89..eafac29 100644
--- a/core/modules/migrate/src/Plugin/MigrateIdMapInterface.php
+++ b/core/modules/migrate/src/Plugin/MigrateIdMapInterface.php
@@ -244,6 +244,14 @@ public function lookupDestinationIds(array $source_id_values);
   public function currentDestination();
 
   /**
+   * Looks up the source identifier(s) currently being iterated.
+   *
+   * @return array
+   *   The source identifier values of the record, or NULL on failure.
+   */
+  public function currentSource();
+
+  /**
    * Removes any persistent storage used by this map.
    *
    * For example, remove the map and message tables.
diff --git a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php
index cf39275..cbf4a9f 100644
--- a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php
+++ b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php
@@ -845,7 +845,25 @@ public function currentDestination() {
     if ($this->valid()) {
       $result = array();
       foreach ($this->destinationIdFields() as $destination_field_name => $idmap_field_name) {
-        $result[$destination_field_name] = $this->currentRow[$idmap_field_name];
+        if (!is_null($this->currentRow[$idmap_field_name])) {
+          $result[$destination_field_name] = $this->currentRow[$idmap_field_name];
+        }
+      }
+      return $result;
+    }
+    else {
+      return NULL;
+    }
+  }
+
+  /**
+   * @inheritdoc
+   */
+  public function currentSource() {
+    if ($this->valid()) {
+      $result = array();
+      foreach ($this->sourceIdFields() as $field_name => $source_id) {
+        $result[$field_name] = $this->currentKey[$source_id];
       }
       return $result;
     }
diff --git a/core/modules/migrate/tests/src/Kernel/MigrateRollbackTest.php b/core/modules/migrate/tests/src/Kernel/MigrateRollbackTest.php
index 9ec0c65..4b78e65 100644
--- a/core/modules/migrate/tests/src/Kernel/MigrateRollbackTest.php
+++ b/core/modules/migrate/tests/src/Kernel/MigrateRollbackTest.php
@@ -128,6 +128,9 @@ public function testRollback() {
       $this->assertNotNull($map_row['destid1']);
     }
 
+    // Add a failed row to test if this can be rolled back without errors.
+    $this->mockFailure($term_migration, ['id' => '4', 'vocab' => '2', 'name' => 'FAIL']);
+
     // Rollback and verify the entities are gone.
     $term_executable->rollback();
     foreach ($term_data_rows as $row) {
diff --git a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php
index a9d1313..e38e971 100644
--- a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php
+++ b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php
@@ -641,6 +641,35 @@ public function testLookupSourceIDMapping($num_source_fields, $num_destination_f
   }
 
   /**
+   * Tests currentDestination() and currentSource().
+   */
+  public function testCurrentDestinationAndSource() {
+    // Simple map with one source and one destination ID.
+    $id_map = $this->setupRows(['nid'], ['nid'], [
+      [1, 101],
+      [2, 102],
+      [3, 103],
+      // Mock a failed row by setting the destination ID to NULL.
+      [4, NULL],
+    ]);
+
+    // The rows are ordered by destination ID so the failed row should be first.
+    $id_map->rewind();
+    $this->assertEquals([], $id_map->currentDestination());
+    $this->assertEquals(['nid' => 4], $id_map->currentSource());
+    $id_map->next();
+    $this->assertEquals(['nid' => 101], $id_map->currentDestination());
+    $this->assertEquals(['nid' => 1], $id_map->currentSource());
+    $id_map->next();
+    $this->assertEquals(['nid' => 102], $id_map->currentDestination());
+    $this->assertEquals(['nid' => 2], $id_map->currentSource());
+    $id_map->next();
+    $this->assertEquals(['nid' => 103], $id_map->currentDestination());
+    $this->assertEquals(['nid' => 3], $id_map->currentSource());
+    $id_map->next();
+  }
+
+  /**
    * Tests the imported count method.
    *
    * Scenarios to test for:
