diff --git a/core/modules/pgsql/pgsql.install b/core/modules/pgsql/pgsql.install
index c25620d7895..dbec0f7d6b0 100644
--- a/core/modules/pgsql/pgsql.install
+++ b/core/modules/pgsql/pgsql.install
@@ -6,6 +6,8 @@
  */
 
 use Drupal\Core\Database\Database;
+use Drupal\Core\Database\DatabaseExceptionWrapper;
+use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
 
 /**
  * Implements hook_requirements().
@@ -40,3 +42,117 @@ function pgsql_requirements() {
 
   return $requirements;
 }
+
+/**
+ * Update sequences' owner created from serial columns in PostgreSQL.
+ */
+function pgsql_update_10101(&$sandbox) {
+  $connection = \Drupal::database();
+  if ($connection->databaseType() !== 'pgsql') {
+    // This database update is a no-op for all other core database drivers.
+    $sandbox['#finished'] = 1;
+    return NULL;
+  }
+
+  if (!isset($sandbox['progress'])) {
+    $sandbox['fixed'] = 0;
+    $sandbox['progress'] = 0;
+    $sandbox['tables'] = [];
+
+    // Discovers all custom tables with serial columns.
+    $module_handler = \Drupal::moduleHandler();
+    $modules = $module_handler->getModuleList();
+    foreach ($modules as $extension) {
+      $module = $extension->getName();
+      if ($module_handler->moduleExists($module)) {
+        $module_handler->loadInclude($module, 'install');
+        $schema = $module_handler->invoke($module, 'schema');
+        if (!empty($schema)) {
+          foreach ($schema as $table_name => $table_info) {
+            foreach ($table_info['fields'] as $column_name => $column_info) {
+              if (substr($column_info['type'], 0, 6) === 'serial') {
+                $sandbox['tables'][] = [
+                  'table' => $table_name,
+                  'column' => $column_name,
+                ];
+              }
+            }
+          }
+        }
+      }
+    }
+
+    // Discovers all content entity types with integer entity keys that are most
+    // likely serial columns.
+    $entity_types = \Drupal::entityTypeManager()->getDefinitions();
+    /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */
+    foreach ($entity_types as $entity_type) {
+      $storage_class = $entity_type->getStorageClass();
+      if (is_subclass_of($storage_class, SqlContentEntityStorage::class)) {
+        $entity_class = $entity_type->getClass();
+        $id_key = $entity_type->getKey('id');
+        $revision_key = $entity_type->getKey('revision');
+
+        /** @var \Drupal\Core\Field\BaseFieldDefinition[] $base_field_definitions */
+        $base_field_definitions = $entity_class::baseFieldDefinitions($entity_type);
+        if ($base_field_definitions[$id_key]->getType() === 'integer') {
+          $sandbox['tables'][] = [
+            'table' => $entity_type->getBaseTable(),
+            'column' => $id_key,
+          ];
+        }
+
+        if ($entity_type->isRevisionable() &&
+          $base_field_definitions[$revision_key]->getType() === 'integer') {
+          $sandbox['tables'][] = [
+            'table' => $entity_type->getRevisionTable(),
+            'column' => $revision_key,
+          ];
+        }
+      }
+    }
+    $sandbox['max'] = count($sandbox['tables']);
+  }
+  else {
+    // Adds ownership of orphan sequences to tables.
+    $to_process = array_slice($sandbox['tables'], $sandbox['progress'], 50);
+
+    // Ensures that a sequence is not owned first, then ensures that the a
+    // sequence exists at all before trying to alter it.
+    foreach ($to_process as $table_info) {
+      if ($connection->schema()->tableExists($table_info['table'])) {
+        $owned = (bool) _getSequenceName($table_info['table'], $table_info['column']);
+
+        if (!$owned) {
+          $sequence_name = $connection
+            ->makeSequenceName($table_info['table'], $table_info['column']);
+          $exists = _sequenceExists($sequence_name);
+          if ($exists) {
+            $transaction = $connection->startTransaction($sequence_name);
+            try {
+              _updateSequenceOwnership($table_info['table'], $table_info['column']);
+
+              $sandbox['fixed']++;
+            }
+            catch (DatabaseExceptionWrapper $e) {
+              $transaction->rollBack();
+            }
+          }
+        }
+      }
+      $sandbox['progress']++;
+    }
+  }
+
+  if ($sandbox['max'] && $sandbox['progress'] < $sandbox['max']) {
+    $sandbox['#finished'] = $sandbox['progress'] / $sandbox['max'];
+  }
+  else {
+    $sandbox['#finished'] = 1;
+
+    return \Drupal::translation()->formatPlural(
+      $sandbox['fixed'],
+      '1 orphaned sequence fixed.',
+      '@count orphaned sequences fixed');
+  }
+}
diff --git a/core/modules/pgsql/pgsql.module b/core/modules/pgsql/pgsql.module
index 0b5ba470500..6c3b77e417c 100644
--- a/core/modules/pgsql/pgsql.module
+++ b/core/modules/pgsql/pgsql.module
@@ -7,6 +7,9 @@
 
 use Drupal\Core\Routing\RouteMatchInterface;
 
+// cSpell:ignore relkind objid refobjid regclass attname attrelid attnum
+// cSpell:ignore refobjsubid
+
 /**
  * Implements hook_help().
  */
@@ -20,3 +23,77 @@ function pgsql_help($route_name, RouteMatchInterface $route_match) {
 
   }
 }
+
+/**
+ * Alters the ownership of a sequence.
+ *
+ * This is used for updating orphaned sequences.
+ *
+ * @param string $table
+ *   The unquoted or prefixed table name.
+ * @param string $column
+ *   The column name for the sequence.
+ *
+ * @see https://www.drupal.org/i/3028706
+ *
+ * @internal
+ */
+function _updateSequenceOwnership(string $table, string $column): void {
+  $connection = \Drupal::database();
+  $seq = $connection->makeSequenceName($table, $column);
+  $connection->query('ALTER SEQUENCE IF EXISTS ' . $seq . ' OWNED BY {' . $table . '}.[' . $column . ']');
+}
+
+/**
+ * Retrieves a sequence name that is owned by the table and column.
+ *
+ * @param string $table
+ *   A table name that is not prefixed or quoted.
+ * @param string $column
+ *   The column name.
+ *
+ * @return string|null
+ *   The name of the sequence or NULL if it does not exist.
+ */
+function _getSequenceName(string $table, string $column): ?string {
+  $connection = \Drupal::database();
+  return $connection
+    ->query("SELECT pg_get_serial_sequence(:table, :column)", [
+      ':table' => $connection->getPrefix() . $table,
+      ':column' => $column,
+    ])
+    ->fetchField();
+}
+
+/**
+ * Checks if a sequence exists.
+ *
+ * @param string $name
+ *   The fully-qualified sequence name.
+ *
+ * @return bool
+ *   TRUE if the sequence exists by the name.
+ *
+ * @see \Drupal\Core\Database\Connection::makeSequenceName()
+ */
+function _sequenceExists(string $name): bool {
+  return (bool) \Drupal::database()
+    ->query("SELECT c.relname FROM pg_class as c WHERE c.relkind = 'S' AND c.relname = :name", [':name' => $name])
+    ->fetchField();
+}
+
+/**
+ * Retrieves the sequence owner object.
+ *
+ * @return object|bool
+ *   Returns the sequence owner object or bool if it does not exist.
+ */
+function _getSequenceOwner(string $table, string $field): object|bool {
+  $seq_name = _getSequenceName($table, $field);
+  return \Drupal::database()->query("SELECT d.refobjid::regclass as table_name, a.attname as field_name
+      FROM pg_depend d
+      JOIN pg_attribute a ON a.attrelid = d.refobjid AND a.attnum = d.refobjsubid
+      WHERE d.objid = :seq_name::regclass
+      AND d.refobjsubid > 0
+      AND d.classid = 'pg_class'::regclass", [':seq_name' => $seq_name])->fetchObject();
+}
diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php b/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php
index 2336d103fa9..f2ddb4fc584 100644
--- a/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php
+++ b/core/modules/pgsql/src/Driver/Database/pgsql/Schema.php
@@ -956,7 +956,7 @@ public function changeField($table, $field, $field_new, $spec, $new_keys = []) {
       // not when altering. Because of that, the sequence needs to be created
       // and initialized by hand.
       $seq = $this->connection->makeSequenceName($table, $field_new);
-      $this->connection->query("CREATE SEQUENCE " . $seq);
+      $this->connection->query("CREATE SEQUENCE " . $seq . " OWNED BY {" . $table . "}.[" . $field_new . ']');
       // Set sequence to maximal field value to not conflict with existing
       // entries.
       $this->connection->query("SELECT setval('" . $seq . "', MAX([" . $field . "])) FROM {" . $table . "}");
diff --git a/core/modules/pgsql/tests/fixtures/update/drupal-9.pgsql-orphan-sequence.php b/core/modules/pgsql/tests/fixtures/update/drupal-9.pgsql-orphan-sequence.php
new file mode 100644
index 00000000000..8115669a7ec
--- /dev/null
+++ b/core/modules/pgsql/tests/fixtures/update/drupal-9.pgsql-orphan-sequence.php
@@ -0,0 +1,43 @@
+<?php
+// @codingStandardsIgnoreFile
+
+use Drupal\Core\Database\Database;
+
+$connection = Database::getConnection();
+$db_type = $connection->databaseType();
+
+// Creates a table, then adds a sequence without ownership to simulate tables
+// that were altered from integer to serial columns.
+$connection
+  ->schema()
+  ->createTable('pgsql_sequence_test', [
+    'fields' => [
+      'sequence_field' => [
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+      ],
+    ],
+    'primary key' => ['sequence_field'],
+  ]);
+$seq = $connection
+  ->makeSequenceName('pgsql_sequence_test', 'sequence_field');
+$connection->query('CREATE SEQUENCE ' . $seq);
+
+// Enables the pgsql_test module so that the pgsql_sequence_test schema will
+// be available.
+$extensions = $connection
+  ->query("SELECT data FROM {config} where name = 'core.extension'")
+  ->fetchField();
+$extensions = unserialize($extensions);
+$extensions['module']['pgsql_test'] = 1;
+
+$connection
+  ->update('config')
+  ->fields(['data' => serialize($extensions)])
+  ->condition('name', 'core.extension')
+  ->execute();
+$connection
+  ->delete('cache_config')
+  ->condition('cid', 'core.extension')
+  ->execute();
diff --git a/core/modules/pgsql/tests/src/Functional/Database/PostgreSqlSequenceUpdateTest.php b/core/modules/pgsql/tests/src/Functional/Database/PostgreSqlSequenceUpdateTest.php
new file mode 100644
index 00000000000..046814e2498
--- /dev/null
+++ b/core/modules/pgsql/tests/src/Functional/Database/PostgreSqlSequenceUpdateTest.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Drupal\Tests\pgsql\Functional\Database;
+
+use Drupal\FunctionalTests\Update\UpdatePathTestBase;
+use Drupal\Core\Database\Database;
+
+/**
+ * Tests that any unowned sequences created previously have a table owner.
+ *
+ * The update path only applies to Drupal sites using the pgsql driver.
+ *
+ * @group Database
+ */
+class PostgreSqlSequenceUpdateTest extends UpdatePathTestBase {
+
+  /**
+   * The database connection to use.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $connection;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function runDbTasks() {
+    parent::runDbTasks();
+    $this->connection = Database::getConnection();
+    if ($this->connection->driver() !== 'pgsql') {
+      $this->markTestSkipped('This test only works with the pgsql driver');
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setDatabaseDumpFiles() {
+    $this->databaseDumpFiles = [
+      __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-9.4.0.bare.standard.php.gz',
+      __DIR__ . '/../../../fixtures/update/drupal-9.pgsql-orphan-sequence.php',
+    ];
+  }
+
+  /**
+   * Asserts that a newly created sequence has the correct ownership.
+   */
+  public function testPostgreSqlSequenceUpdate() {
+    $this->assertFalse(_getSequenceOwner('pgsql_sequence_test', 'sequence_field'));
+
+    // Run the updates.
+    $this->runUpdates();
+
+    $seq_owner = _getSequenceOwner('pgsql_sequence_test', 'sequence_field');
+    $this->assertEquals($this->connection->getPrefix() . 'pgsql_sequence_test', $seq_owner->table_name);
+    $this->assertEquals('sequence_field', $seq_owner->field_name, 'Sequence is owned by the table and column.');
+  }
+
+}
diff --git a/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php b/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php
index b7fee8694a8..89b369a5267 100644
--- a/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php
+++ b/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php
@@ -243,4 +243,49 @@ public function testPgsqlExtensionExists(): void {
     $this->assertTrue($this->schema->extensionExists('pg_trgm'));
   }
 
+  /**
+   * @covers \Drupal\Core\Database\Driver\pgsql\Schema::getSequenceOwner
+   * @covers \Drupal\Core\Database\Driver\pgsql\Schema::getSequenceName
+   * @covers \Drupal\Core\Database\Driver\pgsql\Schema::sequenceExists
+   */
+  public function testPgsqlSequences(): void {
+    $table_specification = [
+      'description' => 'A test table with an ANSI reserved keywords for naming.',
+      'fields' => [
+        'uid' => [
+          'description' => 'Simple unique ID.',
+          'type' => 'serial',
+          'not null' => TRUE,
+        ],
+        'update' => [
+          'description' => 'A column with reserved name.',
+          'type' => 'varchar',
+          'length' => 255,
+        ],
+      ],
+      'primary key' => ['uid'],
+      'unique keys' => [
+        'having' => ['update'],
+      ],
+      'indexes' => [
+        'in' => ['uid', 'update'],
+      ],
+    ];
+
+    // Creating a table.
+    $table_name = 'sequence_test';
+    $this->schema->createTable($table_name, $table_specification);
+    $this->assertTrue($this->schema->tableExists($table_name));
+
+    // @todo replace 'public.' with defaultSchema when issue https://www.drupal.org/i/1060476 lands.
+    $this->assertEquals('public.' . $this->connection->getPrefix() . 'sequence_test_uid_seq', _getSequenceName('sequence_test', 'uid'));
+
+    $this->assertTrue(_sequenceExists($this->connection->getPrefix() . 'sequence_test_uid_seq'));
+
+    $seq_owner = _getSequenceOwner('sequence_test', 'uid');
+    $this->assertEquals($this->connection->getPrefix() . 'sequence_test', $seq_owner->table_name);
+    $this->assertEquals('uid', $seq_owner->field_name, 'New sequence is owned by its table.');
+
+  }
+
 }
diff --git a/core/modules/system/tests/modules/pgsql_test/pgsql_test.info.yml b/core/modules/system/tests/modules/pgsql_test/pgsql_test.info.yml
new file mode 100644
index 00000000000..d31c1cbc27e
--- /dev/null
+++ b/core/modules/system/tests/modules/pgsql_test/pgsql_test.info.yml
@@ -0,0 +1,5 @@
+name: 'PostgreSQL Tests'
+type: module
+description: 'Supports testing pgsql driver.'
+package: Testing
+version: VERSION
diff --git a/core/modules/system/tests/modules/pgsql_test/pgsql_test.install b/core/modules/system/tests/modules/pgsql_test/pgsql_test.install
new file mode 100644
index 00000000000..e9f1577e09d
--- /dev/null
+++ b/core/modules/system/tests/modules/pgsql_test/pgsql_test.install
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the pgsql_test module.
+ */
+
+/**
+ * Implements hook_schema().
+ */
+function pgsql_test_schema() {
+  $schema['pgsql_sequence_test'] = [
+    'description' => 'Test sequence changes on pgsql driver.',
+    'fields' => [
+      'sequence_field' => [
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => 'Primary Key: A serial integer field.',
+      ],
+    ],
+    'primary key' => ['sequence_field'],
+  ];
+  return $schema;
+}
diff --git a/core/phpstan-baseline.neon b/core/phpstan-baseline.neon
index 741fa2d7ac9..ad3c33d89b9 100644
--- a/core/phpstan-baseline.neon
+++ b/core/phpstan-baseline.neon
@@ -1960,6 +1960,11 @@ parameters:
 			count: 1
 			path: modules/path_alias/src/AliasManager.php
 
+		-
+			message: "#^A file could not be loaded from Drupal\\\\Core\\\\Extension\\\\ModuleHandlerInterface\\:\\:loadInclude$#"
+			count: 1
+			path: modules/pgsql/pgsql.install
+
 		-
 			message: "#^Variable \\$table_field might not be defined\\.$#"
 			count: 1
