diff --git a/core/includes/batch.inc b/core/includes/batch.inc
index 5d05cd5039..2eecf4c431 100644
--- a/core/includes/batch.inc
+++ b/core/includes/batch.inc
@@ -378,8 +378,10 @@ function &_batch_current_set() {
  */
 function _batch_next_set() {
   $batch = &batch_get();
-  if (isset($batch['sets'][$batch['current_set'] + 1])) {
-    $batch['current_set']++;
+  $set_indexes = array_keys($batch['sets']);
+  $current_set_index_key = array_search($batch['current_set'], $set_indexes);
+  if (isset($set_indexes[$current_set_index_key + 1])) {
+    $batch['current_set'] = $set_indexes[$current_set_index_key + 1];
     $current_set = &_batch_current_set();
     if (isset($current_set['form_submit']) && ($callback = $current_set['form_submit']) && is_callable($callback)) {
       // We use our stored copies of $form and $form_state to account for
diff --git a/core/includes/form.inc b/core/includes/form.inc
index 9d253351e2..2fffe1cac4 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -757,12 +757,54 @@ function batch_set($batch_definition) {
     else {
       // The set is being added while the batch is running. Insert the new set
       // right after the current one to ensure execution order, and store its
-      // operations in a queue.
-      $index = $batch['current_set'] + 1;
-      $slice1 = array_slice($batch['sets'], 0, $index);
-      $slice2 = array_slice($batch['sets'], $index);
-      $batch['sets'] = array_merge($slice1, [$batch_set], $slice2);
-      _batch_populate_queue($batch, $index);
+      // operations in a queue. If multiple sets are being added while the batch
+      // is running ensure that each new one will be appended instead of
+      // prepended.
+      $append_after_index = $batch['current_set'];
+      $reached_current_set = FALSE;
+      foreach ($batch['sets'] as $index => $set) {
+        // As the indexes are not ordered numerically we need to first reach
+        // the index of the current set and then search for the proper place to
+        // append the new batch set.
+        if (!$reached_current_set) {
+          if ($index == $batch['current_set']) {
+            $reached_current_set = TRUE;
+          }
+          continue;
+        }
+        if ($index > $append_after_index) {
+          if (isset($set['appended_after_index'])) {
+            $append_after_index = $index;
+          }
+          else {
+            break;
+          }
+        }
+      }
+      $batch_set['appended_after_index'] = $append_after_index;
+
+      // Iterate by reference over the existing batch sets and assign them by
+      // reference in the new batch sets array in order not to break a retrieved
+      // reference to the current set. Among other places a reference to the
+      // current set is being retrieved in _batch_process(). We have to preserve
+      // the original indexes as they are used to generate the queue name of
+      // each batch set, as if we do not preserve the indexes of the existing
+      // batch sets the operations of the new batch will be queued in the queue
+      // of a previous batch set, because without index preservation we will be
+      // using not a new, but an existing and already used index for the new
+      // batch set.
+      // @see _batch_populate_queue().
+      $new_sets = [];
+      foreach ($batch['sets'] as $index => &$set) {
+        $new_sets[$index] = &$set;
+        if ($index == $append_after_index) {
+          $new_set_index = count($batch['sets']);
+          $new_sets[$new_set_index] = $batch_set;
+        }
+      }
+
+      $batch['sets'] = $new_sets;
+      _batch_populate_queue($batch, $new_set_index);
     }
   }
 }
diff --git a/core/modules/system/tests/modules/batch_test/batch_test.callbacks.inc b/core/modules/system/tests/modules/batch_test/batch_test.callbacks.inc
index 9116c85fed..d96cccb456 100644
--- a/core/modules/system/tests/modules/batch_test/batch_test.callbacks.inc
+++ b/core/modules/system/tests/modules/batch_test/batch_test.callbacks.inc
@@ -77,15 +77,50 @@ function _batch_test_callback_5($id, $sleep, &$context) {
 /**
  * Implements callback_batch_operation().
  *
- * Performs a batch operation setting up its own batch.
+ * Performs a simple batch operation.
  */
-function _batch_test_nested_batch_callback() {
-  batch_test_stack('setting up batch 2');
-  batch_set(_batch_test_batch_2());
+function _batch_test_callback_6($id, $sleep, &$context) {
+  // No-op, but ensure the batch take a couple iterations.
+  // Batch needs time to run for the test, so sleep a bit.
+  usleep($sleep);
+  // Track execution, and store some result for post-processing in the
+  // 'finished' callback.
+  batch_test_stack("op 6 id $id");
+  $context['results'][6][] = $id;
 }
 
 /**
- * Provides a common 'finished' callback for batches 1 to 4.
+ * Implements callback_batch_operation().
+ *
+ * Performs a simple batch operation.
+ */
+function _batch_test_callback_7($id, $sleep, &$context) {
+  // No-op, but ensure the batch take a couple iterations.
+  // Batch needs time to run for the test, so sleep a bit.
+  usleep($sleep);
+  // Track execution, and store some result for post-processing in the
+  // 'finished' callback.
+  batch_test_stack("op 7 id $id");
+  $context['results'][7][] = $id;
+}
+
+/**
+ * Implements callback_batch_operation().
+ *
+ * Performs a batch operation setting up its own batch(es).
+ */
+function _batch_test_nested_batch_callback(array $batches = []) {
+  foreach ($batches as $batch) {
+    batch_test_stack("setting up batch $batch");
+    $function = '_batch_test_batch_' . $batch;
+    batch_set($function());
+  }
+  \Drupal::state()
+    ->set('batch_test_nested_order_multiple_batches', batch_get());
+}
+
+/**
+ * Provides a common 'finished' callback for batches 1 to 7.
  */
 function _batch_test_finished_helper($batch_id, $success, $results, $operations) {
   if ($results) {
@@ -182,3 +217,21 @@ function _batch_test_finished_4($success, $results, $operations) {
 function _batch_test_finished_5($success, $results, $operations) {
   _batch_test_finished_helper(5, $success, $results, $operations);
 }
+
+/**
+ * Implements callback_batch_finished().
+ *
+ * Triggers 'finished' callback for batch 6.
+ */
+function _batch_test_finished_6($success, $results, $operations) {
+  _batch_test_finished_helper(6, $success, $results, $operations);
+}
+
+/**
+ * Implements callback_batch_finished().
+ *
+ * Triggers 'finished' callback for batch 7.
+ */
+function _batch_test_finished_7($success, $results, $operations) {
+  _batch_test_finished_helper(7, $success, $results, $operations);
+}
diff --git a/core/modules/system/tests/modules/batch_test/batch_test.module b/core/modules/system/tests/modules/batch_test/batch_test.module
index 59327de4fe..e169f61af9 100644
--- a/core/modules/system/tests/modules/batch_test/batch_test.module
+++ b/core/modules/system/tests/modules/batch_test/batch_test.module
@@ -24,6 +24,7 @@ function _batch_test_batch_0() {
     'operations' => [],
     'finished' => '_batch_test_finished_0',
     'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc',
+    'batch_test_id' => 'batch_0',
   ];
   return $batch;
 }
@@ -46,6 +47,7 @@ function _batch_test_batch_1() {
     'operations' => $operations,
     'finished' => '_batch_test_finished_1',
     'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc',
+    'batch_test_id' => 'batch_1',
   ];
   return $batch;
 }
@@ -67,6 +69,7 @@ function _batch_test_batch_2() {
     'operations' => $operations,
     'finished' => '_batch_test_finished_2',
     'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc',
+    'batch_test_id' => 'batch_2',
   ];
   return $batch;
 }
@@ -98,6 +101,7 @@ function _batch_test_batch_3() {
     'operations' => $operations,
     'finished' => '_batch_test_finished_3',
     'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc',
+    'batch_test_id' => 'batch_3',
   ];
   return $batch;
 }
@@ -119,7 +123,7 @@ function _batch_test_batch_4() {
   for ($i = 1; $i <= round($total / 2); $i++) {
     $operations[] = ['_batch_test_callback_1', [$i, $sleep]];
   }
-  $operations[] = ['_batch_test_nested_batch_callback', []];
+  $operations[] = ['_batch_test_nested_batch_callback', [[2]]];
   for ($i = round($total / 2) + 1; $i <= $total; $i++) {
     $operations[] = ['_batch_test_callback_1', [$i, $sleep]];
   }
@@ -127,6 +131,7 @@ function _batch_test_batch_4() {
     'operations' => $operations,
     'finished' => '_batch_test_finished_4',
     'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc',
+    'batch_test_id' => 'batch_4',
   ];
   return $batch;
 }
@@ -149,6 +154,61 @@ function _batch_test_batch_5() {
     'operations' => $operations,
     'finished' => '_batch_test_finished_5',
     'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc',
+    'batch_test_id' => 'batch_5',
+  ];
+  return $batch;
+}
+
+/**
+ * Batch 6: Repeats a simple operation.
+ *
+ * Operations: op 6 from 1 to 10.
+ */
+function _batch_test_batch_6() {
+  // Ensure the batch takes at least two iterations.
+  $total = 10;
+  $sleep = (1000000 / $total) * 2;
+
+  $operations = [];
+  for ($i = 1; $i <= $total; $i++) {
+    $operations[] = ['_batch_test_callback_6', [$i, $sleep]];
+  }
+  $batch = [
+    'operations' => $operations,
+    'finished' => '_batch_test_finished_6',
+    'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc',
+    'batch_test_id' => 'batch_6',
+  ];
+  return $batch;
+}
+
+/**
+ * Batch 6: Performs two batches within a batch.
+ *
+ * Operations:
+ * - op 7 from 1 to 5,
+ * - set batch 5 (op 5 from 1 to 10, should run at the end before batch 2)
+ * - set batch 6 (op 6 from 1 to 10, should run at the end after batch 1)
+ * - op 7 from 6 to 10,
+ */
+function _batch_test_batch_7() {
+  // Ensure the batch takes at least two iterations.
+  $total = 10;
+  $sleep = (1000000 / $total) * 2;
+
+  $operations = [];
+  for ($i = 1; $i <= round($total / 2); $i++) {
+    $operations[] = ['_batch_test_callback_7', [$i, $sleep]];
+  }
+  $operations[] = ['_batch_test_nested_batch_callback', [[5, 6]]];
+  for ($i = round($total / 2) + 1; $i <= $total; $i++) {
+    $operations[] = ['_batch_test_callback_7', [$i, $sleep]];
+  }
+  $batch = [
+    'operations' => $operations,
+    'finished' => '_batch_test_finished_7',
+    'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc',
+    'batch_test_id' => 'batch_7',
   ];
   return $batch;
 }
@@ -187,13 +247,15 @@ function _batch_test_title_callback() {
  * Helper function: Stores or retrieves traced execution data.
  */
 function batch_test_stack($data = NULL, $reset = FALSE) {
+  $state = \Drupal::state();
   if ($reset) {
-    \Drupal::state()->delete('batch_test.stack');
+    $state->delete('batch_test.stack');
   }
   if (!isset($data)) {
-    return \Drupal::state()->get('batch_test.stack');
+    return $state->get('batch_test.stack');
   }
-  $stack = \Drupal::state()->get('batch_test.stack');
+  $state->resetCache();
+  $stack = $state->get('batch_test.stack');
   $stack[] = $data;
-  \Drupal::state()->set('batch_test.stack', $stack);
+  $state->set('batch_test.stack', $stack);
 }
diff --git a/core/modules/system/tests/modules/batch_test/src/Form/BatchTestSimpleForm.php b/core/modules/system/tests/modules/batch_test/src/Form/BatchTestSimpleForm.php
index 999649dfc2..b6dee1a9e6 100644
--- a/core/modules/system/tests/modules/batch_test/src/Form/BatchTestSimpleForm.php
+++ b/core/modules/system/tests/modules/batch_test/src/Form/BatchTestSimpleForm.php
@@ -30,7 +30,10 @@ public function buildForm(array $form, FormStateInterface $form_state) {
         'batch_2' => 'batch 2',
         'batch_3' => 'batch 3',
         'batch_4' => 'batch 4',
+        'batch_6' => 'batch 6',
+        'batch_7' => 'batch 7',
       ],
+      '#multiple' => TRUE,
     ];
     $form['submit'] = [
       '#type' => 'submit',
@@ -46,8 +49,10 @@ public function buildForm(array $form, FormStateInterface $form_state) {
   public function submitForm(array &$form, FormStateInterface $form_state) {
     batch_test_stack(NULL, TRUE);
 
-    $function = '_batch_test_' . $form_state->getValue('batch');
-    batch_set($function());
+    foreach ($form_state->getValue('batch') as $batch) {
+      $function = '_batch_test_' . $batch;
+      batch_set($function());
+    }
 
     $form_state->setRedirect('batch_test.redirect');
   }
diff --git a/core/modules/system/tests/src/Functional/Batch/ProcessingTest.php b/core/modules/system/tests/src/Functional/Batch/ProcessingTest.php
index 2d7c7ae01c..83f6b3cab3 100644
--- a/core/modules/system/tests/src/Functional/Batch/ProcessingTest.php
+++ b/core/modules/system/tests/src/Functional/Batch/ProcessingTest.php
@@ -84,6 +84,31 @@ public function testBatchForm() {
     $this->assertBatchMessages($this->_resultMessages('batch_4'), 'Nested batch performed successfully.');
     $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_4'), 'Execution order was correct.');
     $this->assertText('Redirection successful.', 'Redirection after batch execution is correct.');
+
+    // Batches 4 and 7 with nested batch 2 from within batch 4 and nested
+    // batches 5 and 6 from within batch 7.
+    $edit = ['batch' => ['batch_4', 'batch_7']];
+    $this->drupalPostForm('batch-test', $edit, 'Submit');
+    $this->assertSession()->assertNoEscaped('<');
+    $this->assertSession()->responseContains('Redirection successful.');
+    $this->assertBatchMessages($this->_resultMessages('batch_4'), 'Nested batch performed successfully.');
+    $this->assertBatchMessages($this->_resultMessages('batch_7'), 'Nested batch performed successfully.');
+    $expected_stack = array_merge($this->_resultStack('batch_4'), $this->_resultStack('batch_7'));
+    $this->assertEquals($expected_stack, batch_test_stack(), 'Execution order was correct.');
+    $batch = \Drupal::state()->get('batch_test_nested_order_multiple_batches');
+    $this->assertEquals(5, count($batch['sets']));
+    // Ensure correct queue mapping.
+    foreach ($batch['sets'] as $index => $batch_set) {
+      $this->assertEquals('drupal_batch:' . $batch['id'] . ':' . $index, $batch_set['queue']['name']);
+    }
+    // Ensure correct order of the nested batches. We reset the indexes in
+    // order to directly access the batches by their order.
+    $batch_sets = array_values($batch['sets']);
+    $this->assertEquals('batch_4', $batch_sets[0]['batch_test_id']);
+    $this->assertEquals('batch_2', $batch_sets[1]['batch_test_id']);
+    $this->assertEquals('batch_7', $batch_sets[2]['batch_test_id']);
+    $this->assertEquals('batch_5', $batch_sets[3]['batch_test_id']);
+    $this->assertEquals('batch_6', $batch_sets[4]['batch_test_id']);
   }
 
   /**
@@ -242,6 +267,25 @@ public function _resultStack($id, $value = 0) {
         }
         break;
 
+      case 'batch_6':
+        for ($i = 1; $i <= 10; $i++) {
+          $stack[] = "op 6 id $i";
+        }
+        break;
+
+      case 'batch_7':
+        for ($i = 1; $i <= 5; $i++) {
+          $stack[] = "op 7 id $i";
+        }
+        $stack[] = 'setting up batch 5';
+        $stack[] = 'setting up batch 6';
+        for ($i = 6; $i <= 10; $i++) {
+          $stack[] = "op 7 id $i";
+        }
+        $stack = array_merge($stack, $this->_resultStack('batch_5'));
+        $stack = array_merge($stack, $this->_resultStack('batch_6'));
+        break;
+
       case 'chained':
         $stack[] = 'submit handler 1';
         $stack[] = 'value = ' . $value;
@@ -291,6 +335,16 @@ public function _resultMessages($id) {
         $messages[] = 'results for batch 5<div class="item-list"><ul><li>op 5: processed 10 elements</li></ul></div>';
         break;
 
+      case 'batch_6':
+        $messages[] = 'results for batch 6<div class="item-list"><ul><li>op 6: processed 10 elements</li></ul></div>';
+        break;
+
+      case 'batch_7':
+        $messages[] = 'results for batch 7<div class="item-list"><ul><li>op 7: processed 10 elements</li></ul></div>';
+        $messages = array_merge($messages, $this->_resultMessages('batch_5'));
+        $messages = array_merge($messages, $this->_resultMessages('batch_6'));
+        break;
+
       case 'chained':
         $messages = array_merge($messages, $this->_resultMessages('batch_1'));
         $messages = array_merge($messages, $this->_resultMessages('batch_2'));
