Загрузка миллионов записей в список строк может быть очень медленной

как я могу очень быстро загрузить миллионы записей из tadotable в список строк?

procedure TForm1.SlowLoadingIntoStringList(StringList: TStringList);
begin
  StringList.Clear;
  with SourceTable do
  begin
    Open;
    DisableControls;
    try
      while not EOF do
    begin
      StringList.Add(FieldByName('OriginalData').AsString);
      Next;
    end;
   finally
   EnableControls;
   Close;
  end;
end;

person Kamyar Kimiyabeigi    schedule 07.12.2011    source источник
comment
Узким местом здесь, вероятно, является база данных и / или драйвер ado, а не список строк, хотя вы можете попробовать профилировать, чтобы быть уверенным. В случае подтверждения вам понадобится более быстрая база данных или лучший драйвер, чтобы ускорить процесс.   -  person Eric Grange    schedule 07.12.2011
comment
Вы можете попробовать установить Stringlist.Capacity := SourceTable.RecordCount; Это сэкономило бы много перераспределения базового массива.   -  person Gerry Coll    schedule 07.12.2011
comment
Что здесь медленно, так это FieldByName('OriginalData').   -  person Arnaud Bouchez    schedule 07.12.2011
comment
И попробуйте использовать прямую ссылку на поставщика OleDB, чтобы обойти слой ADO. См., например, наш блок с открытым исходным кодом.   -  person Arnaud Bouchez    schedule 07.12.2011
comment
Очистите мой предыдущий комментарий - я только что попробовал очень простой цикл из 10 миллионов записей, и он был лишь немного (около 15%) быстрее.   -  person Gerry Coll    schedule 07.12.2011
comment
Если TStringList отсортирован, это также может замедлить процесс. Установите Sorted := False при добавлении строк, затем снова включите сортировку, если необходимо, в конце.   -  person Jerry Gagnon    schedule 07.12.2011


Ответы (6)


в вашем цикле вы получаете поле. Искать поле вне цикла

procedure TForm1.SlowLoadingIntoStringList(StringList: TStringList); 
var
  oField: TField;
begin
  StringList.Clear;   
  with SourceTable do   
  begin     
    Open;     
    DisableControls;     
    try       
      oField:= FieldByName('OriginalData');
      if oField<>Nil then
      begin
        while not EOF do
        begin       
          StringList.Add(oField.AsString);       
          Next;     
        end;   
      end; 
    finally    
      EnableControls;    
      Close;   
    end; 
  end;  
end;
person Ravaut123    schedule 07.12.2011
comment
Я бы также добавил BeginUpdate, EndUpdate для случаев, когда кто-то перейдет в качестве ввода этого метода, например. TListBox.Items :) Нет, серьезно, Камьяр, почему загружается так много строк? Вы сохраняете эти строки в виде файла? Или для динамического поиска? Если это динамический поиск, вы должны позволить серверу БД сделать это за вас. - person TLama; 08.12.2011
comment
Если входным параметром является параметр TStringList, TStrings не могут быть переданы. - person Arnaud Bouchez; 08.12.2011

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

person Polynomial    schedule 07.12.2011

С «миллионами записей» вы можете рассмотреть: 1/Измените свой запрос с

SELECT * FROM MYTABLE;

in

SELECT OriginalData FROM MYTABLE;

Вы будете использовать меньше памяти и будете более эффективны.

2/ Посмотрите другой компонент, чем TStringList, в зависимости от ваших потребностей.

3/ Посмотрите все хорошие предыдущие советы, в основном:

  • не используйте FieldByName
  • прямая ссылка на поставщика OleDB
person philnext    schedule 07.12.2011

Это отсортировано?

  // Turn off the sort for now
  StringList.Sorted := False;
  // Preallocate the space
  StringList.Capacity := recordCount;
  // Now add the data with Append()
  ...
  // Now turn the sort back on
  StringList.Sorted := True;
person Marcus Adams    schedule 07.12.2011

Серьезно? Миллионы записей в списке строк?

Хорошо, давайте предположим, что вам действительно нужно использовать этот подход...

Уже есть несколько хороших предложений.

Если вы хотите поэкспериментировать с другим подходом, вы можете рассмотреть возможность объединения отдельных серверных записей (через хранимую процедуру), а затем вернуть объединенные данные в виде большого двоичного объекта (или, возможно, nvarchar (max)), который в основном представляет собой список объединенных строк. разделенные, скажем, возвратом каретки (при условии, что это разумный разделитель для ваших нужд).

Затем вы можете просто присвоить возвращаемое значение свойству Text списка TStringList.

Даже если вы не можете сыграть все струны за один удар, вы можете играть их группами, скажем, по 1000 за раз.

Это сэкономит вам массу времени, зацикливаясь на каждой клиентской стороне записи.

person Peter    schedule 08.12.2011

Расширяя ответ @Ravaut123, я бы предложил следующий код:

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

...
SQLatable:= 'SELECT SingleField FROM atable ORDER BY indexedfield ASC';
AQuery:= TAdoQuery.Create(Form1);
AQuery.Connection:= ....
AQuery.SQL.Text:= SQLatable;  

Использование запроса гарантирует, что вы выберете только 1 поле в нужном вам порядке, что снижает сетевой трафик. Таблица извлекает все поля, вызывая гораздо больше накладных расходов.

function TForm1.LoadingAllIntoStringList(AQuery: TAdoQuery): TStringList;  
var 
  Field1: TField; 
begin 
  Result:= nil;
  try
    if not(AQuery.Active) then begin
      AQuery.Open;
    end else begin
      AQuery.First;
    end;
    AQuery.DisableControls;
    AQuery.Filtered:= false;                    //Filter in the SQL `where` clause
    AQuery.FetchAll;                            //Preload all data into memory
    Result:= TStringlist.Create;
  except
    {ignore error, will return nil}
  end;
  try
    Result.Sorted:= false;                      //Make sure you don't enable sorting
    Result.Capacity:= AQuery.RecordCount;       //Preallocate the needed space     
    Field1:= AQuery.FieldByName('SingleField'); //Never use `fieldbyname` in a loop!
    while not AQuery.EOF do begin
      Result.Add(Field1.AsString);
      AQuery.Next;
    end; {while} 
    AQuery.EnableControls;
  except
    FreeAndNil(Result);
  end;   

Если вы хотите загрузить данные в список строк для выполнения некоторой обработки, рассмотрите возможность вместо этого сделать это в операторе SQL. БД может использовать индексы и другие оптимизации, которые нельзя использовать в списке строк.
Если вы хотите сохранить эти данные в CSV-файле, рассмотрите возможность использования для этого встроенной функции БД.
например. MySQL имеет:

SELECT X FROM table1 INTO OUTFILE 'c:/filename_of_csv_file.txt'

Это создаст для вас файл CSV.
Многие базы данных имеют аналогичные функции.

person Johan    schedule 08.12.2011
comment
+1, хороший момент о том, чтобы позволить серверной стороне выполнять тяжелую работу. В любом случае я бы объявил список выходных строк как параметр, а не как результат функции;) - person TLama; 08.12.2011