diff --git a/core/modules/pgsql/pgsql.install b/core/modules/pgsql/pgsql.install
index c25620d7895..dde8bef0c6c 100644
--- a/core/modules/pgsql/pgsql.install
+++ b/core/modules/pgsql/pgsql.install
@@ -6,6 +6,7 @@
  */
 
 use Drupal\Core\Database\Database;
+use Drupal\pgsql\Update10101;
 
 /**
  * Implements hook_requirements().
@@ -40,3 +41,10 @@ function pgsql_requirements() {
 
   return $requirements;
 }
+
+/**
+ * Update sequences' owner created from serial columns in PostgreSQL.
+ */
+function pgsql_update_10101(&$sandbox) {
+  \Drupal::classResolver(Update10101::class)->update($sandbox);
+}
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/src/Update10101.php b/core/modules/pgsql/src/Update10101.php
new file mode 100644
index 00000000000..a9e14cc64e5
--- /dev/null
+++ b/core/modules/pgsql/src/Update10101.php
@@ -0,0 +1,235 @@
+<?php
+
+namespace Drupal\pgsql;
+
+use Drupal\Core\Database\DatabaseExceptionWrapper;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+// cSpell:ignore relkind objid refobjid regclass attname attrelid attnum
+// cSpell:ignore refobjsubid
+
+/**
+ * An update class for sequence ownership.
+ * @see https://www.drupal.org/i/3028706
+ *
+ * @internal
+ */
+class Update10101 implements ContainerInjectionInterface {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Sequence owner update constructor.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static($container->get('entity_type.manager'));
+  }
+
+  /**
+   * Update *all* existing sequences to include the owner tables.
+   *
+   * @param array $sandbox
+   *   Stores information for batch updates.
+   */
+  public function update(array &$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 = $this->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) $this->pgsql_get_sequence_name($table_info['table'], $table_info['column']);
+
+          if (!$owned) {
+            $sequence_name = $connection
+              ->makeSequenceName($table_info['table'], $table_info['column']);
+            $exists = $this->pgsql_sequence_exists($sequence_name);
+            if ($exists) {
+              $transaction = $connection->startTransaction($sequence_name);
+              try {
+                $this->pgsql_update_sequence_ownership($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');
+    }
+  }
+
+  /**
+   * 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
+   */
+  private function pgsql_update_sequence_ownership(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.
+   */
+  private function pgsql_get_sequence_name(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()
+   */
+  private function pgsql_sequence_exists(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.
+   */
+  public function pgsql_get_sequence_owner(string $table, string $field): object|bool {
+    $seq_name = $this->pgsql_get_sequence_name($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/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..da929394a37
--- /dev/null
+++ b/core/modules/pgsql/tests/src/Functional/Database/PostgreSqlSequenceUpdateTest.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Drupal\Tests\pgsql\Functional\Database;
+
+use Drupal\FunctionalTests\Update\UpdatePathTestBase;
+use Drupal\Core\Database\Database;
+use Drupal\pgsql\Update10101;
+
+/**
+ * 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() {
+    $update_sequence = \Drupal::classResolver(Update10101::class);
+    $this->assertFalse($update_sequence->pgsql_get_sequence_owner('pgsql_sequence_test', 'sequence_field'));
+
+    // Run the updates.
+    $this->runUpdates();
+
+    $seq_owner = $update_sequence->pgsql_get_sequence_owner('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..cb1293fc2e6 100644
--- a/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php
+++ b/core/modules/pgsql/tests/src/Kernel/pgsql/SchemaTest.php
@@ -4,6 +4,9 @@
 
 use Drupal\KernelTests\Core\Database\DriverSpecificSchemaTestBase;
 
+// cSpell:ignore relkind objid refobjid regclass attname attrelid attnum
+// cSpell:ignore refobjsubid
+
 /**
  * Tests schema API for the PostgreSQL driver.
  *
@@ -243,4 +246,69 @@ 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));
+
+    // Retrieves a sequence name that is owned by the table and column.
+    $sequence_name = $this->connection
+      ->query("SELECT pg_get_serial_sequence(:table, :column)", [
+        ':table' => $this->connection->getPrefix() . 'sequence_test',
+        ':column' => 'uid',
+      ])
+      ->fetchField();
+
+    // @todo replace 'public.' with defaultSchema when issue https://www.drupal.org/i/1060476 lands.
+    $this->assertEquals('public.' . $this->connection->getPrefix() . 'sequence_test_uid_seq', $sequence_name);
+
+    // Checks if the sequence exists.
+    $this->assertTrue((bool) \Drupal::database()
+      ->query("SELECT c.relname FROM pg_class as c WHERE c.relkind = 'S' AND c.relname = :name", [
+        ':name' => $this->connection->getPrefix() . 'sequence_test_uid_seq',
+      ])
+      ->fetchField());
+
+    // Retrieves the sequence owner object.
+    $sequence_owner = \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' => $sequence_name])->fetchObject();
+
+    $this->assertEquals($this->connection->getPrefix() . 'sequence_test', $sequence_owner->table_name);
+    $this->assertEquals('uid', $sequence_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 4b0b775af4c..1913bef3003 100644
--- a/core/phpstan-baseline.neon
+++ b/core/phpstan-baseline.neon
@@ -1970,6 +1970,11 @@ parameters:
 			count: 1
 			path: modules/pgsql/src/Driver/Database/pgsql/Upsert.php
 
+		-
+			message: "#^A file could not be loaded from Drupal\\\\Core\\\\Extension\\\\ModuleHandlerInterface\\:\\:loadInclude$#"
+			count: 1
+			path: modules/pgsql/src/Update10101.php
+
 		-
 			message: "#^Variable \\$responsive_image_styles in empty\\(\\) always exists and is not falsy\\.$#"
 			count: 1
