Delphi ClientDataSet - операция удаления - почему она такая медленная?

Я использую TClientDataSet в приложении для управления загрузкой данных, импортированных из нескольких файлов CSV. Всего это может быть миллион или более записей. Я хочу иметь возможность удалить все записи набора данных, связанные с конкретным CSV-файлом, но время на удаление большого количества элементов очень медленное.

В качестве теста, чтобы попытаться разобраться, делаю ли я что-нибудь глупое, я создал простое консольное приложение. Все, что он делает, это:

  • Создайте экземпляр TClientDataSet с 1 определенным полем (ID):

    CDS := TClientDataSet.Create(nil); CDS.FieldDefs.Add('ID', ftInteger); CDS.CreateDataSet; CDS.LogChanges := False;

  • Добавить 100000 элементов (занимает 0,1 секунды):

    for i := 1 to 100000 do begin CDS.AppendRecord([i]); end;

  • Удалить 50000 элементов (занимает ~ 4 секунды или ~ 4,4 секунды с LogChanges=TRUE):

    CDS.First; while CDS['ID'] <= 50000 do CDS.Delete;

Если бы в моем наборе данных было 1,5 миллиона элементов, и я хотел бы удалить 0,5 миллиона записей, то удаление элементов этим методом заняло бы так много времени, что я даже не смог бы его измерить.

В качестве временного решения мне нужно создать новый набор данных, затем скопировать все элементы, которые я хочу сохранить, в новую копию и удалить исходную копию. Если я не удаляю только небольшой% записей из исходного набора данных, этот метод работает намного быстрее.

Возможно, я не использую наиболее подходящий метод для удаления элементов из набора данных? Я предполагаю, что это вызывает кучу внутренней обработки с каждым удаленным элементом. Есть ли способ удалить сразу несколько элементов, которые мне не хватает? Возможно, я могу установить индекс и диапазон на основе этого индекса, а затем удалить все элементы в текущем диапазоне с помощью одной операции?

Может проблема в ClientDataSet, а не во мне? Возможно, мне нужно использовать другой компонент. Какие-либо предложения?


person Phil B    schedule 14.08.2016    source источник
comment
Возможно, вы забыли выключить LogChanges.   -  person Ondrej Kelle    schedule 14.08.2016
comment
Интересно, что разница должна быть, ведь поддержание дельты стоит недешево.   -  person Ondrej Kelle    schedule 14.08.2016
comment
Как вы упомянули агрегаты и индексы, они могут быть виноваты; попробуйте отключить их перед операцией массового удаления и повторно включить (при необходимости) после. (Проблема может заключаться в том, что они обновляются после каждого удаления, в котором нет необходимости.)   -  person Ondrej Kelle    schedule 14.08.2016
comment
Приложение тестовой консоли, которое я использовал, не делает ничего, кроме указанного выше кода, за исключением измерения скорости, поэтому никакие индексы не определены, кроме индекса или агрегатов по умолчанию. В документации по внедрению они заявляют, что определенные индексы не должны иметь никакого влияния, пока они все равно не будут применены. [Я обновлю вопрос еще раз, чтобы избежать сбивающего с толку упоминания индексов / aggs.]   -  person Phil B    schedule 14.08.2016
comment
Раньше я ошибался. Отключение LogChanges, как вы оба упомянули, дало значительное улучшение (от ~ 30 секунд до ~ 10 секунд), однако это все еще 10 секунд, чтобы удалить половину элементов, в отличие от ‹1 секунды, чтобы добавить элементы в первую очередь.   -  person Phil B    schedule 14.08.2016
comment
Я думаю, это ожидаемо. Для удаления необходимо переместить данные в файл.   -  person Sertac Akyuz    schedule 14.08.2016
comment
Возможно, но я хотел бы знать, не хватает ли мне какого-либо метода для удаления блока элементов за один раз. Может быть, в наборе данных есть (или должен быть?) Какой-то метод для маркировки большого диапазона элементов для удаления, а затем удаления сразу всех и манипулирования распределением памяти за 1 цикл?   -  person Phil B    schedule 14.08.2016
comment
Попробуйте сравнить время, необходимое для удаления со спины.   -  person Disillusioned    schedule 15.08.2016
comment
Используя почти идентичный код (я добавил TStopWatch для измерения времени и изменил Append/Set/Post на CDS.AddRecord([I]);, я получаю 73 мс, чтобы добавить 100000 строк, и 3883 мс (3,9 секунды), чтобы удалить первые 50000.   -  person Ken White    schedule 15.08.2016
comment
проблема в реализации midas. в зависимости от версии midas он может быть медленнее или быстрее и приносить разные ошибки. в моем тесте с моей собственной реализацией cds / midas у меня меньше 3 секунд, чтобы удалить 50k записей с включенной дельтой или отключенной дельтой и cds.Capacity (одно из моих собственных расширений). попробуйте протестировать другой компонент, например kbmmt   -  person vavan    schedule 15.08.2016
comment
Какая версия Delphi? Вы пробовали MidasSpeedFix Андреаса Хаусладена?   -  person Ondrej Kelle    schedule 15.08.2016
comment
@Craig: Я попробовал (используя CDS.Last; в то время как CDS ['ID'] ›50000 делает CDS.Delete;). Во всяком случае, это было немного медленнее!   -  person Phil B    schedule 15.08.2016
comment
@Ken: Спасибо за совет CDS.APPENDRECORD([I]). Это определенно было немного быстрее, чем использование Append/Set/Post для добавления элементов.   -  person Phil B    schedule 15.08.2016
comment
@Ondrej: XE9 (Сиэтл). Я не думаю, что MidasSpeedFix применим? Я все равно не могу скомпилировать с добавленной в проект версией 1.2.   -  person Phil B    schedule 15.08.2016
comment
Я думаю, что у меня происходило что-то странное с настройками питания, которые раньше давали мне время удаления ~ 10 секунд. Я перезапускаю тест сейчас, ничего не меняя, и получаю 110 мс для добавления и 3980 мс для удаления. Очень похоже на времена Кена. Интересно, что с предложением Крейга (удалите последние 50 тыс. Элементов) я получаю ~ 5250 для удаления, хотя в любом случае это не актуальный вариант применительно к моему реальному приложению. Удаляемый блок записей можно разместить где угодно.   -  person Phil B    schedule 15.08.2016
comment
@vavan: Я только что тестировал TkbmMemTable и получил 80 мс / 1440 мс, некоторые улучшения! Я ничего не знаю о компоненте, и документация по пробной версии, похоже, недоступна. Может есть более эффективные способы его использования? Тем не менее, существует большой разрыв между добавлением элементов и их удалением. Это все еще проблема в моем приложении, где пользователь может добавить свои данные за 10 секунд, а затем должен подождать 10 минут, чтобы удалить некоторые из них. В конце концов, альтернативный вариант простого копирования всех имеющихся у вас записей в новый набор данных по-прежнему выигрывает.   -  person Phil B    schedule 15.08.2016
comment
@PhilB Всегда ли эти блоки записей, которые вы удаляете, содержат те же записи, которые были добавлены из определенного файла CSV? Если да, то вам может быть лучше, если вы создадите отдельный набор данных для каждого файла CSV и создадите собственный класс, который позволит вам редактировать / искать среди всех записей, поскольку все они были частью одной и той же таблицы базы данных. Нечто похожее на реляционные базы данных. Таким образом вы избежите необходимости реорганизовывать записи в памяти после того, как их блок будет удален. ...   -  person SilverWarior    schedule 15.08.2016
comment
... Также вы получаете возможность многопоточного поиска, когда каждый набор данных может иметь свой собственный поток, выполняющий поиск.   -  person SilverWarior    schedule 15.08.2016
comment
@Silver: Хорошее предложение. Да, операция удаления всегда заключается в удалении всех записей, соответствующих определенному CSV-файлу, а набор данных может содержать более 10 различных CSV-файлов разного размера / формы. Некоторые из них могут иметь перекрывающиеся данные, например соответствует той же информации, но обновленным значениям.   -  person Phil B    schedule 15.08.2016


Ответы (1)


Подумал, что я мог бы также предоставить подробную информацию о моем собственном (временном / возможно постоянном?) Обходном пути на случай, если кто-то с такой же проблемой заинтересован.

ПРОБЛЕМА: удаление большой части записей из большого набора TClientDataSet (100 тыс. Или более записей) занимает много времени при использовании операции удаления по сравнению с начальным временем для добавления элементов (коэффициент 40 или более).

РЕШЕНИЕ: Скопируйте все записи, которые вы не хотите удалять, в новый набор данных, затем удалите оригинал. [Минусы: потеря журнала изменений, дополнительные требования к оперативной памяти?]

var
 CDS: TClientDataSet;

// initialize new CDS instance
function CreateNewCDSInstance: TCLientDataSet;
begin
 Result := TClientDataSet.Create(nil);
 Result.FieldDefs.Add('ID', ftInteger);
 Result.CreateDataSet;
 Result.LogChanges := False;
end;

// close + free CDS instance
procedure CloseCDS;
begin
 CDS.EmptyDataSet;
 CDS.Close;
 CDS.Free;
end;

// delete current record?
function CanDeleteCurrentRecord: boolean;
begin
 Result := CDS['ID'] < 50001; //in this simple example
 // in my application it would be more like:
 // "CDS['FILE_ID'] = AFileIDToDelete"
end;

// delete block of records:
procedure DeleteRecords;
var
 aNewCopy: TClientDataSet;
begin
 aNewCopy := CreateNewCDSInstance;
 CDS.First;
 while not CDS.EoF do
 begin
  if not CanDeleteCurrentRecord then
  begin
   // NB: AppendRecord takes array of values corresponding to field defintions
   aNewCopy.AppendRecord([CDS['ID']]);
  end;
  CDS.Next;
 end;
 CloseCDS;
 CDS := aNewCopy;
 //NB: If you have any aggregates/indexes defined, they must be redefined
end;

Используя приведенный выше пример, этот метод удаления 50k элементов занимает 94 мс вместо ~ 4 секунд.

Однако, задав этот вопрос и прочитав комментарии, мне стало очевидно, что это решение - скорее повязка, чем лекарство. Более серьезная проблема заключается в том, что система, над которой я работаю, не очень хорошо спроектирована для обработки необходимого количества данных. Возможно, это не столько «проблема с TClientDataSet», сколько «проблема с тем, как мы используем TClientDataSet»! Даже с исправлением скорости удаления по-прежнему будут проблемы с производительностью при импорте все большего размера и количества файлов и последующем управлении этими данными.

Возможно (в дождливый день!) Я начну рассматривать подход, похожий на предложение SilverWarior об использовании отдельного набора данных для хранения каждого импортированного файла вместо того, чтобы разбрасывать все данные в одну огромную таблицу памяти. Тогда удаление файла превращается в удаление набора данных - среди других потенциальных преимуществ.

person Phil B    schedule 16.08.2016