diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
index 01bf60e..a6ee9f1 100644
--- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
@@ -739,6 +739,92 @@ protected function buildQuery($ids, $revision_ids = FALSE) {
   }
 
   /**
+   * Database lock ID suffix.
+   */
+  protected const LOCK_ID_SUFFIX = 'content_entity_operations';
+
+  /**
+   * Database lock wait timeout.
+   */
+  protected const LOCK_TIMEOUT = 30;
+
+  /**
+   * As locking in MySQL is server-wide, we must provide a db-specific prefix.
+   *
+   * @return string
+   *   Database-specific prefix.
+   */
+  protected function databaseLockPrefix() : string {
+    $prefix = &drupal_static(__METHOD__);
+
+    if (!isset($prefix)) {
+      $databaseName = $this->database->getConnectionOptions()['database'] ?? '';
+      $prefix = dechex(crc32($databaseName)) . ':';
+    }
+
+    return $prefix;
+  }
+
+  /**
+   * Returns a database lock ID for locking content entity operations.
+   *
+   * @return string
+   *   Database lock ID.
+   */
+  protected function getDatabaseLockId() : string {
+    return $this->databaseLockPrefix() . self::LOCK_ID_SUFFIX;
+  }
+
+  /**
+   * Locks the database for content entity operations.
+   *
+   * @throws \Drupal\Core\Entity\EntityStorageException
+   */
+  protected function lockDatabase() {
+    $lockQueryResults = $this->database->query(
+      'SELECT GET_LOCK(:lock_id, :lock_timeout) AS lock_status',
+      [
+        ':lock_id' => $this->getDatabaseLockId(),
+        ':lock_timeout' => self::LOCK_TIMEOUT,
+      ]
+    );
+    $lockStatus = $lockQueryResults->fetchCol()[0] ?? FALSE;
+    if ($lockStatus === '1') {
+      // Everything went fine.
+      return;
+    }
+    if ($lockStatus === '0') {
+      // Lock timeout.
+      throw new EntityStorageException('Failed to get lock: timeout');
+    }
+    throw new EntityStorageException("Failed to get lock: server failure");
+  }
+
+  /**
+   * Releases the database lock for content entity operations.
+   *
+   * @throws \Drupal\Core\Entity\EntityStorageException
+   */
+  protected function releaseDatabaseLock() {
+    $lockQueryResults = $this->database->query(
+      'SELECT RELEASE_LOCK(:lock_id) AS lock_status',
+      [
+        ':lock_id' => $this->getDatabaseLockId(),
+      ]
+    );
+    $lockStatus = $lockQueryResults->fetchCol()[0] ?? FALSE;
+    if ($lockStatus === '1') {
+      // Everything went fine.
+      return;
+    }
+    if ($lockStatus === '0') {
+      // Invalid lock ownership.
+      throw new EntityStorageException('Failed to release lock: invalid ownership');
+    }
+    throw new EntityStorageException("Failed to release lock: server failure");
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function delete(array $entities) {
@@ -747,18 +833,22 @@ public function delete(array $entities) {
       return;
     }
 
+    $this->lockDatabase();
     try {
       $transaction = $this->database->startTransaction();
       parent::delete($entities);
 
       // Ignore replica server temporarily.
       \Drupal::service('database.replica_kill_switch')->trigger();
+
+      $this->releaseDatabaseLock();
     }
     catch (\Exception $e) {
-      if (isset($transaction)) {
+      if ($transaction) {
         $transaction->rollBack();
       }
       watchdog_exception($this->entityTypeId, $e);
+      $this->releaseDatabaseLock();
       throw new EntityStorageException($e->getMessage(), $e->getCode(), $e);
     }
   }
@@ -800,19 +890,24 @@ protected function doDeleteFieldItems($entities) {
    * {@inheritdoc}
    */
   public function save(EntityInterface $entity) {
+    $this->lockDatabase();
     try {
       $transaction = $this->database->startTransaction();
       $return = parent::save($entity);
 
       // Ignore replica server temporarily.
       \Drupal::service('database.replica_kill_switch')->trigger();
+
+      $this->releaseDatabaseLock();
+
       return $return;
     }
     catch (\Exception $e) {
-      if (isset($transaction)) {
+      if ($transaction) {
         $transaction->rollBack();
       }
       watchdog_exception($this->entityTypeId, $e);
+      $this->releaseDatabaseLock();
       throw new EntityStorageException($e->getMessage(), $e->getCode(), $e);
     }
   }
@@ -821,6 +916,7 @@ public function save(EntityInterface $entity) {
    * {@inheritdoc}
    */
   public function restore(EntityInterface $entity) {
+    $this->lockDatabase();
     try {
       $transaction = $this->database->startTransaction();
       // Insert the entity data in the base and data tables only for default
@@ -856,12 +952,15 @@ public function restore(EntityInterface $entity) {
 
       // Ignore replica server temporarily.
       \Drupal::service('database.replica_kill_switch')->trigger();
+
+      $this->releaseDatabaseLock();
     }
     catch (\Exception $e) {
-      if (isset($transaction)) {
+      if ($transaction) {
         $transaction->rollBack();
       }
       watchdog_exception($this->entityTypeId, $e);
+      $this->releaseDatabaseLock();
       throw new EntityStorageException($e->getMessage(), $e->getCode(), $e);
     }
   }
