diff --git a/core/core.services.yml b/core/core.services.yml
index c42ca2dd21..ca1bf16d72 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -522,7 +522,7 @@ services:
     class: Drupal\Core\Extension\ModuleInstaller
     tags:
       - { name: service_collector, tag: 'module_install.uninstall_validator', call: addUninstallValidator }
-    arguments: ['%app.root%', '@module_handler', '@kernel']
+    arguments: ['%app.root%', '@module_handler', '@kernel', '@schema_installer']
     lazy: true
   extension.list.module:
     class: Drupal\Core\Extension\ModuleExtensionList
@@ -560,6 +560,10 @@ services:
   theme_installer:
     class: Drupal\Core\Extension\ThemeInstaller
     arguments: ['@theme_handler', '@config.factory', '@config.installer', '@module_handler', '@config.manager', '@asset.css.collection_optimizer', '@router.builder', '@logger.channel.default', '@state', '@extension.list.module']
+  schema_installer:
+    class: Drupal\Core\Extension\SchemaInstaller
+    arguments: ['@module_handler', '@cache.default', '@keyvalue', '@database', '@cache_tags.invalidator']
+    lazy: true
   entity.memory_cache:
     class: Drupal\Core\Cache\MemoryCache\MemoryCache
   entity_type.manager:
diff --git a/core/includes/install.inc b/core/includes/install.inc
index 211eadb3d0..566be55cff 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -80,7 +80,9 @@
 function drupal_load_updates() {
   /** @var \Drupal\Core\Extension\ModuleExtensionList $extension_list_module */
   $extension_list_module = \Drupal::service('extension.list.module');
-  foreach (drupal_get_installed_schema_version(NULL, FALSE, TRUE) as $module => $schema_version) {
+  /** @var \Drupal\Core\Extension\SchemaInstallerInterface $schema_installer */
+  $schema_installer = \Drupal::service('schema_installer');
+  foreach ($schema_installer->getInstalledVersions() as $module => $schema_version) {
     if ($extension_list_module->exists($module) && !$extension_list_module->checkIncompatibility($module)) {
       if ($schema_version > -1) {
         module_load_install($module);
diff --git a/core/includes/schema.inc b/core/includes/schema.inc
index e168e89556..194ff93666 100644
--- a/core/includes/schema.inc
+++ b/core/includes/schema.inc
@@ -5,6 +5,8 @@
  * Schema API handling functions.
  */
 
+use Drupal\Core\Extension\SchemaInstallerInterface;
+
 /**
  * @addtogroup schemaapi
  * @{
@@ -12,8 +14,13 @@
 
 /**
  * Indicates that a module has not been installed yet.
+ *
+ * @deprecated in drupal:9.1.0 and is removed from drupal:10.0.0.
+ *   Use \Drupal\Core\Extension\SchemaInstallerInterface::UNINSTALLED.
+ *
+ * @see https://www.drupal.org/node/2970993
  */
-const SCHEMA_UNINSTALLED = -1;
+const SCHEMA_UNINSTALLED = SchemaInstallerInterface::UNINSTALLED;
 
 /**
  * Returns an array of available schema versions for a module.
@@ -70,28 +77,26 @@ function drupal_get_schema_versions($module) {
  *   system.
  *
  * @return string|int
- *   The currently installed schema version, or SCHEMA_UNINSTALLED if the
- *   module is not installed.
+ *   The currently installed schema version, or
+ *   SchemaInstallerInterface::UNINSTALLED if the module is not installed.
+ *
+ * @deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Use
+ *   SchemaInstallerInterface::getInstalledVersion() instead.
+ *
+ * @see https://www.drupal.org/node/2970993
+ * @see Drupal\Core\Extension\SchemaInstallerInterface::getInstalledVersion()
  */
 function drupal_get_installed_schema_version($module, $reset = FALSE, $array = FALSE) {
-  $versions = &drupal_static(__FUNCTION__, []);
-
+  @trigger_error('drupal_get_installed_schema_version() is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Use \Drupal\Core\Extension\SchemaInstallerInterface::getInstalledVersion() instead. See https://www.drupal.org/node/2970993', E_USER_DEPRECATED);
+  /** @var \Drupal\Core\Extension\SchemaInstallerInterface $schema_installer */
+  $schema_installer = \Drupal::service('schema_installer');
   if ($reset) {
-    $versions = [];
+    $schema_installer->resetCache();
   }
-
-  if (!$versions) {
-    if (!$versions = \Drupal::keyValue('system.schema')->getAll()) {
-      $versions = [];
-    }
-  }
-
   if ($array) {
-    return $versions;
-  }
-  else {
-    return isset($versions[$module]) ? $versions[$module] : SCHEMA_UNINSTALLED;
+    return $schema_installer->getInstalledVersions();
   }
+  return $schema_installer->getInstalledVersion($module);
 }
 
 /**
@@ -101,11 +106,16 @@ function drupal_get_installed_schema_version($module, $reset = FALSE, $array = F
  *   A module name.
  * @param string $version
  *   The new schema version.
+ *
+ * @deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Use
+ *   SchemaInstallerInterface::setInstalledVersion() instead.
+ *
+ * @see https://www.drupal.org/node/2970993
+ * @see Drupal\Core\Extension\SchemaInstallerInterface::setInstalledVersion()
  */
 function drupal_set_installed_schema_version($module, $version) {
-  \Drupal::keyValue('system.schema')->set($module, $version);
-  // Reset the static cache of module schema versions.
-  drupal_get_installed_schema_version(NULL, TRUE);
+  @trigger_error('drupal_set_installed_schema_version() is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Use \Drupal\Core\Extension\SchemaInstallerInterface::setInstalledVersion() instead. See https://www.drupal.org/node/2970993', E_USER_DEPRECATED);
+  \Drupal::service('schema_installer')->setInstalledVersion($module, $version);
 }
 
 /**
@@ -113,14 +123,16 @@ function drupal_set_installed_schema_version($module, $version) {
  *
  * @param string $module
  *   The module for which the tables will be created.
+ *
+ * @deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Use
+ *   SchemaInstallerInterface::install() instead.
+ *
+ * @see https://www.drupal.org/node/2970993
+ * @see \Drupal\Core\Extension\SchemaInstallerInterface::install()
  */
 function drupal_install_schema($module) {
-  $schema = drupal_get_module_schema($module);
-  _drupal_schema_initialize($schema, $module, FALSE);
-
-  foreach ($schema as $name => $table) {
-    \Drupal::database()->schema()->createTable($name, $table);
-  }
+  @trigger_error('drupal_install_schema() is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Use \Drupal\Core\Extension\SchemaInstallerInterface::install() instead. See https://www.drupal.org/node/2970993', E_USER_DEPRECATED);
+  \Drupal::service('schema_installer')->install($module);
 }
 
 /**
@@ -128,16 +140,16 @@ function drupal_install_schema($module) {
  *
  * @param string $module
  *   The module for which the tables will be removed.
+ *
+ * @deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Use
+ *   SchemaInstallerInterface::uninstall() instead.
+ *
+ * @see https://www.drupal.org/node/2970993
+ * @see \Drupal\Core\Extension\SchemaInstallerInterface::uninstall()
  */
 function drupal_uninstall_schema($module) {
-  $tables = drupal_get_module_schema($module);
-  _drupal_schema_initialize($tables, $module, FALSE);
-  $schema = \Drupal::database()->schema();
-  foreach ($tables as $table) {
-    if ($schema->tableExists($table['name'])) {
-      $schema->dropTable($table['name']);
-    }
-  }
+  @trigger_error('drupal_uninstall_schema() is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Use \Drupal\Core\Extension\SchemaInstallerInterface::uninstall() instead. See https://www.drupal.org/node/2970993', E_USER_DEPRECATED);
+  \Drupal::service('schema_installer')->uninstall($module);
 }
 
 /**
@@ -152,22 +164,16 @@ function drupal_uninstall_schema($module) {
  * @param string $table
  *   The name of the table. If not given, the module's complete schema
  *   is returned.
+ *
+ * @deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Use
+ *   SchemaInstallerInterface::getSchema() instead.
+ *
+ * @see https://www.drupal.org/node/2970993
+ * @see \Drupal\Core\Extension\SchemaInstallerInterface::getSchema()
  */
 function drupal_get_module_schema($module, $table = NULL) {
-  // Load the .install file to get hook_schema.
-  module_load_install($module);
-  $schema = \Drupal::moduleHandler()->invoke($module, 'schema');
-
-  if (isset($table)) {
-    if (isset($schema[$table])) {
-      return $schema[$table];
-    }
-    return [];
-  }
-  elseif (!empty($schema)) {
-    return $schema;
-  }
-  return [];
+  @trigger_error('drupal_get_module_schema() is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Use \Drupal\Core\Extension\SchemaInstallerInterface::uninstall() instead. See https://www.drupal.org/node/2970993', E_USER_DEPRECATED);
+  return \Drupal::service('schema_installer')->getSchema($module, $table);
 }
 
 /**
@@ -182,8 +188,14 @@ function drupal_get_module_schema($module, $table = NULL) {
  *   (optional) Whether to additionally remove 'description' keys of all tables
  *   and fields to improve performance of serialize() and unserialize().
  *   Defaults to TRUE.
+ *
+ * @deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. No
+ *   direct replacement provided.
+ *
+ * @see https://www.drupal.org/node/2970993
  */
 function _drupal_schema_initialize(&$schema, $module, $remove_descriptions = TRUE) {
+  @trigger_error('_drupal_schema_initialize() is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. No direct replacement provided. See https://www.drupal.org/node/2970993', E_USER_DEPRECATED);
   // Set the name and module key for all tables.
   foreach ($schema as $name => &$table) {
     if (empty($table['module'])) {
diff --git a/core/includes/update.inc b/core/includes/update.inc
index 40e87edeb6..89e51393f3 100644
--- a/core/includes/update.inc
+++ b/core/includes/update.inc
@@ -10,6 +10,7 @@
 
 use Drupal\Component\Graph\Graph;
 use Drupal\Core\Extension\Exception\UnknownExtensionException;
+use Drupal\Core\Extension\SchemaInstallerInterface;
 use Drupal\Core\Utility\Error;
 
 /**
@@ -50,7 +51,7 @@ function update_check_incompatibility($name, $type = 'module') {
 function update_system_schema_requirements() {
   $requirements = [];
 
-  $system_schema = drupal_get_installed_schema_version('system');
+  $system_schema = \Drupal::service('schema_installer')->getInstalledVersion('system');
 
   $requirements['minimum schema']['title'] = 'Minimum schema version';
   if ($system_schema >= \Drupal::CORE_MINIMUM_SCHEMA_VERSION) {
@@ -129,7 +130,7 @@ function _update_fix_missing_schema() {
       if ($last_removed = $module_handler->invoke($module, 'update_last_removed')) {
         $last_update = max($last_update, $last_removed);
       }
-      drupal_set_installed_schema_version($module, $last_update);
+      \Drupal::service('schema_installer')->setInstalledVersion($module, $last_update);
       $args = ['%module' => $module, '%last_update_hook' => $module . '_update_' . $last_update . '()'];
       \Drupal::messenger()->addWarning(t('Schema information for module %module was missing from the database. You should manually review the module updates and your database to check if any updates have been skipped up to, and including, %last_update_hook.', $args));
       \Drupal::logger('update')->warning('Schema information for module %module was missing from the database. You should manually review the module updates and your database to check if any updates have been skipped up to, and including, %last_update_hook.', $args);
@@ -148,12 +149,11 @@ function _update_fix_missing_schema() {
  *   The schema version the module should be set to.
  */
 function update_set_schema($module, $schema_version) {
-  \Drupal::keyValue('system.schema')->set($module, $schema_version);
+  \Drupal::service('schema_installer')->setInstalledVersion($module, $schema_version);
   \Drupal::service('extension.list.profile')->reset();
   \Drupal::service('extension.list.module')->reset();
   \Drupal::service('extension.list.theme_engine')->reset();
   \Drupal::service('extension.list.theme')->reset();
-  drupal_static_reset('drupal_get_installed_schema_version');
 }
 
 /**
@@ -249,7 +249,7 @@ function update_do_one($module, $number, $dependency_map, &$context) {
 
   // Record the schema update if it was completed successfully.
   if ($context['finished'] == 1 && empty($ret['#abort'])) {
-    drupal_set_installed_schema_version($module, $number);
+    \Drupal::service('schema_installer')->setInstalledVersion($module, $number);
   }
 
   $context['message'] = t('Updating @module', ['@module' => $module]);
@@ -338,7 +338,7 @@ function update_get_update_list() {
   // Make sure that the system module is first in the list of updates.
   $ret = ['system' => []];
 
-  $modules = drupal_get_installed_schema_version(NULL, FALSE, TRUE);
+  $modules = \Drupal::service('schema_installer')->getInstalledVersions();
   /** @var \Drupal\Core\Extension\ExtensionList $extension_list */
   $extension_list = \Drupal::service('extension.list.module');
   /** @var array $installed_module_info */
@@ -346,7 +346,7 @@ function update_get_update_list() {
   foreach ($modules as $module => $schema_version) {
     // Skip uninstalled and incompatible modules.
     try {
-      if ($schema_version == SCHEMA_UNINSTALLED || $extension_list->checkIncompatibility($module)) {
+      if ($schema_version == SchemaInstallerInterface::UNINSTALLED || $extension_list->checkIncompatibility($module)) {
         continue;
       }
     }
@@ -639,7 +639,7 @@ function update_is_missing($module, $number, $update_functions) {
  *   performed; FALSE otherwise.
  */
 function update_already_performed($module, $number) {
-  return $number <= drupal_get_installed_schema_version($module);
+  return $number <= \Drupal::service('schema_installer')->getInstalledVersion($module);
 }
 
 /**
@@ -671,7 +671,7 @@ function update_retrieve_dependencies() {
     // store for modules that have been removed from a site without first being
     // cleanly uninstalled. We don't care here if the module has been installed
     // or not, since we'll filter those out in update_get_update_list().
-    if ($schema == SCHEMA_UNINSTALLED || !$extension_list->exists($module)) {
+    if ($schema == SchemaInstallerInterface::UNINSTALLED || !$extension_list->exists($module)) {
       // Nothing to upgrade.
       continue;
     }
diff --git a/core/lib/Drupal/Core/Database/database.api.php b/core/lib/Drupal/Core/Database/database.api.php
index a7ccdc32a0..44898b9fe9 100644
--- a/core/lib/Drupal/Core/Database/database.api.php
+++ b/core/lib/Drupal/Core/Database/database.api.php
@@ -390,7 +390,7 @@
  * ];
  * @endcode
  *
- * @see drupal_install_schema()
+ * @see \Drupal\Core\Extension\SchemaInstallerInterface::install()
  *
  * @}
  */
diff --git a/core/lib/Drupal/Core/Extension/ModuleExtensionList.php b/core/lib/Drupal/Core/Extension/ModuleExtensionList.php
index 60b08cc8b5..c7da050c7b 100644
--- a/core/lib/Drupal/Core/Extension/ModuleExtensionList.php
+++ b/core/lib/Drupal/Core/Extension/ModuleExtensionList.php
@@ -163,7 +163,7 @@ protected function doList() {
     foreach ($extensions as $name => $module) {
       $module->weight = isset($installed_modules[$name]) ? $installed_modules[$name] : 0;
       $module->status = (int) isset($installed_modules[$name]);
-      $module->schema_version = SCHEMA_UNINSTALLED;
+      $module->schema_version = SchemaInstallerInterface::UNINSTALLED;
     }
     $extensions = $this->moduleHandler->buildModuleDependencies($extensions);
 
diff --git a/core/lib/Drupal/Core/Extension/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
index 09f8434370..2f335ab5e9 100644
--- a/core/lib/Drupal/Core/Extension/ModuleInstaller.php
+++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
@@ -43,6 +43,13 @@ class ModuleInstaller implements ModuleInstallerInterface {
    */
   protected $root;
 
+  /**
+   * The schema service.
+   *
+   * @var \Drupal\Core\Extension\SchemaInstallerInterface
+   */
+  protected $schemaInstaller;
+
   /**
    * The uninstall validators.
    *
@@ -59,14 +66,21 @@ class ModuleInstaller implements ModuleInstallerInterface {
    *   The module handler.
    * @param \Drupal\Core\DrupalKernelInterface $kernel
    *   The drupal kernel.
+   * @param \Drupal\Core\Extension\SchemaInstallerInterface|null $schema_installer
+   *   The schema installer service.
    *
    * @see \Drupal\Core\DrupalKernel
    * @see \Drupal\Core\CoreServiceProvider
    */
-  public function __construct($root, ModuleHandlerInterface $module_handler, DrupalKernelInterface $kernel) {
+  public function __construct($root, ModuleHandlerInterface $module_handler, DrupalKernelInterface $kernel, SchemaInstallerInterface $schema_installer = NULL) {
     $this->root = $root;
     $this->moduleHandler = $module_handler;
     $this->kernel = $kernel;
+    if (!$schema_installer) {
+      @trigger_error('The schema installer service must be passed to ' . __METHOD__ . '. Creating ' . __CLASS__ . ' without it is deprecated in drupal:9.1.0 and will be required in drupal:10.0.0. See https://www.drupal.org/node/2970993', E_USER_DEPRECATED);
+      $schema_installer = \Drupal::service('schema_installer');
+    }
+    $this->schemaInstaller = $schema_installer;
   }
 
   /**
@@ -225,7 +239,7 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
         $this->moduleHandler->invokeAll('module_preinstall', [$module]);
 
         // Now install the module's schema if necessary.
-        drupal_install_schema($module);
+        $this->schemaInstaller->install($module);
 
         // Clear plugin manager caches.
         \Drupal::getContainer()->get('plugin.cache_clearer')->clearCachedDefinitions();
@@ -293,7 +307,7 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
         if ($last_removed = $this->moduleHandler->invoke($module, 'update_last_removed')) {
           $version = max($version, $last_removed);
         }
-        drupal_set_installed_schema_version($module, $version);
+        $this->schemaInstaller->setInstalledVersion($module, $version);
 
         // Ensure that all post_update functions are registered already. This
         // should include existing post-updates, as well as any specified as
@@ -464,7 +478,7 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
       }
 
       // Remove the schema.
-      drupal_uninstall_schema($module);
+      $this->schemaInstaller->uninstall($module);
 
       // Remove the module's entry from the config. Don't check schema when
       // uninstalling a module since we are only clearing a key.
@@ -514,7 +528,8 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
     // fastCGI which executes ::destruct() after the Module uninstallation page
     // was sent already.
     \Drupal::service('router.builder')->rebuild();
-    drupal_get_installed_schema_version(NULL, TRUE);
+    $this->schemaInstaller->resetCache();
+    $this->schemaInstaller->getInstalledVersions();
 
     // Let other modules react.
     $this->moduleHandler->invokeAll('modules_uninstalled', [$module_list, $sync_status]);
diff --git a/core/lib/Drupal/Core/Extension/SchemaInstaller.php b/core/lib/Drupal/Core/Extension/SchemaInstaller.php
new file mode 100644
index 0000000000..812a77e329
--- /dev/null
+++ b/core/lib/Drupal/Core/Extension/SchemaInstaller.php
@@ -0,0 +1,204 @@
+<?php
+
+namespace Drupal\Core\Extension;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
+use Drupal\Core\Database\Connection;
+use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
+
+/**
+ * Provides database schema handling.
+ */
+class SchemaInstaller implements SchemaInstallerInterface {
+
+  /**
+   * A static cache of current module schema currentVersions.
+   *
+   * @var array
+   */
+  protected $currentVersions;
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The cache backend.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $cacheBackend;
+
+  /**
+   * The key value factory.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueFactory
+   */
+  protected $keyValue;
+
+  /**
+   * The database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $connection;
+
+  /**
+   * The cache tags invalidator.
+   *
+   * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface
+   */
+  protected $cacheTagsInvalidator;
+
+  /**
+   * Constructs a Schema object.
+   *
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   The cache backend.
+   * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_factory
+   *   The key value factory.
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection.
+   * @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cache_tags_invalidator
+   *   The cache tags invalidator.
+   */
+  public function __construct(ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, KeyValueFactoryInterface $key_value_factory, Connection $connection, CacheTagsInvalidatorInterface $cache_tags_invalidator) {
+    $this->moduleHandler = $module_handler;
+    $this->cacheBackend = $cache_backend;
+    $this->keyValue = $key_value_factory;
+    $this->connection = $connection;
+    $this->cacheTagsInvalidator = $cache_tags_invalidator;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInstalledVersion($module) {
+    $this->ensureInitialized();
+    return $this->currentVersions[$module] ?? static::UNINSTALLED;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInstalledVersions() {
+    $this->ensureInitialized();
+    return $this->currentVersions;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function resetCache() {
+    // Setting to NULL to re-read data on first use in ensureInitialized().
+    $this->currentVersions = NULL;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setInstalledVersion($module, $version) {
+    $this->keyValue->get('system.schema')->set($module, $version);
+    $this->resetCache()->ensureInitialized();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function install($module) {
+    $schema = $this->getSchema($module);
+    $this->initialize($schema, $module, FALSE);
+
+    foreach ($schema as $name => $table) {
+      $this->connection->schema()->createTable($name, $table);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function uninstall($module) {
+    $schema = $this->getSchema($module);
+    $this->initialize($schema, $module, FALSE);
+    $connection_schema = $this->connection->schema();
+
+    foreach ($schema as $table) {
+      if ($connection_schema->tableExists($table['name'])) {
+        $connection_schema->dropTable($table['name']);
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSchema($module, $table = NULL) {
+    // Load the .install file to get hook_schema.
+    $this->moduleHandler->loadInclude($module, 'install');
+    $schema = $this->moduleHandler->invoke($module, 'schema');
+
+    if (isset($table)) {
+      if (isset($schema[$table])) {
+        return $schema[$table];
+      }
+      return [];
+    }
+    elseif (!empty($schema)) {
+      return $schema;
+    }
+
+    return [];
+  }
+
+  /**
+   * Fills in required default values for table definitions from hook_schema().
+   *
+   * @param array $schema
+   *   The schema definition array as it was returned by the module's
+   *   hook_schema().
+   * @param string $module
+   *   The module for which hook_schema() was invoked.
+   * @param bool $remove_descriptions
+   *   (optional) Whether to additionally remove 'description' keys of all
+   *   tables and fields to improve performance of serialize() and
+   *   unserialize(). Defaults to TRUE.
+   */
+  protected function initialize(array &$schema, $module, $remove_descriptions = TRUE) {
+    // Set the name and module key for all tables.
+    foreach ($schema as $name => &$table) {
+      if (empty($table['module'])) {
+        $table['module'] = $module;
+      }
+      if (!isset($table['name'])) {
+        $table['name'] = $name;
+      }
+      if ($remove_descriptions) {
+        unset($table['description']);
+        foreach ($table['fields'] as &$field) {
+          unset($field['description']);
+        }
+      }
+    }
+  }
+
+  /**
+   * Check if the static cache populated and initialize it if not.
+   *
+   * @see resetCache()
+   */
+  protected function ensureInitialized() {
+    if (is_null($this->currentVersions)) {
+      if (!$this->currentVersions = $this->keyValue->get('system.schema')->getAll()) {
+        $this->currentVersions = [];
+      }
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Extension/SchemaInstallerInterface.php b/core/lib/Drupal/Core/Extension/SchemaInstallerInterface.php
new file mode 100644
index 0000000000..9c7f99f3e8
--- /dev/null
+++ b/core/lib/Drupal/Core/Extension/SchemaInstallerInterface.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Drupal\Core\Extension;
+
+/**
+ * Provides an interface for database schema handling.
+ */
+interface SchemaInstallerInterface {
+
+  /**
+   * Indicates that a module schema has not been installed yet.
+   */
+  const UNINSTALLED = -1;
+
+  /**
+   * Returns the currently installed schema version for a module.
+   *
+   * @param string $module
+   *   A module name.
+   *
+   * @return string|int
+   *   The currently installed schema version or static::UNINSTALLED if the
+   *   module is not installed.
+   */
+  public function getInstalledVersion($module);
+
+  /**
+   * Gets all installed schema versions.
+   *
+   * @return array
+   *   An associative array of installed schema versions keyed by module name.
+   */
+  public function getInstalledVersions();
+
+  /**
+   * Reset the version cache.
+   *
+   * @return $this
+   */
+  public function resetCache();
+
+  /**
+   * Updates the installed version information for a module.
+   *
+   * @param string $module
+   *   A module name.
+   * @param string $version
+   *   The new schema version.
+   */
+  public function setInstalledVersion($module, $version);
+
+  /**
+   * Creates all tables defined in a module's hook_schema().
+   *
+   * Note: This function does not pass the module's schema through
+   * hook_schema_alter(). The module's tables will be created exactly as the
+   * module defines them.
+   *
+   * @param string $module
+   *   The module for which the tables will be created.
+   */
+  public function install($module);
+
+  /**
+   * Removes all tables defined in a module's hook_schema().
+   *
+   * Note: This function does not pass the module's schema through
+   * hook_schema_alter(). The module's tables will be deleted exactly as the
+   * module defines them.
+   *
+   * @param string $module
+   *   The module for which the tables will be removed.
+   */
+  public function uninstall($module);
+
+  /**
+   * Returns the module's schema.
+   *
+   * This function can be used to retrieve a schema specification in
+   * hook_schema(), so it allows you to derive your tables from existing
+   * specifications.
+   *
+   * It is also used by ::install() and ::uninstall() to ensure that a module's
+   * tables are created exactly as specified.
+   *
+   * @param string $module
+   *   The module to which the table belongs.
+   * @param string $table
+   *   (optional) The name of the table. Defaults to NULL, which means that the
+   *   module's complete schema is returned.
+   *
+   * @return array
+   *   An array of schema definition provided by hook_schema().
+   *
+   * @see \hook_schema()
+   */
+  public function getSchema($module, $table = NULL);
+
+}
diff --git a/core/lib/Drupal/Core/ProxyClass/Extension/SchemaInstaller.php b/core/lib/Drupal/Core/ProxyClass/Extension/SchemaInstaller.php
new file mode 100644
index 0000000000..3758e2f335
--- /dev/null
+++ b/core/lib/Drupal/Core/ProxyClass/Extension/SchemaInstaller.php
@@ -0,0 +1,128 @@
+<?php
+// @codingStandardsIgnoreFile
+
+/**
+ * This file was generated via php core/scripts/generate-proxy-class.php 'Drupal\Core\Extension\SchemaInstaller' "core/lib/Drupal/Core".
+ */
+
+namespace Drupal\Core\ProxyClass\Extension {
+
+    /**
+     * Provides a proxy class for \Drupal\Core\Extension\SchemaInstaller.
+     *
+     * @see \Drupal\Component\ProxyBuilder
+     */
+    class SchemaInstaller implements \Drupal\Core\Extension\SchemaInstallerInterface
+    {
+
+        use \Drupal\Core\DependencyInjection\DependencySerializationTrait;
+
+        /**
+         * The id of the original proxied service.
+         *
+         * @var string
+         */
+        protected $drupalProxyOriginalServiceId;
+
+        /**
+         * The real proxied service, after it was lazy loaded.
+         *
+         * @var \Drupal\Core\Extension\SchemaInstaller
+         */
+        protected $service;
+
+        /**
+         * The service container.
+         *
+         * @var \Symfony\Component\DependencyInjection\ContainerInterface
+         */
+        protected $container;
+
+        /**
+         * Constructs a ProxyClass Drupal proxy object.
+         *
+         * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+         *   The container.
+         * @param string $drupal_proxy_original_service_id
+         *   The service ID of the original service.
+         */
+        public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container, $drupal_proxy_original_service_id)
+        {
+            $this->container = $container;
+            $this->drupalProxyOriginalServiceId = $drupal_proxy_original_service_id;
+        }
+
+        /**
+         * Lazy loads the real service from the container.
+         *
+         * @return object
+         *   Returns the constructed real service.
+         */
+        protected function lazyLoadItself()
+        {
+            if (!isset($this->service)) {
+                $this->service = $this->container->get($this->drupalProxyOriginalServiceId);
+            }
+
+            return $this->service;
+        }
+
+        /**
+         * {@inheritdoc}
+         */
+        public function getInstalledVersion($module)
+        {
+            return $this->lazyLoadItself()->getInstalledVersion($module);
+        }
+
+        /**
+         * {@inheritdoc}
+         */
+        public function getInstalledVersions()
+        {
+            return $this->lazyLoadItself()->getInstalledVersions();
+        }
+
+        /**
+         * {@inheritdoc}
+         */
+        public function resetCache()
+        {
+            return $this->lazyLoadItself()->resetCache();
+        }
+
+        /**
+         * {@inheritdoc}
+         */
+        public function setInstalledVersion($module, $version)
+        {
+            return $this->lazyLoadItself()->setInstalledVersion($module, $version);
+        }
+
+        /**
+         * {@inheritdoc}
+         */
+        public function install($module)
+        {
+            return $this->lazyLoadItself()->install($module);
+        }
+
+        /**
+         * {@inheritdoc}
+         */
+        public function uninstall($module)
+        {
+            return $this->lazyLoadItself()->uninstall($module);
+        }
+
+        /**
+         * {@inheritdoc}
+         */
+        public function getSchema($module, $table = NULL)
+        {
+            return $this->lazyLoadItself()->getSchema($module, $table);
+        }
+
+    }
+
+}
diff --git a/core/modules/system/src/Controller/DbUpdateController.php b/core/modules/system/src/Controller/DbUpdateController.php
index ef54a705a0..38a5bf2d48 100644
--- a/core/modules/system/src/Controller/DbUpdateController.php
+++ b/core/modules/system/src/Controller/DbUpdateController.php
@@ -5,6 +5,7 @@
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Controller\ControllerBase;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Extension\SchemaInstallerInterface;
 use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface;
 use Drupal\Core\Render\BareHtmlPageRendererInterface;
 use Drupal\Core\Session\AccountInterface;
@@ -77,6 +78,13 @@ class DbUpdateController extends ControllerBase {
    */
   protected $postUpdateRegistry;
 
+  /**
+   * The schema service.
+   *
+   * @var \Drupal\Core\Extension\SchemaInstallerInterface
+   */
+  protected $schemaInstaller;
+
   /**
    * Constructs a new UpdateController.
    *
@@ -96,8 +104,10 @@ class DbUpdateController extends ControllerBase {
    *   The bare HTML page renderer.
    * @param \Drupal\Core\Update\UpdateRegistry $post_update_registry
    *   The post update registry.
+   * @param \Drupal\Core\Extension\SchemaInstallerInterface $schema_installer
+   *   The schema installer.
    */
-  public function __construct($root, KeyValueExpirableFactoryInterface $key_value_expirable_factory, CacheBackendInterface $cache, StateInterface $state, ModuleHandlerInterface $module_handler, AccountInterface $account, BareHtmlPageRendererInterface $bare_html_page_renderer, UpdateRegistry $post_update_registry) {
+  public function __construct($root, KeyValueExpirableFactoryInterface $key_value_expirable_factory, CacheBackendInterface $cache, StateInterface $state, ModuleHandlerInterface $module_handler, AccountInterface $account, BareHtmlPageRendererInterface $bare_html_page_renderer, UpdateRegistry $post_update_registry, SchemaInstallerInterface $schema_installer = NULL) {
     $this->root = $root;
     $this->keyValueExpirableFactory = $key_value_expirable_factory;
     $this->cache = $cache;
@@ -106,6 +116,11 @@ public function __construct($root, KeyValueExpirableFactoryInterface $key_value_
     $this->account = $account;
     $this->bareHtmlPageRenderer = $bare_html_page_renderer;
     $this->postUpdateRegistry = $post_update_registry;
+    if (!$schema_installer) {
+      @trigger_error('The schema installer service must be passed to ' . __METHOD__ . '. Creating ' . __CLASS__ . ' without it is deprecated in drupal:9.1.0 and will be required in drupal:10.0.0. See https://www.drupal.org/node/2970993', E_USER_DEPRECATED);
+      $schema_installer = \Drupal::service('schema_installer');
+    }
+    $this->schemaInstaller = $schema_installer;
   }
 
   /**
@@ -120,7 +135,8 @@ public static function create(ContainerInterface $container) {
       $container->get('module_handler'),
       $container->get('current_user'),
       $container->get('bare_html_page_renderer'),
-      $container->get('update.post_update_registry')
+      $container->get('update.post_update_registry'),
+      $container->get('schema_installer')
     );
   }
 
@@ -600,7 +616,7 @@ protected function triggerBatch(Request $request) {
         // correct place. (The updates are already sorted, so we can simply base
         // this on the first one we come across in the above foreach loop.)
         if (isset($start[$update['module']])) {
-          drupal_set_installed_schema_version($update['module'], $update['number'] - 1);
+          $this->schemaInstaller->setInstalledVersion($update['module'], $update['number'] - 1);
           unset($start[$update['module']]);
         }
         $operations[] = ['update_do_one', [$update['module'], $update['number'], $dependency_map[$function]]];
diff --git a/core/modules/system/src/Form/ModulesUninstallForm.php b/core/modules/system/src/Form/ModulesUninstallForm.php
index 52f378e709..c725c3d991 100644
--- a/core/modules/system/src/Form/ModulesUninstallForm.php
+++ b/core/modules/system/src/Form/ModulesUninstallForm.php
@@ -8,6 +8,7 @@
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface;
+use Drupal\Core\Extension\SchemaInstallerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -45,6 +46,13 @@ class ModulesUninstallForm extends FormBase {
    */
   protected $moduleExtensionList;
 
+  /**
+   * The schema service.
+   *
+   * @var \Drupal\Core\Extension\SchemaInstallerInterface
+   */
+  protected $schemaInstaller;
+
   /**
    * {@inheritdoc}
    */
@@ -53,7 +61,8 @@ public static function create(ContainerInterface $container) {
       $container->get('module_handler'),
       $container->get('module_installer'),
       $container->get('keyvalue.expirable')->get('modules_uninstall'),
-      $container->get('extension.list.module')
+      $container->get('extension.list.module'),
+      $container->get('schema_installer')
     );
   }
 
@@ -68,12 +77,19 @@ public static function create(ContainerInterface $container) {
    *   The key value expirable factory.
    * @param \Drupal\Core\Extension\ModuleExtensionList $extension_list_module
    *   The module extension list.
+   * @param \Drupal\Core\Extension\SchemaInstallerInterface $schema_installer
+   *   The schema installer.
    */
-  public function __construct(ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, KeyValueStoreExpirableInterface $key_value_expirable, ModuleExtensionList $extension_list_module) {
+  public function __construct(ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, KeyValueStoreExpirableInterface $key_value_expirable, ModuleExtensionList $extension_list_module, SchemaInstallerInterface $schema_installer = NULL) {
     $this->moduleExtensionList = $extension_list_module;
     $this->moduleHandler = $module_handler;
     $this->moduleInstaller = $module_installer;
     $this->keyValueExpirable = $key_value_expirable;
+    if (!$schema_installer) {
+      @trigger_error('The schema installer service must be passed to ' . __METHOD__ . '. Creating ' . __CLASS__ . ' without it is deprecated in drupal:9.1.0 and will be required in drupal:10.0.0. See https://www.drupal.org/node/2970993', E_USER_DEPRECATED);
+      $schema_installer = \Drupal::service('schema_installer');
+    }
+    $this->schemaInstaller = $schema_installer;
   }
 
   /**
@@ -153,7 +169,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       // All modules which depend on this one must be uninstalled first, before
       // we can allow this module to be uninstalled.
       foreach (array_keys($module->required_by) as $dependent) {
-        if (drupal_get_installed_schema_version($dependent) != SCHEMA_UNINSTALLED) {
+        if ($this->schemaInstaller->getInstalledVersion($dependent) != SchemaInstallerInterface::UNINSTALLED) {
           $form['modules'][$module->getName()]['#required_by'][] = $dependent;
           $form['uninstall'][$module->getName()]['#disabled'] = TRUE;
         }
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index dc39b9a88c..5425e158b3 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -786,10 +786,12 @@ function system_requirements($phase) {
 
     // Check installed modules.
     $has_pending_updates = FALSE;
+    /** @var \Drupal\Core\Extension\SchemaInstallerInterface $schema_installer */
+    $schema_installer = \Drupal::service('schema_installer');
     foreach (\Drupal::moduleHandler()->getModuleList() as $module => $filename) {
       $updates = drupal_get_schema_versions($module);
       if ($updates !== FALSE) {
-        $default = drupal_get_installed_schema_version($module);
+        $default = $schema_installer->getInstalledVersion($module);
         if (max($updates) > $default) {
           $has_pending_updates = TRUE;
           break;
@@ -1231,17 +1233,19 @@ function system_requirements($phase) {
   // one that was last removed.
   if ($phase == 'update') {
     $module_handler = \Drupal::moduleHandler();
+    /** @var \Drupal\Core\Extension\SchemaInstallerInterface $schema_installer */
+    $schema_installer = \Drupal::service('schema_installer');
     $module_list = [];
     foreach ($module_handler->getImplementations('update_last_removed') as $module) {
       $last_removed = $module_handler->invoke($module, 'update_last_removed');
-      if ($last_removed && $last_removed > drupal_get_installed_schema_version($module)) {
+      if ($last_removed && $last_removed > $schema_installer->getInstalledVersion($module)) {
 
         /** @var \Drupal\Core\Extension\Extension $module_info */
         $module_info = \Drupal::service('extension.list.module')->get($module);
         $module_list[$module] = [
           'name' => $module_info->info['name'],
           'last_removed' => $last_removed,
-          'installed_version' => drupal_get_installed_schema_version($module),
+          'installed_version' => $schema_installer->getInstalledVersion($module),
         ];
       }
     }
diff --git a/core/modules/system/tests/modules/module_test/module_test.install b/core/modules/system/tests/modules/module_test/module_test.install
index 9d80d89605..9ec2788c5d 100644
--- a/core/modules/system/tests/modules/module_test/module_test.install
+++ b/core/modules/system/tests/modules/module_test/module_test.install
@@ -30,7 +30,7 @@ function module_test_schema() {
  * Implements hook_install().
  */
 function module_test_install() {
-  $schema = drupal_get_module_schema('module_test', 'module_test');
+  $schema = \Drupal::service('schema_installer')->getSchema('module_test', 'module_test');
   Database::getConnection()->insert('module_test')
     ->fields([
       'data' => $schema['fields']['data']['type'],
diff --git a/core/modules/system/tests/src/Functional/Module/InstallTest.php b/core/modules/system/tests/src/Functional/Module/InstallTest.php
index e6a83e9323..0812ed7abc 100644
--- a/core/modules/system/tests/src/Functional/Module/InstallTest.php
+++ b/core/modules/system/tests/src/Functional/Module/InstallTest.php
@@ -51,9 +51,13 @@ public function testEnableUserTwice() {
    * Tests recorded schema versions of early installed modules in the installer.
    */
   public function testRequiredModuleSchemaVersions() {
-    $version = drupal_get_installed_schema_version('system', TRUE);
+    /** @var \Drupal\Core\Extension\SchemaInstallerInterface $schema_installer */
+    $schema_installer = \Drupal::service('schema_installer');
+    $schema_installer->resetCache();
+    $version = $schema_installer->getInstalledVersion('system');
     $this->assertTrue($version > 0, 'System module version is > 0.');
-    $version = drupal_get_installed_schema_version('user', TRUE);
+    $schema_installer->resetCache();
+    $version = $schema_installer->getInstalledVersion('user');
     $this->assertTrue($version > 0, 'User module version is > 0.');
 
     $post_update_key_value = \Drupal::keyValue('post_update');
diff --git a/core/modules/system/tests/src/Functional/Module/ModuleTestBase.php b/core/modules/system/tests/src/Functional/Module/ModuleTestBase.php
index e83f558df1..da4f6a14d1 100644
--- a/core/modules/system/tests/src/Functional/Module/ModuleTestBase.php
+++ b/core/modules/system/tests/src/Functional/Module/ModuleTestBase.php
@@ -56,7 +56,7 @@ public function assertTableCount($base_table, $count = TRUE) {
    *   The name of the module.
    */
   public function assertModuleTablesExist($module) {
-    $tables = array_keys(drupal_get_module_schema($module));
+    $tables = array_keys(\Drupal::service('schema_installer')->getSchema($module));
     $tables_exist = TRUE;
     $schema = Database::getConnection()->schema();
     foreach ($tables as $table) {
@@ -74,7 +74,7 @@ public function assertModuleTablesExist($module) {
    *   The name of the module.
    */
   public function assertModuleTablesDoNotExist($module) {
-    $tables = array_keys(drupal_get_module_schema($module));
+    $tables = array_keys(\Drupal::service('schema_installer')->getSchema($module));
     $tables_exist = FALSE;
     $schema = Database::getConnection()->schema();
     foreach ($tables as $table) {
diff --git a/core/modules/system/tests/src/Functional/System/StatusTest.php b/core/modules/system/tests/src/Functional/System/StatusTest.php
index 09f27a5ba0..7e6c1851b8 100644
--- a/core/modules/system/tests/src/Functional/System/StatusTest.php
+++ b/core/modules/system/tests/src/Functional/System/StatusTest.php
@@ -68,12 +68,12 @@ public function testStatusPage() {
 
     // Set the schema version of update_test_postupdate to a lower version, so
     // update_test_postupdate_update_8001() needs to be executed.
-    drupal_set_installed_schema_version('update_test_postupdate', 8000);
+    \Drupal::service('schema_installer')->setInstalledVersion('update_test_postupdate', 8000);
     $this->drupalGet('admin/reports/status');
     $this->assertText(t('Out of date'));
 
     // Now cleanup the executed post update functions.
-    drupal_set_installed_schema_version('update_test_postupdate', 8001);
+    \Drupal::service('schema_installer')->setInstalledVersion('update_test_postupdate', 8001);
     /** @var \Drupal\Core\Update\UpdateRegistry $post_update_registry */
     $post_update_registry = \Drupal::service('update.post_update_registry');
     $post_update_registry->filterOutInvokedUpdatesByModule('update_test_postupdate');
diff --git a/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathLastRemovedTest.php b/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathLastRemovedTest.php
index 20d18856c1..4d39ad2e03 100644
--- a/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathLastRemovedTest.php
+++ b/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathLastRemovedTest.php
@@ -53,8 +53,8 @@ protected function setUp(): void {
    * Tests that a module with a too old schema version can not be updated.
    */
   public function testLastRemovedVersion() {
-    drupal_set_installed_schema_version('update_test_last_removed', 8000);
-    drupal_set_installed_schema_version('system', 8804);
+    $this->getSchemaInstaller()->setInstalledVersion('update_test_last_removed', 8000);
+    $this->getSchemaInstaller()->setInstalledVersion('system', 8804);
 
     // Access the update page with a schema version that is too old for system
     // and the test module, only the generic core message should be shown.
@@ -70,7 +70,7 @@ public function testLastRemovedVersion() {
 
     // Update the installed version of system and then assert that now,
     // the test module is shown instead.
-    drupal_set_installed_schema_version('system', 8805);
+    $this->getSchemaInstaller()->setInstalledVersion('system', 8805);
     $this->drupalGet($this->updateUrl);
 
     $assert_session->pageTextNotContains('The version of Drupal you are trying to update from is too old');
@@ -81,11 +81,21 @@ public function testLastRemovedVersion() {
 
     // Set the expected schema version for the node and test module, updates are
     // successful now.
-    drupal_set_installed_schema_version('update_test_last_removed', 8002);
+    $this->getSchemaInstaller()->setInstalledVersion('update_test_last_removed', 8002);
 
     $this->runUpdates();
-    $this->assertEquals(8003, drupal_get_installed_schema_version('update_test_last_removed'));
+    $this->assertEquals(8003, $this->getSchemaInstaller()->getInstalledVersion('update_test_last_removed'));
 
   }
 
+  /**
+   * Returns the schema installer service.
+   *
+   * @return \Drupal\Core\Extension\SchemaInstallerInterface
+   *   The schema installer.
+   */
+  protected function getSchemaInstaller() {
+    return \Drupal::service('schema_installer');
+  }
+
 }
diff --git a/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathNewDependencyTest.php b/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathNewDependencyTest.php
index 6830c3b7f5..c876c9b0e7 100644
--- a/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathNewDependencyTest.php
+++ b/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathNewDependencyTest.php
@@ -31,7 +31,7 @@ public function testUpdateNewDependency() {
       ->set('module.new_dependency_test', 0)
       ->set('module', module_config_sort($extension_config->get('module')))
       ->save(TRUE);
-    drupal_set_installed_schema_version('new_dependency_test', \Drupal::CORE_MINIMUM_SCHEMA_VERSION);
+    \Drupal::service('schema_installer')->setInstalledVersion('new_dependency_test', \Drupal::CORE_MINIMUM_SCHEMA_VERSION);
 
     // Rebuild the container and test that the service with the optional unmet
     // dependency is still available while the ones that fail are not.
diff --git a/core/modules/system/tests/src/Functional/UpdateSystem/UpdateSchemaTest.php b/core/modules/system/tests/src/Functional/UpdateSystem/UpdateSchemaTest.php
index 95efd30eac..2859259ffe 100644
--- a/core/modules/system/tests/src/Functional/UpdateSystem/UpdateSchemaTest.php
+++ b/core/modules/system/tests/src/Functional/UpdateSystem/UpdateSchemaTest.php
@@ -54,9 +54,11 @@ protected function setUp(): void {
    */
   public function testUpdateHooks() {
     $connection = Database::getConnection();
+    /** @var \Drupal\Core\Extension\SchemaInstallerInterface $schema_installer */
+    $schema_installer = \Drupal::service('schema_installer');
 
     // Verify that the 8000 schema is in place.
-    $this->assertEqual(drupal_get_installed_schema_version('update_test_schema'), 8000);
+    $this->assertEqual($schema_installer->getInstalledVersion('update_test_schema'), 8000);
     $this->assertFalse($connection->schema()->indexExists('update_test_schema_table', 'test'), 'Version 8000 of the update_test_schema module is installed.');
 
     // Increment the schema version.
@@ -72,7 +74,8 @@ public function testUpdateHooks() {
     $this->checkForMetaRefresh();
 
     // Ensure schema has changed.
-    $this->assertEqual(drupal_get_installed_schema_version('update_test_schema', TRUE), 8001);
+    $schema_installer->resetCache();
+    $this->assertEqual($schema_installer->getInstalledVersion('update_test_schema'), 8001);
     // Ensure the index was added for column a.
     $this->assertTrue($connection->schema()->indexExists('update_test_schema_table', 'test'), 'Version 8001 of the update_test_schema module is installed.');
 
@@ -80,7 +83,7 @@ public function testUpdateHooks() {
     require_once $this->root . '/core/includes/update.inc';
     update_set_schema('update_test_schema', 8003);
     // Ensure schema has changed.
-    $this->assertEqual(drupal_get_installed_schema_version('update_test_schema'), 8003);
+    $this->assertEqual($schema_installer->getInstalledVersion('update_test_schema'), 8003);
 
   }
 
diff --git a/core/modules/system/tests/src/Functional/UpdateSystem/UpdateScriptTest.php b/core/modules/system/tests/src/Functional/UpdateSystem/UpdateScriptTest.php
index 38a6120d8f..f5977d8ab9 100644
--- a/core/modules/system/tests/src/Functional/UpdateSystem/UpdateScriptTest.php
+++ b/core/modules/system/tests/src/Functional/UpdateSystem/UpdateScriptTest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Tests\system\Functional\UpdateSystem;
 
+use Drupal\Core\Extension\SchemaInstallerInterface;
 use Drupal\Component\Serialization\Yaml;
 use Drupal\Core\Url;
 use Drupal\language\Entity\ConfigurableLanguage;
@@ -142,7 +143,8 @@ public function testRequirements() {
     // successfully.
     $this->drupalLogin($this->updateUser);
     $update_script_test_config->set('requirement_type', REQUIREMENT_WARNING)->save();
-    drupal_set_installed_schema_version('update_script_test', drupal_get_installed_schema_version('update_script_test') - 1);
+    $schema_installer = \Drupal::service('schema_installer');
+    $schema_installer->setInstalledVersion('update_script_test', $schema_installer->getInstalledVersion('update_script_test') - 1);
     $this->drupalGet($this->updateUrl, ['external' => TRUE]);
     $this->assertText('This is a requirements warning provided by the update_script_test module.');
     $this->clickLink('try again');
@@ -547,13 +549,15 @@ public function testSuccessfulUpdateFunctionality() {
     $final_maintenance_mode = $this->container->get('state')->get('system.maintenance_mode');
     $this->assertEqual($final_maintenance_mode, $initial_maintenance_mode, 'Maintenance mode should not have changed after database updates.');
 
-    // Reset the static cache to ensure we have the most current setting.
-    $schema_version = drupal_get_installed_schema_version('update_script_test', TRUE);
+    // Reset the static cache to get the current schema after running updates.
+    $schema_installer = \Drupal::service('schema_installer');
+    $this->assertTrue($schema_installer instanceof SchemaInstallerInterface);
+    $schema_version = $schema_installer->resetCache()->getInstalledVersion('update_script_test');
     $this->assertEqual($schema_version, 8001, 'update_script_test schema version is 8001 after updating.');
 
     // Set the installed schema version to one less than the current update.
-    drupal_set_installed_schema_version('update_script_test', $schema_version - 1);
-    $schema_version = drupal_get_installed_schema_version('update_script_test', TRUE);
+    $schema_installer->setInstalledVersion('update_script_test', $schema_version - 1);
+    $schema_version = $schema_installer->getInstalledVersion('update_script_test');
     $this->assertEqual($schema_version, 8000, 'update_script_test schema version overridden to 8000.');
 
     // Click through update.php with 'access administration pages' and
@@ -607,13 +611,14 @@ public function testSuccessfulMultilingualUpdateFunctionality() {
     $config->set('url.prefixes.en', 'en');
     $config->save();
 
-    // Reset the static cache to ensure we have the most current setting.
-    $schema_version = drupal_get_installed_schema_version('update_script_test', TRUE);
+    /** @var \Drupal\Core\Extension\SchemaInstallerInterface $schema_installer */
+    $schema_installer = \Drupal::service('schema_installer');
+    $schema_version = $schema_installer->getInstalledVersion('update_script_test');
     $this->assertEqual($schema_version, 8001, 'update_script_test schema version is 8001 after updating.');
 
     // Set the installed schema version to one less than the current update.
-    drupal_set_installed_schema_version('update_script_test', $schema_version - 1);
-    $schema_version = drupal_get_installed_schema_version('update_script_test', TRUE);
+    $schema_installer->setInstalledVersion('update_script_test', $schema_version - 1);
+    $schema_version = $schema_installer->getInstalledVersion('update_script_test');
     $this->assertEqual($schema_version, 8000, 'update_script_test schema version overridden to 8000.');
 
     // Create admin user.
@@ -662,12 +667,14 @@ public function testMaintenanceModeLink() {
    * Helper function to run updates via the browser.
    */
   protected function runUpdates($maintenance_mode) {
-    $schema_version = drupal_get_installed_schema_version('update_script_test');
+    /** @var \Drupal\Core\Extension\SchemaInstallerInterface $schema_installer */
+    $schema_installer = \Drupal::service('schema_installer');
+    $schema_version = $schema_installer->getInstalledVersion('update_script_test');
     $this->assertEqual($schema_version, 8001, 'update_script_test is initially installed with schema version 8001.');
 
     // Set the installed schema version to one less than the current update.
-    drupal_set_installed_schema_version('update_script_test', $schema_version - 1);
-    $schema_version = drupal_get_installed_schema_version('update_script_test', TRUE);
+    $schema_installer->setInstalledVersion('update_script_test', $schema_version - 1);
+    $schema_version = $schema_installer->getInstalledVersion('update_script_test');
     $this->assertEqual($schema_version, 8000, 'update_script_test schema version overridden to 8000.');
 
     // Click through update.php with 'administer software updates' permission.
diff --git a/core/modules/system/tests/src/Functional/UpdateSystem/UpdatesWith7xTest.php b/core/modules/system/tests/src/Functional/UpdateSystem/UpdatesWith7xTest.php
index 27e721b762..9efff4b26a 100644
--- a/core/modules/system/tests/src/Functional/UpdateSystem/UpdatesWith7xTest.php
+++ b/core/modules/system/tests/src/Functional/UpdateSystem/UpdatesWith7xTest.php
@@ -49,11 +49,11 @@ protected function setUp(): void {
   public function testWith7x() {
     // Ensure that the minimum schema version is 8000, despite 7200 update
     // hooks and a 7XXX hook_update_last_removed().
-    $this->assertEqual(drupal_get_installed_schema_version('update_test_with_7x'), 8000);
+    $this->assertEqual(\Drupal::service('schema_installer')->getInstalledVersion('update_test_with_7x'), 8000);
 
     // Try to manually set the schema version to 7110 and ensure that no
     // updates are allowed.
-    drupal_set_installed_schema_version('update_test_with_7x', 7110);
+    \Drupal::service('schema_installer')->setInstalledVersion('update_test_with_7x', 7110);
 
     // Click through update.php with 'administer software updates' permission.
     $this->drupalLogin($this->updateUser);
diff --git a/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php b/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php
index c1e2618795..02b8380be8 100644
--- a/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php
+++ b/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php
@@ -5,6 +5,7 @@
 use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Core\Extension\MissingDependencyException;
 use Drupal\Core\Extension\ModuleUninstallValidatorException;
+use Drupal\Core\Extension\SchemaInstallerInterface;
 use Drupal\entity_test\Entity\EntityTest;
 use Drupal\KernelTests\KernelTestBase;
 
@@ -124,8 +125,10 @@ public function testDependencyResolution() {
     $result = $this->moduleInstaller()->uninstall(['config', 'help', 'color']);
     $this->assertTrue($result, 'ModuleInstaller::uninstall() returned TRUE.');
 
+    /** @var \Drupal\Core\Extension\SchemaInstallerInterface $schema_installer */
+    $schema_installer = \Drupal::service('schema_installer');
     foreach (['color', 'config', 'help'] as $module) {
-      $this->assertEqual(drupal_get_installed_schema_version($module), SCHEMA_UNINSTALLED, "$module module was uninstalled.");
+      $this->assertEqual($schema_installer->getInstalledVersion($module), SchemaInstallerInterface::UNINSTALLED, "$module module was uninstalled.");
     }
     $uninstalled_modules = \Drupal::state()->get('module_test.uninstall_order', []);
     $this->assertEqual($uninstalled_modules, ['color', 'config', 'help'], 'Modules were uninstalled in the correct order.');
@@ -177,7 +180,7 @@ public function testUninstallProfileDependency() {
     $result = $this->moduleInstaller()->uninstall([$non_dependency]);
     $this->assertTrue($result, 'ModuleInstaller::uninstall() returns TRUE.');
     $this->assertFalse($this->moduleHandler()->moduleExists($non_dependency));
-    $this->assertEquals(drupal_get_installed_schema_version($non_dependency), SCHEMA_UNINSTALLED, "$non_dependency module was uninstalled.");
+    $this->assertEquals(\Drupal::service('schema_installer')->getInstalledVersion($non_dependency), SchemaInstallerInterface::UNINSTALLED, "$non_dependency module was uninstalled.");
 
     // Verify that the installation profile itself was not uninstalled.
     $uninstalled_modules = \Drupal::state()->get('module_test.uninstall_order', []);
@@ -272,7 +275,7 @@ public function testUninstallContentDependency() {
 
     $result = $this->moduleInstaller()->uninstall(['help']);
     $this->assertTrue($result, 'ModuleInstaller::uninstall() returns TRUE.');
-    $this->assertEqual(drupal_get_installed_schema_version('entity_test'), SCHEMA_UNINSTALLED, "entity_test module was uninstalled.");
+    $this->assertEqual(\Drupal::service('schema_installer')->getInstalledVersion('entity_test'), SchemaInstallerInterface::UNINSTALLED, "entity_test module was uninstalled.");
   }
 
   /**
diff --git a/core/tests/Drupal/FunctionalTests/Update/UpdatePathTestBaseTest.php b/core/tests/Drupal/FunctionalTests/Update/UpdatePathTestBaseTest.php
index 34d2e86829..1d9282b1c9 100644
--- a/core/tests/Drupal/FunctionalTests/Update/UpdatePathTestBaseTest.php
+++ b/core/tests/Drupal/FunctionalTests/Update/UpdatePathTestBaseTest.php
@@ -33,9 +33,10 @@ protected function setDatabaseDumpFiles() {
   public function testDatabaseLoaded() {
     // Set a value in the cache to prove caches are cleared.
     \Drupal::service('cache.default')->set(__CLASS__, 'Test');
-
+    /** @var \Drupal\Core\Extension\SchemaInstallerInterface $schema_installer */
+    $schema_installer = \Drupal::service('schema_installer');
     foreach (['user' => 8100, 'node' => 8700, 'system' => 8805, 'update_test_schema' => 8000] as $module => $schema) {
-      $this->assertEqual(drupal_get_installed_schema_version($module), $schema, new FormattableMarkup('Module @module schema is @schema', ['@module' => $module, '@schema' => $schema]));
+      $this->assertEqual($schema_installer->getInstalledVersion($module), $schema, new FormattableMarkup('Module @module schema is @schema', ['@module' => $module, '@schema' => $schema]));
     }
 
     // Ensure that all {router} entries can be unserialized. If they cannot be
@@ -97,8 +98,11 @@ public function testUpdateHookN() {
     $this->assertEqual([], $container_cannot_be_saved_messages);
 
     // Ensure schema has changed.
-    $this->assertEqual(drupal_get_installed_schema_version('update_test_schema', TRUE), 8001);
-    $this->assertEqual(drupal_get_installed_schema_version('update_test_semver_update_n', TRUE), 8001);
+    /** @var \Drupal\Core\Extension\SchemaInstallerInterface $schema_installer */
+    $schema_installer = \Drupal::service('schema_installer');
+    $schema_installer->resetCache();
+    $this->assertEqual($schema_installer->getInstalledVersion('update_test_schema'), 8001);
+    $this->assertEqual($schema_installer->getInstalledVersion('update_test_semver_update_n'), 8001);
     // Ensure the index was added for column a.
     $this->assertTrue($connection->schema()->indexExists('update_test_schema_table', 'test'), 'Version 8001 of the update_test_schema module is installed.');
     // Ensure update_test_semver_update_n_update_8001 was run.
diff --git a/core/tests/Drupal/KernelTests/Core/Database/InsertDefaultsTest.php b/core/tests/Drupal/KernelTests/Core/Database/InsertDefaultsTest.php
index 8c0f57f020..347d6e2e38 100644
--- a/core/tests/Drupal/KernelTests/Core/Database/InsertDefaultsTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Database/InsertDefaultsTest.php
@@ -18,7 +18,7 @@ public function testDefaultInsert() {
     $query = $this->connection->insert('test')->useDefaults(['job']);
     $id = $query->execute();
 
-    $schema = drupal_get_module_schema('database_test', 'test');
+    $schema = \Drupal::service('schema_installer')->getSchema('database_test', 'test');
 
     $job = $this->connection->query('SELECT job FROM {test} WHERE id = :id', [':id' => $id])->fetchField();
     $this->assertEqual($job, $schema['fields']['job']['default'], 'Default field value is set.');
@@ -52,7 +52,7 @@ public function testDefaultInsertWithFields() {
       ->useDefaults(['job']);
     $id = $query->execute();
 
-    $schema = drupal_get_module_schema('database_test', 'test');
+    $schema = \Drupal::service('schema_installer')->getSchema('database_test', 'test');
 
     $job = $this->connection->query('SELECT job FROM {test} WHERE id = :id', [':id' => $id])->fetchField();
     $this->assertEqual($job, $schema['fields']['job']['default'], 'Default field value is set.');
diff --git a/core/tests/Drupal/KernelTests/Core/Extension/SchemaDeprecationTest.php b/core/tests/Drupal/KernelTests/Core/Extension/SchemaDeprecationTest.php
new file mode 100644
index 0000000000..a4c4d82f91
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Extension/SchemaDeprecationTest.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Extension;
+
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Tests deprecated schema.inc functions.
+ *
+ * @group legacy
+ * @group extension
+ */
+class SchemaDeprecationTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['system', 'dblog'];
+
+  /**
+   * @expectedDeprecation drupal_get_installed_schema_version() is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Use \Drupal\Core\Extension\SchemaInstallerInterface::getInstalledVersion() instead. See https://www.drupal.org/node/2970993
+   * @expectedDeprecation drupal_set_installed_schema_version() is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Use \Drupal\Core\Extension\SchemaInstallerInterface::setInstalledVersion() instead. See https://www.drupal.org/node/2970993
+   */
+  public function testDeprecatedGetInstalledSchemaVersion() {
+    $this->assertEquals(SCHEMA_UNINSTALLED, drupal_get_installed_schema_version('dblog'));
+    drupal_set_installed_schema_version('dblog', '1');
+    $this->assertEquals('1', drupal_get_installed_schema_version('dblog'));
+  }
+
+  /**
+   * @expectedDeprecation drupal_install_schema() is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Use \Drupal\Core\Extension\SchemaInstallerInterface::install() instead. See https://www.drupal.org/node/2970993
+   * @expectedDeprecation drupal_get_module_schema() is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Use \Drupal\Core\Extension\SchemaInstallerInterface::uninstall() instead. See https://www.drupal.org/node/2970993
+   * @expectedDeprecation drupal_uninstall_schema() is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Use \Drupal\Core\Extension\SchemaInstallerInterface::uninstall() instead. See https://www.drupal.org/node/2970993
+   */
+  public function testDeprecatedInstallSchema() {
+    drupal_install_schema('dblog');
+    $this->assertEquals('watchdog', key(drupal_get_module_schema('dblog')));
+    drupal_uninstall_schema('dblog');
+  }
+
+  /**
+   * @expectedDeprecation _drupal_schema_initialize() is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. No direct replacement provided. See https://www.drupal.org/node/2970993
+   */
+  public function testDeprecatedSchemaInitialize() {
+    $module = 'dblog';
+    $table = 'watchdog';
+    $schema = [$table => []];
+    _drupal_schema_initialize($schema, $module, FALSE);
+    $this->assertEquals($module, $schema[$table]['module']);
+    $this->assertEquals($table, $schema[$table]['name']);
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/KernelTestBase.php b/core/tests/Drupal/KernelTests/KernelTestBase.php
index b1ebad5e32..be0175862f 100644
--- a/core/tests/Drupal/KernelTests/KernelTestBase.php
+++ b/core/tests/Drupal/KernelTests/KernelTestBase.php
@@ -699,7 +699,7 @@ protected function installConfig($modules) {
    *   If $module is not enabled or the table schema cannot be found.
    */
   protected function installSchema($module, $tables) {
-    // drupal_get_module_schema() is technically able to install a schema
+    // Schema installer is technically able to install a schema
     // of a non-enabled module, but its ability to load the module's .install
     // file depends on many other factors. To prevent differences in test
     // behavior and non-reproducible test failures, we only allow the schema of
@@ -708,8 +708,9 @@ protected function installSchema($module, $tables) {
       throw new \LogicException("$module module is not enabled.");
     }
     $tables = (array) $tables;
+    $schema_installer = $this->container->get('schema_installer');
     foreach ($tables as $table) {
-      $schema = drupal_get_module_schema($module, $table);
+      $schema = $schema_installer->getSchema($module, $table);
       if (empty($schema)) {
         throw new \LogicException("$module module does not define a schema for table '$table'.");
       }
diff --git a/core/tests/Drupal/Tests/UpdatePathTestTrait.php b/core/tests/Drupal/Tests/UpdatePathTestTrait.php
index 421f7097f8..fd0ea57f52 100644
--- a/core/tests/Drupal/Tests/UpdatePathTestTrait.php
+++ b/core/tests/Drupal/Tests/UpdatePathTestTrait.php
@@ -60,7 +60,7 @@ protected function runUpdates($update_url = NULL) {
 
       // Ensure that there are no pending updates. Clear the schema version
       // static cache first in case it was accessed before running updates.
-      drupal_get_installed_schema_version(NULL, TRUE);
+      \Drupal::service('schema_installer')->resetCache();
       foreach (['update', 'post_update'] as $update_type) {
         switch ($update_type) {
           case 'update':
