Optimize AbstractDatabaseObjectAction::update() for bulk processing
authorMatthias Schmidt <gravatronics@live.com>
Sun, 23 Jun 2019 07:51:23 +0000 (09:51 +0200)
committerMatthias Schmidt <gravatronics@live.com>
Sun, 23 Jun 2019 12:26:51 +0000 (14:26 +0200)
wcfsetup/install/files/lib/data/AbstractDatabaseObjectAction.class.php

index 49d25159a34debb29f4ef3d53790415f7742c14f..b3ad8863a08457689231867b49621339f980c7d2 100644 (file)
@@ -362,16 +362,56 @@ abstract class AbstractDatabaseObjectAction implements IDatabaseObjectAction, ID
                        $this->readObjects();
                }
                
-               if (isset($this->parameters['data'])) {
-                       foreach ($this->getObjects() as $object) {
-                               $object->update($this->parameters['data']);
+               $tableName = call_user_func([$this->className, 'getDatabaseTableName']);
+               $indexName = call_user_func([$this->className, 'getDatabaseTableIndexName']);
+               
+               // instead of executing one query per object id, execute queries
+               // for batches of up to 1000 object ids at once
+               $itemsPerLoop = 1000;
+               $batchCount = ceil(count($this->objectIDs) / $itemsPerLoop);
+               
+               if (!empty($this->parameters['data'])) {
+                       $updateSQL = '';
+                       $statementParameters = [];
+                       foreach ($this->parameters['data'] as $key => $value) {
+                               if (!empty($updateSQL)) $updateSQL .= ', ';
+                               $updateSQL .= $key . ' = ?';
+                               $statementParameters[] = $value;
+                       }
+                       
+                       WCF::getDB()->beginTransaction();
+                       for ($i = 0; $i < $batchCount; $i++) {
+                               $batchObjectIDs = array_slice($this->objectIDs, $i * $itemsPerLoop, $itemsPerLoop);
+                               
+                               $sql = "UPDATE  " . $tableName . "
+                                       SET     " . $updateSQL . "
+                                       WHERE   " . $indexName . " IN (?" . str_repeat(', ?', count($batchObjectIDs) - 1) . ")";
+                               $statement = WCF::getDB()->prepareStatement($sql);
+                               $statement->execute(array_merge($statementParameters, $batchObjectIDs));
                        }
+                       WCF::getDB()->commitTransaction();
                }
                
-               if (isset($this->parameters['counters'])) {
-                       foreach ($this->getObjects() as $object) {
-                               $object->updateCounters($this->parameters['counters']);
+               if (!empty($this->parameters['counters'])) {
+                       $updateSQL = '';
+                       $statementParameters = [];
+                       foreach ($this->parameters['counters'] as $key => $value) {
+                               if (!empty($updateSQL)) $updateSQL .= ', ';
+                               $updateSQL .= $key . ' = ' . $key . ' + ?';
+                               $statementParameters[] = $value;
+                       }
+                       
+                       WCF::getDB()->beginTransaction();
+                       for ($i = 0; $i < $batchCount; $i++) {
+                               $batchObjectIDs = array_slice($this->objectIDs, $i * $itemsPerLoop, $itemsPerLoop);
+                               
+                               $sql = "UPDATE  " . $tableName . "
+                                       SET     " . $updateSQL . "
+                                       WHERE   " . $indexName . " IN (?" . str_repeat(', ?', count($batchObjectIDs) - 1) . ")";
+                               $statement = WCF::getDB()->prepareStatement($sql);
+                               $statement->execute(array_merge($statementParameters, $batchObjectIDs));
                        }
+                       WCF::getDB()->commitTransaction();
                }
        }