diff --git a/core/core.services.yml b/core/core.services.yml
index c42ca2dd21..83b754a9a7 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', '@database']
     lazy: true
   extension.list.module:
     class: Drupal\Core\Extension\ModuleExtensionList
diff --git a/core/includes/schema.inc b/core/includes/schema.inc
index e168e89556..a830c1e05f 100644
--- a/core/includes/schema.inc
+++ b/core/includes/schema.inc
@@ -113,8 +113,14 @@ 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. No direct
+ *   replacement is provided.
+ *
+ * @see https://www.drupal.org/node/2970993
  */
 function drupal_install_schema($module) {
+  @trigger_error('drupal_install_schema() is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. No direct replacement is provided. See https://www.drupal.org/node/2970993', E_USER_DEPRECATED);
   $schema = drupal_get_module_schema($module);
   _drupal_schema_initialize($schema, $module, FALSE);
 
@@ -128,8 +134,14 @@ 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. No direct
+ *   replacement is provided.
+ *
+ * @see https://www.drupal.org/node/2970993
  */
 function drupal_uninstall_schema($module) {
+  @trigger_error('drupal_uninstall_schema() is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. No direct replacement is provided. See https://www.drupal.org/node/2970993', E_USER_DEPRECATED);
   $tables = drupal_get_module_schema($module);
   _drupal_schema_initialize($tables, $module, FALSE);
   $schema = \Drupal::database()->schema();
@@ -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
+ *   \Drupal\Core\Extension\ModuleInstallerInterface::getSchema() instead.
+ *
+ * @see https://www.drupal.org/node/2970993
+ * @see \Drupal\Core\Extension\ModuleInstallerInterface::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\ModuleInstallerInterface::getSchema() instead. See https://www.drupal.org/node/2970993', E_USER_DEPRECATED);
+  return \Drupal::service('module_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 is 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 is 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/lib/Drupal/Core/Database/database.api.php b/core/lib/Drupal/Core/Database/database.api.php
index a7ccdc32a0..9743bbe3ae 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\ModuleInstaller::installSchema()
  *
  * @}
  */
diff --git a/core/lib/Drupal/Core/Extension/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
index 09f8434370..9593438bc7 100644
--- a/core/lib/Drupal/Core/Extension/ModuleInstaller.php
+++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
@@ -4,6 +4,7 @@
 
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Database\Connection;
 use Drupal\Core\DrupalKernelInterface;
 use Drupal\Core\Entity\EntityStorageException;
 use Drupal\Core\Entity\FieldableEntityInterface;
@@ -43,6 +44,13 @@ class ModuleInstaller implements ModuleInstallerInterface {
    */
   protected $root;
 
+  /**
+   * The database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $connection;
+
   /**
    * The uninstall validators.
    *
@@ -59,14 +67,21 @@ class ModuleInstaller implements ModuleInstallerInterface {
    *   The module handler.
    * @param \Drupal\Core\DrupalKernelInterface $kernel
    *   The drupal kernel.
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection.
    *
    * @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, Connection $connection = NULL) {
     $this->root = $root;
     $this->moduleHandler = $module_handler;
     $this->kernel = $kernel;
+    if (!$connection) {
+      @trigger_error('The database connection 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);
+      $connection = \Drupal::service('database');
+    }
+    $this->connection = $connection;
   }
 
   /**
@@ -225,7 +240,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->installSchema($module);
 
         // Clear plugin manager caches.
         \Drupal::getContainer()->get('plugin.cache_clearer')->clearCachedDefinitions();
@@ -464,7 +479,7 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
       }
 
       // Remove the schema.
-      drupal_uninstall_schema($module);
+      $this->uninstallSchema($module);
 
       // Remove the module's entry from the config. Don't check schema when
       // uninstalling a module since we are only clearing a key.
@@ -602,4 +617,73 @@ public function validateUninstall(array $module_list) {
     return $reasons;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getSchema($module, $table = NULL) {
+    // Load the .install file to use hook_schema() implementation.
+    $this->moduleHandler->loadInclude($module, 'install');
+    $tables = $this->populateSchemaTables($module);
+
+    if (isset($table)) {
+      return $tables[$table] ?? [];
+    }
+    return $tables;
+  }
+
+  /**
+   * Creates all tables defined in a module's hook_schema().
+   *
+   * @param string $module
+   *   The module for which the tables will be created.
+   */
+  private function installSchema($module) {
+    $tables = $this->populateSchemaTables($module);
+    $schema = $this->connection->schema();
+
+    foreach ($tables as $name => $table) {
+      $schema->createTable($name, $table);
+    }
+  }
+
+  /**
+   * Removes all tables defined in a module's hook_schema().
+   *
+   * @param string $module
+   *   The module for which the tables will be removed.
+   */
+  private function uninstallSchema($module) {
+    $tables = $this->populateSchemaTables($module);
+    $schema = $this->connection->schema();
+
+    foreach ($tables as $table) {
+      if ($schema->tableExists($table['name'])) {
+        $schema->dropTable($table['name']);
+      }
+    }
+  }
+
+  /**
+   * Fills in required default values for table definitions from hook_schema().
+   *
+   * @param string $module
+   *   The module for which hook_schema() should be invoked.
+   *
+   * @return array
+   *   An array of schema definition provided by hook_schema().
+   */
+  private function populateSchemaTables($module) {
+    $tables = $this->moduleHandler->invoke($module, 'schema') ?? [];
+    // Set the name and module key for all tables.
+    foreach ($tables as $name => &$table) {
+      if (empty($table['module'])) {
+        $table['module'] = $module;
+      }
+      if (!isset($table['name'])) {
+        $table['name'] = $name;
+      }
+    }
+    return $tables;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php b/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php
index c8221a1722..ac8ff3d478 100644
--- a/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php
+++ b/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php
@@ -81,4 +81,27 @@ public function addUninstallValidator(ModuleUninstallValidatorInterface $uninsta
    */
   public function validateUninstall(array $module_list);
 
+  /**
+   * 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/ModuleInstaller.php b/core/lib/Drupal/Core/ProxyClass/Extension/ModuleInstaller.php
index 174f7fb744..3a6759449a 100644
--- a/core/lib/Drupal/Core/ProxyClass/Extension/ModuleInstaller.php
+++ b/core/lib/Drupal/Core/ProxyClass/Extension/ModuleInstaller.php
@@ -99,6 +99,14 @@ public function validateUninstall(array $module_list)
             return $this->lazyLoadItself()->validateUninstall($module_list);
         }
 
+        /**
+         * {@inheritdoc}
+         */
+        public function getSchema($module, $table = NULL)
+        {
+            return $this->lazyLoadItself()->getSchema($module, $table);
+        }
+
     }
 
 }
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..89631eebd4 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('module_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/ModuleTestBase.php b/core/modules/system/tests/src/Functional/Module/ModuleTestBase.php
index e83f558df1..3c0dd87338 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('module_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('module_installer')->getSchema($module));
     $tables_exist = FALSE;
     $schema = Database::getConnection()->schema();
     foreach ($tables as $table) {
diff --git a/core/tests/Drupal/KernelTests/Core/Database/InsertDefaultsTest.php b/core/tests/Drupal/KernelTests/Core/Database/InsertDefaultsTest.php
index 8c0f57f020..8cc9e6287e 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('module_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('module_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/ModuleInstallerTest.php b/core/tests/Drupal/KernelTests/Core/Extension/ModuleInstallerTest.php
index 696a4f798b..3f47af15c2 100644
--- a/core/tests/Drupal/KernelTests/Core/Extension/ModuleInstallerTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Extension/ModuleInstallerTest.php
@@ -79,11 +79,16 @@ public function testCacheBinCleanup() {
 
   /**
    * Ensure that rebuilding the container in hook_install() works.
+   *
+   * @covers ::getSchema
    */
   public function testKernelRebuildDuringHookInstall() {
     \Drupal::state()->set('module_test_install:rebuild_container', TRUE);
     $module_installer = $this->container->get('module_installer');
     $this->assertTrue($module_installer->install(['module_test']));
+    $schema = $module_installer->getSchema('module_test');
+    $this->assertSame('module_test', $schema['module_test']['module']);
+    $this->assertSame('module_test', $schema['module_test']['name']);
   }
 
   /**
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..470ba66d7f
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Extension/SchemaDeprecationTest.php
@@ -0,0 +1,43 @@
+<?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_install_schema() is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. No direct replacement is provided. 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. No direct replacement is provided. 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\ModuleInstallerInterface::getSchema() 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 is 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..51906423a7 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
+    // Module 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
@@ -707,9 +707,11 @@ protected function installSchema($module, $tables) {
     if (!$this->container->get('module_handler')->moduleExists($module)) {
       throw new \LogicException("$module module is not enabled.");
     }
+    /** @var \Drupal\Core\Extension\ModuleInstaller $module_installer */
+    $module_installer = $this->container->get('module_installer');
     $tables = (array) $tables;
     foreach ($tables as $table) {
-      $schema = drupal_get_module_schema($module, $table);
+      $schema = $module_installer->getSchema($module, $table);
       if (empty($schema)) {
         throw new \LogicException("$module module does not define a schema for table '$table'.");
       }
