diff --git a/vendor/magento/module-catalog/Model/Indexer/Category/Product.php b/vendor/magento/module-catalog/Model/Indexer/Category/Product.php
index f5a8c33cfa6..89a6e6d3c25 100644
--- a/vendor/magento/module-catalog/Model/Indexer/Category/Product.php
+++ b/vendor/magento/module-catalog/Model/Indexer/Category/Product.php
@@ -5,7 +5,10 @@
  */
 namespace Magento\Catalog\Model\Indexer\Category;

+use Magento\Framework\App\ObjectManager;
 use Magento\Framework\Indexer\CacheContext;
+use Magento\Framework\Indexer\IndexMutexException;
+use Magento\Framework\Indexer\IndexMutexInterface;

 /**
  * Category product indexer
@@ -41,19 +44,27 @@ class Product implements \Magento\Framework\Indexer\ActionInterface, \Magento\Fr
      */
     protected $cacheContext;

+    /**
+     * @var IndexMutexInterface
+     */
+    private $indexMutex;
+
     /**
      * @param Product\Action\FullFactory $fullActionFactory
      * @param Product\Action\RowsFactory $rowsActionFactory
      * @param \Magento\Framework\Indexer\IndexerRegistry $indexerRegistry
+     * @param IndexMutexInterface|null $indexMutex
      */
     public function __construct(
         Product\Action\FullFactory $fullActionFactory,
         Product\Action\RowsFactory $rowsActionFactory,
-        \Magento\Framework\Indexer\IndexerRegistry $indexerRegistry
+        \Magento\Framework\Indexer\IndexerRegistry $indexerRegistry,
+        ?IndexMutexInterface $indexMutex = null
     ) {
         $this->fullActionFactory = $fullActionFactory;
         $this->rowsActionFactory = $rowsActionFactory;
         $this->indexerRegistry = $indexerRegistry;
+        $this->indexMutex = $indexMutex ?? ObjectManager::getInstance()->get(IndexMutexInterface::class);
     }

     /**
@@ -61,6 +72,7 @@ class Product implements \Magento\Framework\Indexer\ActionInterface, \Magento\Fr
      *
      * @param int[] $ids
      * @return void
+     * @throws IndexMutexException
      */
     public function execute($ids)
     {
@@ -84,11 +96,17 @@ class Product implements \Magento\Framework\Indexer\ActionInterface, \Magento\Fr
      * Execute full indexation
      *
      * @return void
+     * @throws IndexMutexException
      */
     public function executeFull()
     {
-        $this->fullActionFactory->create()->execute();
-        $this->registerTags();
+        $this->indexMutex->execute(
+            static::INDEXER_ID,
+            function () {
+                $this->fullActionFactory->create()->execute();
+                $this->registerTags();
+            }
+        );
     }

     /**
@@ -107,6 +125,7 @@ class Product implements \Magento\Framework\Indexer\ActionInterface, \Magento\Fr
      *
      * @param int[] $ids
      * @return void
+     * @throws IndexMutexException
      */
     public function executeList(array $ids)
     {
@@ -118,6 +137,7 @@ class Product implements \Magento\Framework\Indexer\ActionInterface, \Magento\Fr
      *
      * @param int $id
      * @return void
+     * @throws IndexMutexException
      */
     public function executeRow($id)
     {
@@ -129,18 +149,22 @@ class Product implements \Magento\Framework\Indexer\ActionInterface, \Magento\Fr
      *
      * @param int[] $ids
      * @return $this
+     * @throws IndexMutexException
      */
     protected function executeAction($ids)
     {
         $ids = array_unique($ids);
         $indexer = $this->indexerRegistry->get(static::INDEXER_ID);

-        /** @var Product\Action\Rows $action */
-        $action = $this->rowsActionFactory->create();
         if ($indexer->isScheduled()) {
-            $action->execute($ids, true);
+            $this->indexMutex->execute(
+                static::INDEXER_ID,
+                function () use ($ids) {
+                    $this->rowsActionFactory->create()->execute($ids, true);
+                }
+            );
         } else {
-            $action->execute($ids);
+            $this->rowsActionFactory->create()->execute($ids);
         }

         return $this;
diff --git a/vendor/magento/module-catalog/Model/Indexer/Product/Category.php b/vendor/magento/module-catalog/Model/Indexer/Product/Category.php
index e50b16837ee..b5ecd1076f9 100644
--- a/vendor/magento/module-catalog/Model/Indexer/Product/Category.php
+++ b/vendor/magento/module-catalog/Model/Indexer/Product/Category.php
@@ -5,7 +5,11 @@
  */
 namespace Magento\Catalog\Model\Indexer\Product;

+use Magento\Framework\Indexer\IndexMutexInterface;
+
 /**
+ * Product Categories indexer
+ *
  * @api
  * @since 100.0.2
  */
@@ -20,13 +24,17 @@ class Category extends \Magento\Catalog\Model\Indexer\Category\Product
      * @param \Magento\Catalog\Model\Indexer\Category\Product\Action\FullFactory $fullActionFactory
      * @param Category\Action\RowsFactory $rowsActionFactory
      * @param \Magento\Framework\Indexer\IndexerRegistry $indexerRegistry
+     * @param IndexMutexInterface|null $indexMutex
+     * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod.Found
      */
     public function __construct(
         \Magento\Catalog\Model\Indexer\Category\Product\Action\FullFactory $fullActionFactory,
         Category\Action\RowsFactory $rowsActionFactory,
-        \Magento\Framework\Indexer\IndexerRegistry $indexerRegistry
+        \Magento\Framework\Indexer\IndexerRegistry $indexerRegistry,
+        ?IndexMutexInterface $indexMutex = null
     ) {
-        parent::__construct($fullActionFactory, $rowsActionFactory, $indexerRegistry);
+        //phpcs:enable
+        parent::__construct($fullActionFactory, $rowsActionFactory, $indexerRegistry, $indexMutex);
     }

     /**
diff --git a/vendor/magento/module-indexer/Model/IndexMutex.php b/vendor/magento/module-indexer/Model/IndexMutex.php
new file mode 100644
index 00000000000..fd0cf3dc480
--- /dev/null
+++ b/vendor/magento/module-indexer/Model/IndexMutex.php
@@ -0,0 +1,75 @@
+<?php
+/**
+ * Copyright Â© Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+declare(strict_types=1);
+
+namespace Magento\Indexer\Model;
+
+use Magento\Framework\Indexer\ConfigInterface;
+use Magento\Framework\Indexer\IndexMutexException;
+use Magento\Framework\Indexer\IndexMutexInterface;
+use Magento\Framework\Lock\LockManagerInterface;
+
+/**
+ * Intended to prevent race conditions between indexers using the same index table.
+ */
+class IndexMutex implements IndexMutexInterface
+{
+    private const LOCK_PREFIX = 'indexer_lock_';
+
+    private const LOCK_TIMEOUT = 60;
+
+    /**
+     * @var LockManagerInterface
+     */
+    private $lockManager;
+
+    /**
+     * @var ConfigInterface
+     */
+    private $config;
+
+    /**
+     * @var int
+     */
+    private $lockWaitTimeout;
+
+    /**
+     * @param LockManagerInterface $lockManager
+     * @param ConfigInterface $config
+     * @param int $lockWaitTimeout
+     */
+    public function __construct(
+        LockManagerInterface $lockManager,
+        ConfigInterface $config,
+        int $lockWaitTimeout = self::LOCK_TIMEOUT
+    ) {
+        $this->lockManager = $lockManager;
+        $this->lockWaitTimeout = $lockWaitTimeout;
+        $this->config = $config;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function execute(string $indexerName, callable $callback): void
+    {
+        $lockName = $indexerName;
+        $indexerConfig = $this->config->getIndexer($indexerName);
+        if (isset($indexerConfig['shared_index'])) {
+            $lockName = $indexerConfig['shared_index'];
+        }
+
+        if ($this->lockManager->lock(self::LOCK_PREFIX . $lockName, $this->lockWaitTimeout)) {
+            try {
+                $callback();
+            } finally {
+                $this->lockManager->unlock(self::LOCK_PREFIX . $lockName);
+            }
+        } else {
+            throw new IndexMutexException($indexerName);
+        }
+    }
+}
diff --git a/vendor/magento/module-indexer/etc/di.xml b/vendor/magento/module-indexer/etc/di.xml
index 9496f29cb1d..d7996ee68a7 100644
--- a/vendor/magento/module-indexer/etc/di.xml
+++ b/vendor/magento/module-indexer/etc/di.xml
@@ -12,6 +12,7 @@
     <preference for="Magento\Framework\Indexer\IndexerInterface" type="Magento\Indexer\Model\Indexer\DependencyDecorator" />
     <preference for="Magento\Framework\Indexer\Table\StrategyInterface" type="Magento\Framework\Indexer\Table\Strategy" />
     <preference for="Magento\Framework\Indexer\StateInterface" type="Magento\Indexer\Model\Indexer\State" />
+    <preference for="Magento\Framework\Indexer\IndexMutexInterface" type="Magento\Indexer\Model\IndexMutex" />
     <type name="Magento\Framework\Indexer\Table\StrategyInterface" shared="false" />
     <type name="Magento\Indexer\Model\Indexer">
         <arguments>
diff --git a/vendor/magento/framework/Indexer/IndexMutexException.php b/vendor/magento/framework/Indexer/IndexMutexException.php
new file mode 100644
index 00000000000..5148bb2bafd
--- /dev/null
+++ b/vendor/magento/framework/Indexer/IndexMutexException.php
@@ -0,0 +1,24 @@
+<?php
+/**
+ * Copyright Â© Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+declare(strict_types=1);
+
+namespace Magento\Framework\Indexer;
+
+use RuntimeException;
+
+/**
+ * Exception thrown when index lock could not be acquired
+ */
+class IndexMutexException extends RuntimeException
+{
+    /**
+     * @param string $indexerName
+     */
+    public function __construct(string $indexerName)
+    {
+        parent::__construct('Could not acquire lock for index: ' . $indexerName);
+    }
+}
diff --git a/vendor/magento/framework/Indexer/IndexMutexInterface.php b/vendor/magento/framework/Indexer/IndexMutexInterface.php
new file mode 100644
index 00000000000..25642ea1d4a
--- /dev/null
+++ b/vendor/magento/framework/Indexer/IndexMutexInterface.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * Copyright Â© Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+declare(strict_types=1);
+
+namespace Magento\Framework\Indexer;
+
+/**
+ * Intended to prevent race conditions between indexers using the same index table.
+ */
+interface IndexMutexInterface
+{
+    /**
+     * Acquires a lock for an indexer, executes callable and releases the lock after.
+     *
+     * @param string $indexerName
+     * @param callable $callback
+     * @throws IndexMutexException
+     */
+    public function execute(string $indexerName, callable $callback): void;
+}
