diff --git src/wp-includes/class-wp-metadata-lazyloader.php src/wp-includes/class-wp-metadata-lazyloader.php
new file mode 100644
index 0000000..653a2cb
--- /dev/null
+++ src/wp-includes/class-wp-metadata-lazyloader.php
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * Lazyloader for object metadata.
+ *
+ * @since 4.5.0
+ */
+class WP_Metadata_Lazyloader {
+	protected $pending_posts;
+
+	/**
+	 * Add posts to the metadata lazyload queue.
+	 *
+	 * @since 4.5.0
+	 *
+	 * @param array  $posts     Array of WP_Post objects.
+	 * @param string $meta_type Type of meta to be lazyloaded. Accepts 'term' or 'comment'.
+	 */
+	public function add_posts( $posts, $meta_type ) {
+		if ( empty( $posts ) ) {
+			return;
+		}
+
+		foreach ( $posts as $post ) {
+			if ( ! ( $post instanceof WP_Post ) || isset( $this->pending_posts[ $meta_type ][ $post->ID ] ) ) {
+				continue;
+			}
+
+			$this->pending_posts[ $meta_type ][ $post->ID ] = $post->post_type;
+		}
+
+		// @todo Comment meta support.
+		add_action( 'get_term_metadata', array( $this, 'lazyload_term_meta' ), 10, 2 );
+	}
+
+	/**
+	 * Lazy-loads termmeta for located posts.
+	 *
+	 * As a rule, term queries (`get_terms()` and `wp_get_object_terms()`) prime the metadata cache for matched
+	 * terms by default. However, this can cause a slight performance penalty, especially when that metadata is
+	 * not actually used. In the context of a `WP_Query` instance, we're able to avoid this potential penalty.
+	 * `update_object_term_cache()`, called from `update_post_caches()`, does not 'update_term_meta_cache'.
+	 * Instead, the first time `get_term_meta()` is called from within a `WP_Query` loop, the current method
+	 * detects the fact, and then primes the metadata cache for all terms attached to all posts in the loop,
+	 * with a single database query.
+	 *
+	 * This method is public so that it can be used as a filter callback. As a rule, there is no need to invoke it
+	 * directly, from either inside or outside the `WP_Query` object.
+	 *
+	 * @since 4.5.0
+	 * @access public
+	 *
+	 * @param mixed $check  The `$check` param passed from the 'get_term_metadata' hook.
+	 * @param int  $term_id ID of the term whose metadata is being cached.
+	 * @return mixed In order not to short-circuit `get_metadata()`. Generally, this is `null`, but it could be
+	 *               another value if filtered by a plugin.
+	 */
+	public function lazyload_term_meta( $check, $term_id ) {
+		$post_type_taxonomies = $term_ids = array();
+		foreach ( $this->pending_posts['term'] as $post_id => $post_type ) {
+			if ( ! isset( $post_type_taxonomies[ $post_type ] ) ) {
+				$post_type_taxonomies[ $post_type ] = get_object_taxonomies( $post_type );
+
+			}
+
+			foreach ( $post_type_taxonomies[ $post_type ] as $taxonomy ) {
+				// Term cache should already be primed by 'update_post_term_cache'.
+				$terms = get_object_term_cache( $post_id, $taxonomy );
+				if ( false !== $terms ) {
+					foreach ( $terms as $term ) {
+						if ( ! isset( $term_ids[ $term->term_id ] ) ) {
+							$term_ids[ $term->term_id ] = 1;
+						}
+					}
+				}
+			}
+		}
+
+		if ( $term_ids ) {
+			update_termmeta_cache( array_keys( $term_ids ) );
+		}
+
+		// No need to run again for this set of posts.
+		$this->pending_posts = array();
+		remove_filter( 'get_term_metadata', array( $this, 'lazyload_term_meta' ), 10, 2 );
+
+		return $check;
+	}
+}
diff --git src/wp-includes/post.php src/wp-includes/post.php
index 43361d4..7035ead 100644
--- src/wp-includes/post.php
+++ src/wp-includes/post.php
@@ -5948,6 +5948,24 @@ function wp_delete_auto_drafts() {
 }
 
 /**
+ * Queue posts for metadata lazyloading.
+ *
+ * @since 4.5.0
+ *
+ * @param array  $posts     Array of WP_Post objects.
+ * @param string $meta_type Type of meta to be lazyloaded. Accepts 'term' or 'comment'.
+ */
+function wp_queue_posts_for_metadata_lazyloading( $posts, $meta_type ) {
+	static $wp_metadata_lazyloader;
+
+	if ( null === $wp_metadata_lazyloader ) {
+		$wp_metadata_lazyloader = new WP_Metadata_Lazyloader();
+	}
+
+	$wp_metadata_lazyloader->add_posts( $posts, $meta_type );
+}
+
+/**
  * Update the custom taxonomies' term counts when a post's status is changed.
  *
  * For example, default posts term counts (for custom taxonomies) don't include
diff --git src/wp-includes/query.php src/wp-includes/query.php
index a8fbbc5..e6d7aec 100644
--- src/wp-includes/query.php
+++ src/wp-includes/query.php
@@ -3607,7 +3607,7 @@ class WP_Query {
 
 
 		if ( $q['update_post_term_cache'] ) {
-			add_filter( 'get_term_metadata', array( $this, 'lazyload_term_meta' ), 10, 2 );
+			wp_queue_posts_for_metadata_lazyloading( $this->posts, 'term' );
 		}
 
 		if ( ! $q['suppress_filters'] ) {
@@ -4834,74 +4834,6 @@ class WP_Query {
 	}
 
 	/**
-	 * Lazy-loads termmeta for located posts.
-	 *
-	 * As a rule, term queries (`get_terms()` and `wp_get_object_terms()`) prime the metadata cache for matched
-	 * terms by default. However, this can cause a slight performance penalty, especially when that metadata is
-	 * not actually used. In the context of a `WP_Query` instance, we're able to avoid this potential penalty.
-	 * `update_object_term_cache()`, called from `update_post_caches()`, does not 'update_term_meta_cache'.
-	 * Instead, the first time `get_term_meta()` is called from within a `WP_Query` loop, the current method
-	 * detects the fact, and then primes the metadata cache for all terms attached to all posts in the loop,
-	 * with a single database query.
-	 *
-	 * This method is public so that it can be used as a filter callback. As a rule, there is no need to invoke it
-	 * directly, from either inside or outside the `WP_Query` object.
-	 *
-	 * @since 4.4.0
-	 * @access public
-	 *
-	 * @param mixed $check  The `$check` param passed from the 'get_term_metadata' hook.
-	 * @param int  $term_id ID of the term whose metadata is being cached.
-	 * @return mixed In order not to short-circuit `get_metadata()`. Generally, this is `null`, but it could be
-	 *               another value if filtered by a plugin.
-	 */
-	public function lazyload_term_meta( $check, $term_id ) {
-		// We can only lazyload if the entire post object is present.
-		$posts = array();
-		foreach ( $this->posts as $post ) {
-			if ( $post instanceof WP_Post ) {
-				$posts[] = $post;
-			}
-		}
-
-		if ( ! empty( $posts ) ) {
-			// Fetch cached term_ids for each post. Keyed by term_id for faster lookup.
-			$term_ids = array();
-			foreach ( $posts as $post ) {
-				$taxonomies = get_object_taxonomies( $post->post_type );
-				foreach ( $taxonomies as $taxonomy ) {
-					// Term cache should already be primed by 'update_post_term_cache'.
-					$terms = get_object_term_cache( $post->ID, $taxonomy );
-					if ( false !== $terms ) {
-						foreach ( $terms as $term ) {
-							if ( ! isset( $term_ids[ $term->term_id ] ) ) {
-								$term_ids[ $term->term_id ] = 1;
-							}
-						}
-					}
-				}
-			}
-
-			/*
-			 * Only update the metadata cache for terms belonging to these posts if the term_id passed
-			 * to `get_term_meta()` matches one of those terms. This prevents a single call to
-			 * `get_term_meta()` from priming metadata for all `WP_Query` objects.
-			 */
-			if ( isset( $term_ids[ $term_id ] ) ) {
-				update_termmeta_cache( array_keys( $term_ids ) );
-				remove_filter( 'get_term_metadata', array( $this, 'lazyload_term_meta' ), 10, 2 );
-			}
-		}
-
-		// If no terms were found, there's no need to run this again.
-		if ( empty( $term_ids ) ) {
-			remove_filter( 'get_term_metadata', array( $this, 'lazyload_term_meta' ), 10, 2 );
-		}
-
-		return $check;
-	}
-
-	/**
 	 * Lazy-load comment meta when inside of a `WP_Query` loop.
 	 *
 	 * This method is public so that it can be used as a filter callback. As a rule, there is no need to invoke it
diff --git src/wp-settings.php src/wp-settings.php
index ef1d2cd..3c21597 100644
--- src/wp-settings.php
+++ src/wp-settings.php
@@ -126,6 +126,7 @@ require( ABSPATH . WPINC . '/class-wp-roles.php' );
 require( ABSPATH . WPINC . '/class-wp-role.php' );
 require( ABSPATH . WPINC . '/class-wp-user.php' );
 require( ABSPATH . WPINC . '/query.php' );
+require( ABSPATH . WPINC . '/class-wp-metadata-lazyloader.php' );
 require( ABSPATH . WPINC . '/date.php' );
 require( ABSPATH . WPINC . '/theme.php' );
 require( ABSPATH . WPINC . '/class-wp-theme.php' );
diff --git tests/phpunit/tests/term/meta.php tests/phpunit/tests/term/meta.php
index f6c7b90..da387e4 100644
--- tests/phpunit/tests/term/meta.php
+++ tests/phpunit/tests/term/meta.php
@@ -148,93 +148,6 @@ class Tests_Term_Meta extends WP_UnitTestCase {
 		}
 	}
 
-	/**
-	 * @ticket 34073
-	 */
-	public function test_term_meta_should_be_lazy_loaded_only_for_the_queries_in_which_the_term_has_posts() {
-		global $wpdb;
-
-		$posts = self::factory()->post->create_many( 3, array( 'post_status' => 'publish' ) );
-		register_taxonomy( 'wptests_tax', 'post' );
-		$terms = self::factory()->term->create_many( 6, array( 'taxonomy' => 'wptests_tax' ) );
-
-		wp_set_object_terms( $posts[0], array( $terms[0], $terms[1] ), 'wptests_tax' );
-		wp_set_object_terms( $posts[1], array( $terms[2], $terms[3] ), 'wptests_tax' );
-		wp_set_object_terms( $posts[2], array( $terms[0], $terms[4], $terms[5] ), 'wptests_tax' );
-
-		foreach ( $terms as $t ) {
-			add_term_meta( $t, 'foo', 'bar' );
-		}
-
-		$q0 = new WP_Query( array( 'p' => $posts[0], 'cache_results' => true ) );
-		$q1 = new WP_Query( array( 'p' => $posts[1], 'cache_results' => true ) );
-		$q2 = new WP_Query( array( 'p' => $posts[2], 'cache_results' => true ) );
-
-		/*
-		 * $terms[0] belongs to both $posts[0] and $posts[2], so `get_term_meta( $terms[0] )` should prime
-		 * the cache for term matched by $q0 and $q2.
-		 */
-
-		// First request will hit the database.
-		$num_queries = $wpdb->num_queries;
-
-		// Prime caches.
-		$this->assertSame( 'bar', get_term_meta( $terms[0], 'foo', true ) );
-
-		// Two queries: one for $q0 and one for $q2.
-		$num_queries += 2;
-		$this->assertSame( $num_queries, $wpdb->num_queries );
-
-		// Next requests should be in cache.
-		$this->assertSame( 'bar', get_term_meta( $terms[1], 'foo', true ) );
-		$this->assertSame( 'bar', get_term_meta( $terms[4], 'foo', true ) );
-		$this->assertSame( 'bar', get_term_meta( $terms[5], 'foo', true ) );
-		$this->assertSame( $num_queries, $wpdb->num_queries );
-
-		// Querying for $terms[2] will prime $terms[3] as well.
-		$this->assertSame( 'bar', get_term_meta( $terms[2], 'foo', true ) );
-		$num_queries++;
-		$this->assertSame( $num_queries, $wpdb->num_queries );
-
-		$this->assertSame( 'bar', get_term_meta( $terms[3], 'foo', true ) );
-		$this->assertSame( $num_queries, $wpdb->num_queries );
-	}
-
-	public function test_adding_term_meta_should_bust_get_terms_cache() {
-		$terms = self::factory()->term->create_many( 2, array( 'taxonomy' => 'wptests_tax' ) );
-
-		add_term_meta( $terms[0], 'foo', 'bar' );
-
-		// Prime cache.
-		$found = get_terms( 'wptests_tax', array(
-			'hide_empty' => false,
-			'fields' => 'ids',
-			'meta_query' => array(
-				array(
-					'key' => 'foo',
-					'value' => 'bar',
-				),
-			),
-		) );
-
-		$this->assertEqualSets( array( $terms[0] ), $found );
-
-		add_term_meta( $terms[1], 'foo', 'bar' );
-
-		$found = get_terms( 'wptests_tax', array(
-			'hide_empty' => false,
-			'fields' => 'ids',
-			'meta_query' => array(
-				array(
-					'key' => 'foo',
-					'value' => 'bar',
-				),
-			),
-		) );
-
-		$this->assertEqualSets( array( $terms[0], $terms[1] ), $found );
-	}
-
 	public function test_updating_term_meta_should_bust_get_terms_cache() {
 		$terms = self::factory()->term->create_many( 2, array( 'taxonomy' => 'wptests_tax' ) );
 
