diff --git a/core/modules/forum/forum.install b/core/modules/forum/forum.install
index 4a16a8939a..c4bddebaf3 100644
--- a/core/modules/forum/forum.install
+++ b/core/modules/forum/forum.install
@@ -5,6 +5,7 @@
  * Install, update, and uninstall functions for the Forum module.
  */
 
+use Drupal\Core\Database\Database;
 use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\taxonomy\Entity\Term;
 
@@ -158,6 +159,7 @@ function forum_schema() {
       'forum_topics' => ['nid', 'tid', 'sticky', 'last_comment_timestamp'],
       'created' => ['created'],
       'last_comment_timestamp' => ['last_comment_timestamp'],
+      'tid' => ['tid'],
     ],
     'foreign keys' => [
       'tracked_node' => [
@@ -175,3 +177,82 @@ function forum_schema() {
 
   return $schema;
 }
+
+/**
+ * Add cid field and index on tid to forum_index table.
+ */
+function forum_update_8400() {
+  $schema = Database::getConnection()->schema();
+  $spec = [
+    'description' => 'Maintains denormalized information about node/term relationships.',
+    'fields' => [
+      'nid' => [
+        'description' => 'The {node}.nid this record tracks.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ],
+      'title' => [
+        'description' => 'The node title.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ],
+      'tid' => [
+         'description' => 'The term ID.',
+         'type' => 'int',
+         'unsigned' => TRUE,
+         'not null' => TRUE,
+         'default' => 0,
+      ],
+      'sticky' => [
+        'description' => 'Boolean indicating whether the node is sticky.',
+        'type' => 'int',
+        'not null' => FALSE,
+        'default' => 0,
+        'size' => 'tiny',
+      ],
+      'created' => [
+        'description' => 'The Unix timestamp when the node was created.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ],
+      'last_comment_timestamp' => [
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => 'The Unix timestamp of the last comment that was posted within this node, from {comment}.timestamp.',
+      ],
+      'comment_count' => [
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => 'The total number of comments on this node.',
+      ],
+    ],
+    'indexes' => [
+      'forum_topics' => ['nid', 'tid', 'sticky', 'last_comment_timestamp'],
+      'created' => ['created'],
+      'last_comment_timestamp' => ['last_comment_timestamp'],
+      'tid' => ['tid'],
+    ],
+    'foreign keys' => [
+      'tracked_node' => [
+        'table' => 'node',
+        'columns' => ['nid' => 'nid'],
+      ],
+      'term' => [
+        'table' => 'taxonomy_term_data',
+        'columns' => [
+          'tid' => 'tid',
+        ],
+      ],
+    ],
+  ];
+  $schema->addIndex('forum_index', 'tid', ['tid'], $spec);
+}
diff --git a/core/modules/forum/src/ForumIndexStorage.php b/core/modules/forum/src/ForumIndexStorage.php
index 79945746c1..d6c092e031 100644
--- a/core/modules/forum/src/ForumIndexStorage.php
+++ b/core/modules/forum/src/ForumIndexStorage.php
@@ -91,7 +91,7 @@ public function update(NodeInterface $node) {
    */
   public function updateIndex(NodeInterface $node) {
     $nid = $node->id();
-    $count = $this->database->query("SELECT COUNT(cid) FROM {comment_field_data} c INNER JOIN {forum_index} i ON c.entity_id = i.nid WHERE c.entity_id = :nid AND c.field_name = 'comment_forum' AND c.entity_type = 'node' AND c.status = :status AND c.default_langcode = 1", [
+    $count = $this->database->query("SELECT COUNT(c.cid) FROM {comment_field_data} c INNER JOIN {forum_index} i ON c.entity_id = i.nid WHERE c.entity_id = :nid AND c.field_name = 'comment_forum' AND c.entity_type = 'node' AND c.status = :status AND c.default_langcode = 1", [
       ':nid' => $nid,
       ':status' => CommentInterface::PUBLISHED,
     ])->fetchField();
diff --git a/core/modules/forum/src/ForumManager.php b/core/modules/forum/src/ForumManager.php
index 05cd860760..39713b796d 100644
--- a/core/modules/forum/src/ForumManager.php
+++ b/core/modules/forum/src/ForumManager.php
@@ -327,42 +327,78 @@ protected function lastVisit($nid, AccountInterface $account) {
    *
    * @return \stdClass
    *   The last post for the given forum.
+   *
+   * @deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0.
+   *   Use getLastPostData() instead
    */
   protected function getLastPost($tid) {
     if (!empty($this->lastPostData[$tid])) {
       return $this->lastPostData[$tid];
     }
-    // Query "Last Post" information for this forum.
-    $query = $this->connection->select('node_field_data', 'n');
-    $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', [':tid' => $tid]);
-    $query->join('comment_entity_statistics', 'ces', "n.nid = ces.entity_id AND ces.field_name = 'comment_forum' AND ces.entity_type = 'node'");
-    $query->join('users_field_data', 'u', 'ces.last_comment_uid = u.uid AND u.default_langcode = 1');
-    $query->addExpression('CASE ces.last_comment_uid WHEN 0 THEN ces.last_comment_name ELSE u.name END', 'last_comment_name');
-
-    $topic = $query
-      ->fields('ces', ['last_comment_timestamp', 'last_comment_uid'])
-      ->condition('n.status', 1)
-      ->orderBy('last_comment_timestamp', 'DESC')
-      ->range(0, 1)
-      ->addTag('node_access')
-      ->execute()
-      ->fetchObject();
-
-    // Build the last post information.
-    $last_post = new \stdClass();
-    if (!empty($topic->last_comment_timestamp)) {
-      $last_post->created = $topic->last_comment_timestamp;
-      $last_post->name = $topic->last_comment_name;
-      $last_post->uid = $topic->last_comment_uid;
+    $data = $this->getLastPostData([$tid]);
+    return reset($data);
+  }
+
+  /**
+   * Provides the last post information for the given forum tids.
+   *
+   * @param int[] $tids
+   *   The forum tids.
+   *
+   * @return \stdClass[]
+   *   The last post information for the given forums.
+   */
+  protected function getLastPostData(array $tids) {
+    // Check if all tids already have post info. We assume no duplicate tids.
+    $tids_as_keys = array_flip($tids);
+    $known_data = array_intersect_key($this->lastPostData, $tids_as_keys);
+    if (count($known_data) < count($tids)) {
+      $unknown_tids = array_diff($tids, array_keys($this->lastPostData));
+
+      // Query "Last Post" information. Only add the node table to the query if
+      // this is necessary for filtering access-restricted records.
+      if ($this->currentUserCanViewAllNodes()) {
+        $query = $this->connection->select('forum_index', 'f');
+      }
+      else {
+        $query = $this->connection->select('node', 'n')
+          ->addTag('node_access');
+        $query->join('forum_index', 'f', 'n.nid = f.nid');
+      }
+      $query->join('comment_entity_statistics', 'ces', "f.nid = ces.entity_id AND ces.field_name = 'comment_forum' AND ces.entity_type = 'node'");
+      $query->join('users_field_data', 'u', 'ces.last_comment_uid = u.uid AND u.default_langcode = 1');
+      $query->addField('f', 'tid');
+      $query->addExpression('COALESCE(ces.last_comment_name, u.name)', 'last_comment_name');
+
+      $topics = $query
+        ->fields('ces', ['last_comment_timestamp', 'last_comment_uid'])
+        ->condition('f.tid', $unknown_tids, 'IN')
+        ->orderBy('f.last_comment_timestamp', 'DESC')
+        ->range(0, 1)
+        ->execute()
+        ->fetchAllAssoc('tid');
+
+      // Build the last post information.
+      foreach ($unknown_tids as $tid) {
+        $this->lastPostData[$tid] = new \stdClass();
+        if (!empty($topics[$tid]->last_comment_timestamp)) {
+          $this->lastPostData[$tid]->created = $topics[$tid]->last_comment_timestamp;
+          $this->lastPostData[$tid]->name = $topics[$tid]->last_comment_name;
+          $this->lastPostData[$tid]->uid = $topics[$tid]->last_comment_uid;
+        }
+      }
+
+      $known_data = array_intersect_key($this->lastPostData, $tids_as_keys);
     }
 
-    $this->lastPostData[$tid] = $last_post;
-    return $last_post;
+    return $known_data;
   }
 
   /**
    * Provides statistics for a forum.
    *
+   * This will prime statistics for all known forums, to minimize queries.
+   *
    * @param int $tid
    *   The forum tid.
    *
@@ -371,18 +407,23 @@ protected function getLastPost($tid) {
    */
   protected function getForumStatistics($tid) {
     if (empty($this->forumStatistics)) {
-      // Prime the statistics.
-      $query = $this->connection->select('node_field_data', 'n');
-      $query->join('comment_entity_statistics', 'ces', "n.nid = ces.entity_id AND ces.field_name = 'comment_forum' AND ces.entity_type = 'node'");
-      $query->join('forum', 'f', 'n.vid = f.vid');
-      $query->addExpression('COUNT(n.nid)', 'topic_count');
-      $query->addExpression('SUM(ces.comment_count)', 'comment_count');
+      // Prime the statistics. Only add the node table to the query if this is
+      // necessary for filtering access-restricted records.
+      if ($this->currentUserCanViewAllNodes()) {
+        $query = $this->connection->select('forum_index', 'f');
+      }
+      else {
+        $query = $this->connection->select('node', 'n')
+          ->addTag('node_access');
+        $query->join('forum_index', 'f', 'n.nid = f.nid');
+      }
+      $query->addExpression('COUNT(f.nid)', 'topic_count');
+      $query->addExpression('SUM(f.comment_count)', 'comment_count');
+
       $this->forumStatistics = $query
         ->fields('f', ['tid'])
-        ->condition('n.status', 1)
-        ->condition('n.default_langcode', 1)
         ->groupBy('tid')
-        ->addTag('node_access')
+        ->orderBy('NULL')
         ->execute()
         ->fetchAllAssoc('tid');
     }
@@ -393,6 +434,19 @@ protected function getForumStatistics($tid) {
   }
 
   /**
+   * Checks if the current user can view all nodes.
+   *
+   * This is a private method which will/may ONLY be used for modifying queries
+   * in a way that does not alter the returned results. (Under this condition it
+   * is not a huge problem that this method calls a global, non-injected
+   * function; there is no real 'injectable' alternative for it yet.)
+   */
+  function currentUserCanViewAllNodes() {
+    $account = \Drupal::currentUser();
+    return $account->hasPermission('bypass node access') || node_access_view_all_nodes($account);
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function getChildren($vid, $tid) {
@@ -401,6 +455,15 @@ public function getChildren($vid, $tid) {
     }
     $forums = [];
     $_forums = $this->entityManager->getStorage('taxonomy_term')->loadTree($vid, $tid, NULL, TRUE);
+
+    // Prime last post details for the forums. Unlike getForumStatistics() we
+    // only query data for the forums we actually need.
+    $tids = [];
+    foreach ($_forums as $forum) {
+      $tids[] = $forum->id();
+    }
+    $last_post_data = $this->getLastPostData($tids);
+
     foreach ($_forums as $forum) {
       // Merge in the topic and post counters.
       if (($count = $this->getForumStatistics($forum->id()))) {
@@ -413,7 +476,7 @@ public function getChildren($vid, $tid) {
       }
 
       // Merge in last post details.
-      $forum->last_post = $this->getLastPost($forum->id());
+      $forum->last_post = $last_post_data[$forum->id()];
       $forums[$forum->id()] = $forum;
     }
 
