diff --git a/core/lib/Drupal/Core/Cache/MemoryBackend.php b/core/lib/Drupal/Core/Cache/MemoryBackend.php
index d402d70b543a9c7083c45233602059799dd46758..a0269f5301031ec4c120a0bb6b5e0e8817664a25 100644
--- a/core/lib/Drupal/Core/Cache/MemoryBackend.php
+++ b/core/lib/Drupal/Core/Cache/MemoryBackend.php
@@ -64,26 +64,21 @@ public function getMultiple(&$cids, $allow_invalid = FALSE) {
    * Checks that items are either permanent or did not expire, and returns data
    * as appropriate.
    *
-   * @param object $cache
+   * @param array $cache
    *   An item loaded from self::get() or self::getMultiple().
    * @param bool $allow_invalid
    *   (optional) If TRUE, cache items may be returned even if they have expired
    *   or been invalidated.
    *
-   * @return mixed
+   * @return object|false
    *   The item with data as appropriate or FALSE if there is no
    *   valid item to load.
    */
-  protected function prepareItem($cache, $allow_invalid) {
-    if (!isset($cache->data)) {
+  protected function prepareItem(array $cache, $allow_invalid) {
+    if (!isset($cache['data'])) {
       return FALSE;
     }
-    // The object passed into this function is the one stored in $this->cache.
-    // We must clone it as part of the preparation step so that the actual
-    // cache object is not affected by the unserialize() call or other
-    // manipulations of the returned object.
-
-    $prepared = clone $cache;
+    $prepared = (object) $cache;
     $prepared->data = unserialize($prepared->data);
 
     // Check expire time.
@@ -104,7 +99,10 @@ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = []) {
     $tags = array_unique($tags);
     // Sort the cache tags so that they are stored consistently in the database.
     sort($tags);
-    $this->cache[$cid] = (object) [
+
+    // Do not create an object at this point to minimize the number of objects
+    // garbage collection has to keep a track off.
+    $this->cache[$cid] = [
       'cid' => $cid,
       'data' => serialize($data),
       'created' => $this->getRequestTime(),
@@ -148,7 +146,7 @@ public function deleteAll() {
    */
   public function invalidate($cid) {
     if (isset($this->cache[$cid])) {
-      $this->cache[$cid]->expire = $this->getRequestTime() - 1;
+      $this->cache[$cid]['expire'] = $this->getRequestTime() - 1;
     }
   }
 
@@ -158,7 +156,7 @@ public function invalidate($cid) {
   public function invalidateMultiple(array $cids) {
     $items = array_intersect_key($this->cache, array_flip($cids));
     foreach ($items as $cid => $item) {
-      $this->cache[$cid]->expire = $this->getRequestTime() - 1;
+      $this->cache[$cid]['expire'] = $this->getRequestTime() - 1;
     }
   }
 
@@ -167,8 +165,8 @@ public function invalidateMultiple(array $cids) {
    */
   public function invalidateTags(array $tags) {
     foreach ($this->cache as $cid => $item) {
-      if (array_intersect($tags, $item->tags)) {
-        $this->cache[$cid]->expire = $this->getRequestTime() - 1;
+      if (array_intersect($tags, $item['tags'])) {
+        $this->cache[$cid]['expire'] = $this->getRequestTime() - 1;
       }
     }
   }
@@ -178,7 +176,7 @@ public function invalidateTags(array $tags) {
    */
   public function invalidateAll() {
     foreach ($this->cache as $cid => $item) {
-      $this->cache[$cid]->expire = $this->getRequestTime() - 1;
+      $this->cache[$cid]['expire'] = $this->getRequestTime() - 1;
     }
   }
 
diff --git a/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCache.php b/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCache.php
index 327110821159b6523c022dfe75505eebb7e96a49..841e4568a07270ecb284eb05087887e86bb4d822 100644
--- a/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCache.php
+++ b/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCache.php
@@ -8,40 +8,29 @@
 /**
  * Defines a memory cache implementation.
  *
- * Stores cache items in memory using a PHP array.
+ * Stores cache items in memory using a PHP array. Cache data is not serialized
+ * thereby returning the same object as was cached.
  *
  * @ingroup cache
  */
 class MemoryCache extends MemoryBackend implements MemoryCacheInterface {
 
   /**
-   * Prepares a cached item.
-   *
-   * Checks that items are either permanent or did not expire, and returns data
-   * as appropriate.
-   *
-   * @param object $cache
-   *   An item loaded from self::get() or self::getMultiple().
-   * @param bool $allow_invalid
-   *   (optional) If TRUE, cache items may be returned even if they have expired
-   *   or been invalidated. Defaults to FALSE.
-   *
-   * @return mixed
-   *   The item with data as appropriate or FALSE if there is no
-   *   valid item to load.
+   * {@inheritdoc}
    */
-  protected function prepareItem($cache, $allow_invalid = FALSE) {
-    if (!isset($cache->data)) {
+  protected function prepareItem(array $cache, $allow_invalid = FALSE) {
+    if (!isset($cache['data'])) {
       return FALSE;
     }
+    $prepared = (object) $cache;
     // Check expire time.
-    $cache->valid = $cache->expire == static::CACHE_PERMANENT || $cache->expire >= $this->getRequestTime();
+    $prepared->valid = $prepared->expire == static::CACHE_PERMANENT || $prepared->expire >= $this->getRequestTime();
 
-    if (!$allow_invalid && !$cache->valid) {
+    if (!$allow_invalid && !$prepared->valid) {
       return FALSE;
     }
 
-    return $cache;
+    return $prepared;
   }
 
   /**
@@ -51,8 +40,11 @@ public function set($cid, $data, $expire = MemoryCacheInterface::CACHE_PERMANENT
     assert(Inspector::assertAllStrings($tags), 'Cache tags must be strings.');
     $tags = array_unique($tags);
 
-    $this->cache[$cid] = (object) [
+    // Do not create an object at this point to minimize the number of objects
+    // garbage collection has to keep a track off.
+    $this->cache[$cid] = [
       'cid' => $cid,
+      // Note that $data is not serialized.
       'data' => $data,
       'created' => $this->getRequestTime(),
       'expire' => $expire,
diff --git a/core/lib/Drupal/Core/Update/UpdateBackend.php b/core/lib/Drupal/Core/Update/UpdateBackend.php
new file mode 100644
index 0000000000000000000000000000000000000000..08e4cfd1f6e7fd9e22ac73ed2c6c6cd822dd58a6
--- /dev/null
+++ b/core/lib/Drupal/Core/Update/UpdateBackend.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace Drupal\Core\Update;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Cache\MemoryBackend;
+
+/**
+ * Defines a cache backend for use during updating Drupal.
+ *
+ * Passes on deletes to another backend while using a memory backend to avoid
+ * using anything cached prior to running updates.
+ */
+class UpdateBackend extends MemoryBackend {
+
+  /**
+   * The regular runtime cache backend.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $backend;
+
+  /**
+   * UpdateBackend constructor.
+   *
+   * @param \Drupal\Core\Cache\CacheBackendInterface $backend
+   *   The regular runtime cache backend.
+   */
+  public function __construct(CacheBackendInterface $backend) {
+    $this->backend = $backend;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function delete($cid) {
+    parent::delete($cid);
+    $this->backend->delete($cid);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteMultiple(array $cids) {
+    parent::deleteMultiple($cids);
+    $this->backend->deleteMultiple($cids);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteAll() {
+    parent::deleteAll();
+    $this->backend->deleteAll();
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Update/UpdateCacheBackendFactory.php b/core/lib/Drupal/Core/Update/UpdateCacheBackendFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..e3f7dd12f16526c4191b886802e51c026f003ce2
--- /dev/null
+++ b/core/lib/Drupal/Core/Update/UpdateCacheBackendFactory.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Drupal\Core\Update;
+
+use Drupal\Core\Cache\CacheFactoryInterface;
+
+/**
+ * Cache factory implementation for updating Drupal.
+ *
+ * Decorates the regular runtime cache_factory service so that caches use
+ * \Drupal\Core\Update\UpdateBackend.
+ *
+ * @see \Drupal\Core\Update\UpdateServiceProvider::register()
+ */
+class UpdateCacheBackendFactory implements CacheFactoryInterface {
+
+  /**
+   * The regular runtime cache_factory service.
+   *
+   * @var \Drupal\Core\Cache\CacheFactoryInterface
+   */
+  protected $cacheFactory;
+
+  /**
+   * Instantiated update cache bins.
+   *
+   * @var \Drupal\Core\Update\UpdateBackend[]
+   */
+  protected $bins = [];
+
+  /**
+   * UpdateCacheBackendFactory constructor.
+   *
+   * @param \Drupal\Core\Cache\CacheFactoryInterface $cache_factory
+   *   The regular runtime cache_factory service.
+   */
+  public function __construct(CacheFactoryInterface $cache_factory) {
+    $this->cacheFactory = $cache_factory;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function get($bin) {
+    if (!isset($this->bins[$bin])) {
+      $this->bins[$bin] = new UpdateBackend($this->cacheFactory->get($bin));
+    }
+    return $this->bins[$bin];
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Update/UpdateServiceProvider.php b/core/lib/Drupal/Core/Update/UpdateServiceProvider.php
index 22c9131eb381ca935dd2fa33ffa809cffab36526..7ac68e6b18e4aab9d5f475af94002d49a04d0098 100644
--- a/core/lib/Drupal/Core/Update/UpdateServiceProvider.php
+++ b/core/lib/Drupal/Core/Update/UpdateServiceProvider.php
@@ -19,8 +19,16 @@ class UpdateServiceProvider implements ServiceProviderInterface, ServiceModifier
    */
   public function register(ContainerBuilder $container) {
     $definition = new Definition('Drupal\Core\Cache\NullBackend', ['null']);
+    $definition->setDeprecated(TRUE, 'The "%service_id%\" service is deprecated. While updating Drupal all caches use \Drupal\Core\Update\UpdateBackend. See https://www.drupal.org/node/3066407');
     $container->setDefinition('cache.null', $definition);
 
+    // Decorate the cache factory in order to use
+    // \Drupal\Core\Update\UpdateBackend while running updates.
+    $container
+      ->register('update.cache_factory', UpdateCacheBackendFactory::class)
+      ->setDecoratedService('cache_factory')
+      ->addArgument(new Reference('update.cache_factory.inner'));
+
     $container->addCompilerPass(new UpdateCompilerPass(), PassConfig::TYPE_REMOVE, 128);
   }
 
@@ -28,25 +36,6 @@ public function register(ContainerBuilder $container) {
    * {@inheritdoc}
    */
   public function alter(ContainerBuilder $container) {
-    // Ensures for some services that they don't cache.
-    $null_cache_service = new Reference('cache.null');
-
-    $definition = $container->getDefinition('asset.resolver');
-    $definition->replaceArgument(5, $null_cache_service);
-
-    $definition = $container->getDefinition('library.discovery.collector');
-    $definition->replaceArgument(0, $null_cache_service);
-
-    $definition = $container->getDefinition('theme.registry');
-    $definition->replaceArgument(1, $null_cache_service);
-    $definition->replaceArgument(7, $null_cache_service);
-
-    $definition = $container->getDefinition('theme.initialization');
-    $definition->replaceArgument(2, $null_cache_service);
-
-    $definition = $container->getDefinition('plugin.manager.element_info');
-    $definition->replaceArgument(1, $null_cache_service);
-
     // Prevent the alias-based path processor, which requires a path_alias db
     // table, from being registered to the path processor manager. We do this by
     // removing the tags that the compiler pass looks for. This means the url
diff --git a/core/modules/system/tests/src/Functional/Update/UpdateCacheTest.php b/core/modules/system/tests/src/Functional/Update/UpdateCacheTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1d9765da4ddf17dca44dab0e2be4e50bb7d66262
--- /dev/null
+++ b/core/modules/system/tests/src/Functional/Update/UpdateCacheTest.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Drupal\Tests\system\Functional\Update;
+
+use Drupal\Core\Url;
+use Drupal\Tests\BrowserTestBase;
+use Drupal\Tests\RequirementsPageTrait;
+
+/**
+ * Tests caches during updates.
+ *
+ * @group Update
+ */
+class UpdateCacheTest extends BrowserTestBase {
+  use RequirementsPageTrait;
+
+  /**
+   * Tests that caches are cleared during updates.
+   *
+   * @see \Drupal\Core\Update\UpdateServiceProvider
+   * @see \Drupal\Core\Update\UpdateBackend
+   */
+  public function testCaches() {
+    \Drupal::cache()->set('will_not_exist_after_update', TRUE);
+    // The site might be broken at the time so logging in using the UI might
+    // not work, so we use the API itself.
+    $this->writeSettings([
+      'settings' => [
+        'update_free_access' => (object) [
+          'value' => TRUE,
+          'required' => TRUE,
+        ],
+      ],
+    ]);
+
+    // Clicking continue should clear the caches.
+    $this->drupalGet(Url::fromRoute('system.db_update', [], ['path_processing' => FALSE]));
+    $this->updateRequirementsProblem();
+    $this->clickLink(t('Continue'));
+
+    $this->assertFalse(\Drupal::cache()->get('will_not_exist_after_update', FALSE));
+  }
+
+}