Index: src/wp-admin/admin.php
===================================================================
--- src/wp-admin/admin.php	(revision 41895)
+++ src/wp-admin/admin.php	(working copy)
@@ -75,40 +75,41 @@ if ( get_option('db_upgraded') ) {
 			do_action( 'after_mu_upgrade', $response );
 			unset($response);
 		}
 		unset($c);
 	}
 }
 
 require_once(ABSPATH . 'wp-admin/includes/admin.php');
 
 auth_redirect();
 
 // Schedule trash collection
 if ( ! wp_next_scheduled( 'wp_scheduled_delete' ) && ! wp_installing() )
 	wp_schedule_event(time(), 'daily', 'wp_scheduled_delete');
 
+// Schedule Transient cleanup
+if ( ! wp_next_scheduled( 'delete_expired_transients' ) && ! wp_installing() )
+	wp_schedule_event( time(), 'daily', 'delete_expired_transients' );
+
 set_screen_options();
 
 $date_format = __( 'F j, Y' );
 $time_format = __( 'g:i a' );
 
 wp_enqueue_script( 'common' );
 
-
-
-
 /**
  * $pagenow is set in vars.php
  * $wp_importers is sometimes set in wp-admin/includes/import.php
  * The remaining variables are imported as globals elsewhere, declared as globals here
  *
  * @global string $pagenow
  * @global array  $wp_importers
  * @global string $hook_suffix
  * @global string $plugin_page
  * @global string $typenow
  * @global string $taxnow
  */
 global $pagenow, $wp_importers, $hook_suffix, $plugin_page, $typenow, $taxnow;
 
 $page_hook = null;
Index: src/wp-admin/includes/schema.php
===================================================================
--- src/wp-admin/includes/schema.php	(revision 41895)
+++ src/wp-admin/includes/schema.php	(working copy)
@@ -572,51 +572,32 @@ function populate_options() {
 		'links_recently_updated_time', 'links_recently_updated_prepend', 'links_recently_updated_append',
 		'weblogs_cacheminutes', 'comment_allowed_tags', 'search_engine_friendly_urls', 'default_geourl_lat',
 		'default_geourl_lon', 'use_default_geourl', 'weblogs_xml_url', 'new_users_can_blog', '_wpnonce',
 		'_wp_http_referer', 'Update', 'action', 'rich_editing', 'autosave_interval', 'deactivated_plugins',
 		'can_compress_scripts', 'page_uris', 'update_core', 'update_plugins', 'update_themes', 'doing_cron',
 		'random_seed', 'rss_excerpt_length', 'secret', 'use_linksupdate', 'default_comment_status_page',
 		'wporg_popular_tags', 'what_to_show', 'rss_language', 'language', 'enable_xmlrpc', 'enable_app',
 		'embed_autourls', 'default_post_edit_rows', 'gzipcompression', 'advanced_edit'
 	);
 	foreach ( $unusedoptions as $option )
 		delete_option($option);
 
 	// Delete obsolete magpie stuff.
 	$wpdb->query("DELETE FROM $wpdb->options WHERE option_name REGEXP '^rss_[0-9a-f]{32}(_ts)?$'");
 
-	/*
-	 * Deletes all expired transients. The multi-table delete syntax is used
-	 * to delete the transient record from table a, and the corresponding
-	 * transient_timeout record from table b.
-	 */
-	$time = time();
-	$sql = "DELETE a, b FROM $wpdb->options a, $wpdb->options b
-		WHERE a.option_name LIKE %s
-		AND a.option_name NOT LIKE %s
-		AND b.option_name = CONCAT( '_transient_timeout_', SUBSTRING( a.option_name, 12 ) )
-		AND b.option_value < %d";
-	$wpdb->query( $wpdb->prepare( $sql, $wpdb->esc_like( '_transient_' ) . '%', $wpdb->esc_like( '_transient_timeout_' ) . '%', $time ) );
-
-	if ( is_main_site() && is_main_network() ) {
-		$sql = "DELETE a, b FROM $wpdb->options a, $wpdb->options b
-			WHERE a.option_name LIKE %s
-			AND a.option_name NOT LIKE %s
-			AND b.option_name = CONCAT( '_site_transient_timeout_', SUBSTRING( a.option_name, 17 ) )
-			AND b.option_value < %d";
-		$wpdb->query( $wpdb->prepare( $sql, $wpdb->esc_like( '_site_transient_' ) . '%', $wpdb->esc_like( '_site_transient_timeout_' ) . '%', $time ) );
-	}
+	// Clear expired transients
+	delete_expired_transients( true );
 }
 
 /**
  * Execute WordPress role creation for the various WordPress versions.
  *
  * @since 2.0.0
  */
 function populate_roles() {
 	populate_roles_160();
 	populate_roles_210();
 	populate_roles_230();
 	populate_roles_250();
 	populate_roles_260();
 	populate_roles_270();
 	populate_roles_280();
Index: src/wp-admin/includes/upgrade.php
===================================================================
--- src/wp-admin/includes/upgrade.php	(revision 41895)
+++ src/wp-admin/includes/upgrade.php	(working copy)
@@ -1734,45 +1734,32 @@ function upgrade_460() {
 		}
 	}
 }
 
 /**
  * Executes network-level upgrade routines.
  *
  * @since 3.0.0
  *
  * @global int   $wp_current_db_version
  * @global wpdb  $wpdb
  */
 function upgrade_network() {
 	global $wp_current_db_version, $wpdb;
 
-	// Always.
-	if ( is_main_network() ) {
-		/*
-		 * Deletes all expired transients. The multi-table delete syntax is used
-		 * to delete the transient record from table a, and the corresponding
-		 * transient_timeout record from table b.
-		 */
-		$time = time();
-		$sql = "DELETE a, b FROM $wpdb->sitemeta a, $wpdb->sitemeta b
-			WHERE a.meta_key LIKE %s
-			AND a.meta_key NOT LIKE %s
-			AND b.meta_key = CONCAT( '_site_transient_timeout_', SUBSTRING( a.meta_key, 17 ) )
-			AND b.meta_value < %d";
-		$wpdb->query( $wpdb->prepare( $sql, $wpdb->esc_like( '_site_transient_' ) . '%', $wpdb->esc_like ( '_site_transient_timeout_' ) . '%', $time ) );
-	}
+	// Always clear expired transients
+	delete_expired_transients( true );
 
 	// 2.8.
 	if ( $wp_current_db_version < 11549 ) {
 		$wpmu_sitewide_plugins = get_site_option( 'wpmu_sitewide_plugins' );
 		$active_sitewide_plugins = get_site_option( 'active_sitewide_plugins' );
 		if ( $wpmu_sitewide_plugins ) {
 			if ( !$active_sitewide_plugins )
 				$sitewide_plugins = (array) $wpmu_sitewide_plugins;
 			else
 				$sitewide_plugins = array_merge( (array) $active_sitewide_plugins, (array) $wpmu_sitewide_plugins );
 
 			update_site_option( 'active_sitewide_plugins', $sitewide_plugins );
 		}
 		delete_site_option( 'wpmu_sitewide_plugins' );
 		delete_site_option( 'deactivated_sitewide_plugins' );
Index: src/wp-includes/default-filters.php
===================================================================
--- src/wp-includes/default-filters.php	(revision 41895)
+++ src/wp-includes/default-filters.php	(working copy)
@@ -305,38 +305,41 @@ add_action( 'sanitize_comment_cookies', 
 add_action( 'admin_print_scripts',        'print_emoji_detection_script'                   );
 add_action( 'admin_print_scripts',        'print_head_scripts',                      20    );
 add_action( 'admin_print_footer_scripts', '_wp_footer_scripts'                             );
 add_action( 'admin_print_styles',         'print_emoji_styles'                             );
 add_action( 'admin_print_styles',         'print_admin_styles',                      20    );
 add_action( 'init',                       'smilies_init',                             5    );
 add_action( 'plugins_loaded',             'wp_maybe_load_widgets',                    0    );
 add_action( 'plugins_loaded',             'wp_maybe_load_embeds',                     0    );
 add_action( 'shutdown',                   'wp_ob_end_flush_all',                      1    );
 // Create a revision whenever a post is updated.
 add_action( 'post_updated',               'wp_save_post_revision',                   10, 1 );
 add_action( 'publish_post',               '_publish_post_hook',                       5, 1 );
 add_action( 'transition_post_status',     '_transition_post_status',                  5, 3 );
 add_action( 'transition_post_status',     '_update_term_count_on_transition_post_status', 10, 3 );
 add_action( 'comment_form',               'wp_comment_form_unfiltered_html_nonce'          );
-add_action( 'wp_scheduled_delete',        'wp_scheduled_delete'                            );
-add_action( 'wp_scheduled_auto_draft_delete', 'wp_delete_auto_drafts'                      );
 add_action( 'admin_init',                 'send_frame_options_header',               10, 0 );
-add_action( 'importer_scheduled_cleanup', 'wp_delete_attachment'                           );
-add_action( 'upgrader_scheduled_cleanup', 'wp_delete_attachment'                           );
 add_action( 'try_gutenberg_panel',        'wp_try_gutenberg_panel'                         );
 add_action( 'welcome_panel',              'wp_welcome_panel'                               );
 
+// Cron tasks
+add_action( 'wp_scheduled_delete',            'wp_scheduled_delete'       );
+add_action( 'wp_scheduled_auto_draft_delete', 'wp_delete_auto_drafts'     );
+add_action( 'importer_scheduled_cleanup',     'wp_delete_attachment'      );
+add_action( 'upgrader_scheduled_cleanup',     'wp_delete_attachment'      );
+add_action( 'delete_expired_transients',      'delete_expired_transients' );
+
 // Navigation menu actions
 add_action( 'delete_post',                '_wp_delete_post_menu_item'         );
 add_action( 'delete_term',                '_wp_delete_tax_menu_item',   10, 3 );
 add_action( 'transition_post_status',     '_wp_auto_add_pages_to_menu', 10, 3 );
 add_action( 'delete_post',                '_wp_delete_customize_changeset_dependent_auto_drafts' );
 
 // Post Thumbnail CSS class filtering
 add_action( 'begin_fetch_post_thumbnail_html', '_wp_post_thumbnail_class_filter_add'    );
 add_action( 'end_fetch_post_thumbnail_html',   '_wp_post_thumbnail_class_filter_remove' );
 
 // Redirect Old Slugs
 add_action( 'template_redirect',  'wp_old_slug_redirect'              );
 add_action( 'post_updated',       'wp_check_for_changed_slugs', 12, 3 );
 add_action( 'attachment_updated', 'wp_check_for_changed_slugs', 12, 3 );
 
Index: src/wp-includes/option.php
===================================================================
--- src/wp-includes/option.php	(revision 41895)
+++ src/wp-includes/option.php	(working copy)
@@ -805,30 +805,85 @@ function set_transient( $transient, $val
 		 * Fires after the value for a transient has been set.
 		 *
 		 * @since 3.0.0
 		 * @since 3.6.0 The `$value` and `$expiration` parameters were added.
 		 *
 		 * @param string $transient  The name of the transient.
 		 * @param mixed  $value      Transient value.
 		 * @param int    $expiration Time until expiration in seconds.
 		 */
 		do_action( 'setted_transient', $transient, $value, $expiration );
 	}
 	return $result;
 }
 
 /**
+ * Deletes all expired transients.
+ *
+ * The multi-table delete syntax is used to delete the transient record
+ * from table a, and the corresponding transient_timeout record from table b.                           +
+ *
+ * @since 4.9.0
+ *
+ * @param bool $force_db Optional. Force cleanup to run against the database even when an external object cache is used.
+ */
+function delete_expired_transients( $force_db = false) {
+	global $wpdb;
+
+	if ( ! $force_db && wp_using_ext_object_cache() ) {
+		return;
+	}
+
+	$wpdb->query( $wpdb->prepare(
+		"DELETE a, b FROM {$wpdb->options} a, {$wpdb->options} b
+			WHERE a.option_name LIKE %s
+			AND a.option_name NOT LIKE %s
+			AND b.option_name = CONCAT( '_transient_timeout_', SUBSTRING( a.option_name, 12 ) )
+			AND b.option_value < %d",
+		$wpdb->esc_like( '_transient_' ) . '%',
+		$wpdb->esc_like( '_transient_timeout_' ) . '%',
+		time()
+	) );
+
+	if ( ! is_multisite() ) {
+		// non-Multisite stores site transients in the options table.
+		$wpdb->query( $wpdb->prepare(
+			"DELETE a, b FROM {$wpdb->options} a, {$wpdb->options} b
+				WHERE a.option_name LIKE %s
+				AND a.option_name NOT LIKE %s
+				AND b.option_name = CONCAT( '_site_transient_timeout_', SUBSTRING( a.option_name, 17 ) )
+				AND b.option_value < %d",
+			$wpdb->esc_like( '_site_transient_' ) . '%',
+			$wpdb->esc_like( '_site_transient_timeout_' ) . '%',
+			time()
+		) );
+	} elseif ( is_multisite() && is_main_site() && is_main_network() ) {
+		// Multisite stores site transients in the sitemeta table.
+		$wpdb->query( $wpdb->prepare(
+			"DELETE a, b FROM {$wpdb->sitemeta} a, {$wpdb->sitemeta} b
+				WHERE a.meta_key LIKE %s
+				AND a.meta_key NOT LIKE %s
+				AND b.meta_key = CONCAT( '_site_transient_timeout_', SUBSTRING( a.meta_key, 17 ) )
+				AND b.meta_value < %d",
+			$wpdb->esc_like( '_site_transient_' ) . '%',
+			$wpdb->esc_like( '_site_transient_timeout_' ) . '%',
+			time()
+		) );
+	}
+}
+
+/**
  * Saves and restores user interface settings stored in a cookie.
  *
  * Checks if the current user-settings cookie is updated and stores it. When no
  * cookie exists (different browser used), adds the last saved cookie restoring
  * the settings.
  *
  * @since 2.7.0
  */
 function wp_user_settings() {
 
 	if ( ! is_admin() || wp_doing_ajax() ) {
 		return;
 	}
 
 	if ( ! $user_id = get_current_user_id() ) {
