perf: оптимизировано массовое удаление строк
- Добавлен batch delete метод на бэкенде (удаление множества строк за один запрос) - Использование WHERE IN для удаления нескольких строк одним SQL запросом - Добавлен прогресс-бар при удалении большого количества строк - Удаление 1000 строк теперь занимает секунды вместо минут - Добавлена поддержка транзакций для атомарности операций - Оптимизирован размер батчей для баланса производительности и надежности
This commit is contained in:
@@ -435,6 +435,115 @@ class DataService
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Массовое удаление строк (оптимизированное)
|
||||
*/
|
||||
public function deleteMultipleRows(string $schema, string $table, array $rows, array $pk): array
|
||||
{
|
||||
if (empty($rows)) {
|
||||
return ['deleted' => 0, 'errors' => 0, 'message' => 'No rows provided'];
|
||||
}
|
||||
|
||||
if (empty($pk)) {
|
||||
throw new \RuntimeException('No primary key — delete disabled');
|
||||
}
|
||||
|
||||
error_log("=== МАССОВОЕ УДАЛЕНИЕ: Schema=$schema, Table=$table, Строк=" . count($rows) . " ===");
|
||||
|
||||
$deleted = 0;
|
||||
$errors = 0;
|
||||
$errorMessages = [];
|
||||
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
try {
|
||||
// Если PK состоит из одного поля - используем WHERE IN (самый быстрый способ)
|
||||
if (count($pk) === 1) {
|
||||
$pkField = $pk[0];
|
||||
|
||||
// Группируем по батчам (по 500 строк) для избежания превышения лимитов MySQL
|
||||
$batchSize = 500;
|
||||
$batches = array_chunk($rows, $batchSize);
|
||||
|
||||
foreach ($batches as $batchIndex => $batch) {
|
||||
$pkValues = [];
|
||||
|
||||
foreach ($batch as $row) {
|
||||
if (!array_key_exists($pkField, $row)) {
|
||||
$errors++;
|
||||
$errorMessages[] = "Строка не содержит PK поле: $pkField";
|
||||
continue;
|
||||
}
|
||||
$pkValues[] = $row[$pkField];
|
||||
}
|
||||
|
||||
if (empty($pkValues)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Удаляем батч за один запрос
|
||||
$placeholders = implode(',', array_fill(0, count($pkValues), '?'));
|
||||
$sql = "DELETE FROM `{$schema}`.`{$table}` WHERE `{$pkField}` IN ($placeholders)";
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($pkValues);
|
||||
|
||||
$deletedInBatch = $stmt->rowCount();
|
||||
$deleted += $deletedInBatch;
|
||||
|
||||
error_log("Батч " . ($batchIndex + 1) . ": удалено $deletedInBatch строк");
|
||||
}
|
||||
} else {
|
||||
// Составной PK - удаляем по одной строке (но в одной транзакции)
|
||||
foreach ($rows as $index => $row) {
|
||||
try {
|
||||
$whereParts = [];
|
||||
$params = [];
|
||||
|
||||
foreach ($pk as $name) {
|
||||
if (!array_key_exists($name, $row)) {
|
||||
throw new \RuntimeException("Missing PK value: $name");
|
||||
}
|
||||
$whereParts[] = "`$name` = :pk_$name";
|
||||
$params[":pk_$name"] = $row[$name];
|
||||
}
|
||||
|
||||
$sql = sprintf(
|
||||
"DELETE FROM `%s`.`%s` WHERE %s",
|
||||
$schema, $table,
|
||||
implode(' AND ', $whereParts)
|
||||
);
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$deleted += $stmt->rowCount();
|
||||
} catch (\PDOException $e) {
|
||||
$errors++;
|
||||
$errorMessages[] = "Строка " . ($index + 1) . ": " . $e->getMessage();
|
||||
|
||||
if ($errors <= 10) {
|
||||
error_log("Ошибка удаления строки " . ($index + 1) . ": " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->pdo->commit();
|
||||
error_log("=== УДАЛЕНИЕ ЗАВЕРШЕНО: удалено=$deleted, ошибок=$errors ===");
|
||||
} catch (\Exception $e) {
|
||||
$this->pdo->rollBack();
|
||||
error_log("=== КРИТИЧЕСКАЯ ОШИБКА УДАЛЕНИЯ: " . $e->getMessage() . " ===");
|
||||
throw new \RuntimeException('Delete failed: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
return [
|
||||
'deleted' => $deleted,
|
||||
'errors' => $errors,
|
||||
'errorMessages' => $errorMessages
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
private function arrayToCSV(array $headers, array $rows): string
|
||||
{
|
||||
$output = fopen('php://temp', 'r+');
|
||||
|
||||
Reference in New Issue
Block a user