Производительность Delphi: чтение всех значений в поле набора данных - PullRequest
7 голосов
/ 05 ноября 2011

Мы пытаемся найти некоторые исправления производительности, читающие из TADOQuery. В настоящее время мы перебираем записи с помощью метода 'while not Q.eof do begin ... Q.next. Для каждого мы читаем ID и значение каждой записи и добавляем каждый в список выпадающих списков.

Есть ли способ преобразовать все значения указанного поля в список за один снимок? Вместо того, чтобы перебирать набор данных? Было бы очень удобно, если бы я мог сделать что-то вроде ...

TStrings(MyList).Assign(Q.ValuesOfField['Val']);

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

Ответы [ 8 ]

13 голосов
/ 05 ноября 2011

Глядя на ваш комментарий, вот несколько предложений:

Есть несколько вещей, которые могут стать узким местом в этой ситуации.Первый - это многократный поиск полей.Если вы вызываете FieldByName или FindField внутри цикла, вы тратите время ЦП на пересчет значения, которое не изменится.Вызовите FieldByName один раз для каждого поля, из которого вы читаете, и вместо этого назначьте их локальным переменным.

При извлечении значений из полей вызовите AsString или AsInteger или другие методы, которые возвращают тип данных, который выищу.Если вы читаете из свойства TField.Value, вы тратите время на variant преобразований.

Если вы добавляете несколько элементов в поле со списком Delphi, вы, вероятно, имеете дело ссписок строк в виде свойства Items.Установите свойство списка Capacity и обязательно вызовите BeginUpdate перед началом обновления, а затем вызовите EndUpdate в конце.Это может включить некоторые внутренние оптимизации, которые ускоряют загрузку больших объемов данных.

В зависимости от используемого поля со списком, могут возникнуть проблемы с большим количеством элементов в его внутреннем списке.Посмотрите, есть ли у него «виртуальный» режим, где вместо того, чтобы загружать все заранее, вы просто сообщаете ему, сколько элементов ему нужно, а когда он выпадает, он вызывает обработчик событий для каждого элемента, который должен быть показанна экране, и вы даете ему правильный текст для отображения.Это действительно может ускорить определенные элементы управления пользовательским интерфейсом.

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

И, наконец,Комментарий Микаэля Эрикссона определенно заслуживает внимания!

8 голосов
/ 05 ноября 2011

Вы можете использовать Getrows .Вы указываете интересующий вас столбец (столбцы), и он возвращает массив со значениями.Время, необходимое для добавления 22 000 строк в поле со списком, в моих тестах составляет от 7 секунд с циклом while not ADOQuery1.Eof ... до 1,3 секунды.

Пример кода:

var
  V: Variant;
  I: Integer;
begin
  V := ADOQuery1.Recordset.GetRows(adGetRowsRest, EmptyParam, 'ColumnName');

  for I:= VarArrayLowBound(V, 2) to VarArrayHighBound(V, 2) do
    ComboBox1.Items.Add(V[0, I]));
end;

Если вы хотитеболее одного столбца в массиве, вы должны использовать вариантный массив в качестве третьего параметра.

V := ADOQuery1.Recordset.GetRows(adGetRowsRest, EmptyParam, 
       VarArrayOf(['ColumnName1', 'ColumnName2']);
3 голосов
/ 05 ноября 2011

Есть несколько замечательных предложений по производительности, сделанных другими людьми, которые вы должны реализовать в Delphi.Вы должны рассмотреть их.Я сосредоточусь на ADO.

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

ADO RecordSet

В ADO есть объект RecordSet.В данном случае этот объект RecordSet является в основном вашим ResultSet.Интересная вещь в итерации через RecordSet заключается в том, что он все еще связан с провайдером.

Тип курсора

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

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

Если вы не знаете, что такое курсор, при вызове функцииКак и Далее, вы перемещаете курсор, который представляет текущую запись.

Не каждый провайдер поддерживает все типы курсоров.

CacheSize

Размер кэша Delphi и ADO по умолчанию для RecordSet равен 1. Это 1 запись.Это работает в сочетании с типом курсора.Размер кэша сообщает RecordSet, сколько записей нужно извлечь и сохранить за раз.

Когда вы запускаете команду типа Next (действительно MoveNext в ADO) с размером кэша 1, RecordSet имеет только текущую запись впамяти, поэтому, когда он выбирает эту следующую запись, он должен снова запросить ее у провайдера.Если курсор не является статическим, это дает провайдеру возможность получить последние данные перед возвратом следующей записи.Таким образом, размер 1 имеет смысл для Keyset или Dynamic, потому что вы хотите, чтобы поставщик мог получать обновленные данные.

Очевидно, что при значении 1 существует связь между поставщиком и RecordSet каждыйвремя переместить курсор.Ну, это накладные расходы, которые нам не нужны, если тип курсора статический.Таким образом, увеличение размера кэша приведет к уменьшению количества циклов между RecordSet и провайдером.Это также увеличивает ваши требования к памяти, но это должно быть быстрее.

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

3 голосов
/ 05 ноября 2011

Ваш запрос также привязан к некоторым элементам управления данными или к TDataSource?Если это так, выполняйте цикл внутри блока DisableControls и EnableControls, чтобы визуальные элементы управления не обновлялись при каждом переходе к новой записи.

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

3 голосов
/ 05 ноября 2011

Вы не можете избежать зацикливания. «Очень долгое время» является относительным, но если получение 20000 записей занимает слишком много времени, значит что-то не так.

  • Проверьте ваш запрос;возможно, SQL можно улучшить (отсутствует индекс?)
  • Показать код вашего цикла, в который вы добавляете элементы в выпадающий список.Может быть, это можно оптимизировать.(повторно вызывать FieldByName в цикле? использовать варианты для получения значений полей?)
  • Обязательно вызывать ComboBox.Items.BeginUpdate; до цикла и ComboBox.Items.EndUpdate после.
  • Использовать профилировщикнайти узкое место.
3 голосов
/ 05 ноября 2011

Вы можете попробовать вставить все данные в ClientDataSet и выполнить итерацию этого, но я думаю, что копирование данных в CDS делает именно то, что вы делаете в настоящее время - циклы и присваивание.

То, что я когда-то делал, было объединением значенийна сервере, передавая его в одном пакете клиенту и снова разделяя его.Это на самом деле ускорило работу системы, поскольку сократило необходимую связь между клиентом и сервером.

Вы должны внимательно следить за узким местом в производительности.Это также может быть поле со списком, если вы не блокируете обновления графического интерфейса при добавлении значений (особенно, когда мы говорим о значениях 20 КБ - это много для прокрутки).

Редактировать: тогда, когда вы не можете изменить связь,Вы, возможно, могли бы сделать это асинхронным.Запрашивать новые данные в потоке, поддерживать отзывчивость графического интерфейса, заполнять поле со списком, когда данные есть.Это означает, что пользователь видит пустой комбинированный список в течение 5 секунд, но, по крайней мере, он может сделать что-то еще в это время.Однако не влияет на количество необходимого времени.

1 голос
/ 03 мая 2012

попробуйте использовать DisableControls и EnableControls для повышения производительности линейного процесса в наборе данных.

   var
  SL: TStringList;
  Fld: TField;
begin
  SL := TStringList.Create;
  AdoQuery1.DisableControls;
  Fld := AdoQuery1.FieldByName('ListFieldName'); 
  try
    SL.Sorted := False; // Sort in the query itself first
    SL.Capacity := 25000; // Some amount estimate + fudge factor

    SL.BeginUpdate;  
    try
      while not AdoQuery1.Eof do
      begin
        SL.Append(Fld.AsString);
        AdoQuery1.Next;
      end;
    finally
      SL.EndUpdate;
    end;

    YourComboBox.Items.AddStrings(SL);

  finally
    SL.Free;
    AdoQuery1.EnableControls;
  end;
end;
0 голосов
/ 05 ноября 2011

Не уверен, поможет ли это, но я предлагаю не добавлять непосредственно в ComboBox.Вместо этого загрузите в локальный TStringList, сделайте это как можно быстрее, а затем используйте TComboBox.Items.AddStrings, чтобы добавить их все сразу:

var
  SL: TStringList;
  Fld: TField;
begin
  SL := TStringList.Create;
  Fld := AdoQuery1.FieldByName('ListFieldName'); 
  try
    SL.Sorted := False; // Sort in the query itself first
    SL.Capacity := 25000; // Some amount estimate + fudge factor
    SL.BeginUpdate;  
    try
      while not AdoQuery1.Eof do
      begin
        SL.Append(Fld.AsString);
        AdoQuery1.Next;
      end;
    finally
      SL.EndUpdate;
    end;
    YourComboBox.Items.BeginUpdate;
    try
      YourComboBox.Items.AddStrings(SL);
    finally
      YourComboBox.Items.EndUpdate;
    end;
  finally
    SL.Free;
  end;
end;
...