TClientDataSet работает ОЧЕНЬ МЕДЛЕННО с более чем 100 тыс. строк

у меня проблема с получением данных с помощью Delphi TClientDataSet

Код с ADO:

ADOQuery1.SQL.Text:='SELECT * FROM Table1 WITH (NoLock)';
DataSource1.DataSet:=ADOQuery1;
DataSource1.DataSet.Open;
DataSource1.DataSet.Last;

Приведенный выше код возвращает более 180 тыс. строк за 3-6 секунд при использовании чистого ADO.

Тот же код с TClientDataSet:

ADOQuery1.SQL.Text:='SELECT * FROM Table1 WITH (NoLock)';
CDS1.SetProvider(ADOQuery1);
DataSource1.DataSet:=CDS1;
DataSource1.DataSet.Open;
DataSource1.DataSet.Last;

Следующий код возвращает такое же количество строк (более 180 тыс.), Но в течение 3-4 минут.

Что не так с CDS? Это примерно в 100 раз медленнее, чем при использовании ADO. Можно ли это исправить?


person Valeriy    schedule 15.01.2015    source источник
comment
Я не знаю ответа, но возвращать клиенту 180 000 (!) записей - это в любом случае очень плохой дизайн...   -  person kobik    schedule 15.01.2015
comment
Какая версия Делфи? Было огромное замедление от XE2 до XE3 (настолько сильное, что мы отменили наше обновление Delphi), я не знаю, было ли оно исправлено с тех пор.   -  person Jan Doggen    schedule 15.01.2015
comment
@kobik Название компонента (TClientDataSet) не означает, что клиент является человеком. Приложением, в котором используется этот компонент, может быть любая программа, и некоторые программы должны иметь возможность обрабатывать огромный набор данных в памяти. Если в документации явно не указано, что не используйте этот компонент для более чем X записей.   -  person mjn    schedule 15.01.2015
comment
Я не был бы на 100% уверен, что ADO возвращает все строки. В зависимости от таких настроек, как CursorLocation, вы можете получить только несколько первых и несколько последних строк. Существуют также другие параметры, которые могут дать ADO возможность реализовать определенные улучшения эффективности. Например. Если настроить чтение только вперед, ADO не нужно будет хранить или обрабатывать какие-либо промежуточные строки при выполнении DataSet.Last.   -  person Disillusioned    schedule 15.01.2015
comment
Тем не менее: для извлечения всех данных в ClientDataSet требуется, чтобы каждая строка была преобразована во внутренний формат CDS. Этот формат предоставляет дополнительные функции, такие как: двунаправленная навигация, кэширование изменений, локальное индексирование. Все это требует дополнительной обработки и выделения памяти для поддерживающих внутренних структур. (Если вам нужны эти функции, вам нужна дополнительная обработка. Если они вам не нужны, то TClientDataSet, вероятно, не лучший инструмент для ваших требований.)   -  person Disillusioned    schedule 15.01.2015
comment
Просто пытаюсь исключить очевидное, но у вас нет компонента, поддерживающего данные, подключенного к DataSource, не так ли? Вы пробовали Disable-/EnableControls?   -  person Lieven Keersmaekers    schedule 15.01.2015
comment
Отключение элементов управления не влияет. Microsoft Management Studio выполняет ту же команду в течение 3-4 секунд.   -  person Valeriy    schedule 15.01.2015
comment
Отключение элементов управления является обязательным. И я согласен с большинством комментариев здесь. Для получения 180 тыс. строк на стороне клиента нужны серьезные причины. И если вы уверены, что вам нужно получить столько кортежей, выберите другое хранилище. TDataSet потомки медленные в целом. Для извлечения оптимизируйте запрос, чтобы использовать прямой курсор только для чтения и доступ к базовому интерфейсу Recordset. @mjn, это не задокументировано, а также не задокументировано, сколько элементов разумно использовать, например. с TList.   -  person TLama    schedule 15.01.2015
comment
Период запроса может быть за последние 10 лет. Таким образом, количество возвращаемых строк может быть еще больше. В любом случае, главный вопрос заключается в том, как избежать огромных потерь времени.   -  person Valeriy    schedule 15.01.2015
comment
что вы собираетесь делать с результирующим набором из 108 тыс. записей? нужно ли их отображать? импортировать их? анализировать их? возможно, все, что вам нужно, это правильный оператор SQL/SP, чтобы получить то, что вы хотите. извлекать все записи имеет смысл только в том случае, если вам нужно импортировать весь результирующий набор, и в этом случае я бы вообще не использовал CDS. @mjn, я имел в виду клиентское приложение.   -  person kobik    schedule 15.01.2015
comment
Первая версия кода будет просто перемещаться по базовому набору записей ADO (т. е. выполняться внутри ADO), в то время как вторая версия передает весь набор записей, поэтому применяются накладные расходы оболочки TDataset ADO, а также накладные расходы TClientDataset. Это может быть неактуально, но оболочка Delphi ADO имеет значительное улучшение производительности за счет вызова DisableControls даже для TDatasets без связанных с ними элементов управления. Это может иметь значение, но все равно будет медленно.   -  person Kanitatlan    schedule 15.01.2015
comment
У меня была аналогичная проблема, после которой я обнаружил, что для каждой записи для данных BLOB-объектов выполнялось обращение к серверу базы данных. Теперь значение FetchOnDemand для CDS равно true, и значение poFetchBlobsOnDemand для провайдера также верно, а элементы управления отключены во время открытия, но я не помню дальнейших подробностей.   -  person Sertac Akyuz    schedule 15.01.2015
comment
@TLama: я бы скорее сказал, что, хотя некоторые конкретные потомки набора данных работают медленно (из-за плохого внутреннего дизайна/реализации), другие (например, kbmMT или моя собственная реализация CDS) довольно быстры и не имеют проблем с загрузкой/обработкой сотен тысяч или даже миллионы записей. например, пользователи моих приложений иногда загружают до 500 тыс. записей (более 100 полей, включая множество строковых/памятных) в CDS и в основном ограничиваются только доступным адресным пространством (32-битный режим)   -  person vavan    schedule 16.01.2015


Ответы (3)


Приведенный выше код возвращает более 180 тыс. строк за 3-6 секунд при использовании чистого ADO.

По некоторым причинам я бы не ожидал, что код, который вы разместили, вернет каждую из 180 000 записей... Я ожидаю увидеть первые «X» записей, загруженных после вызова TADOQuery.Open, а затем последний «X». "записи, отправленные при вызове TADOQuery.Last. Переход while not EoF do вместо ".Last", вероятно, будет лучшим тестом производительности, поскольку (я полагаю) вы действительно хотите просмотреть все записи.

При вызове TClientDataset.Last при подключении к DataProvider он, скорее всего, выполняет эквивалент while not EoF do в вашем запросе, который передает все 180 000 записей. Кроме того, операция вставки/добавления TClientDataset, как правило, становится все медленнее и медленнее, чем больше в ней записей. Я думаю, что время от времени ему приходится перераспределять буфер памяти. Если это так, я не нашел способа сообщить TClientDataset: «Эй! Приготовьтесь, 180 000 записей поступает!» (сродни TList.SetCapacity).

Если у вас более старая версия delphi, вам может помочь Midas Speed ​​Fix.

person Ken Bourassa    schedule 15.01.2015
comment
действительно стандартный TDataPacketWriter.WriteDataSet не только зацикливается на исходном наборе данных до тех пор, пока не будет получено либо eof, либо запрошенное количество записей, но также записывает каждое другое значение поля (в результирующий поток) во внутреннем цикле полей, завернутом в пустой блок try/except. - person vavan; 16.01.2015

Это довольно старая программа, но сейчас появилось много новых программистов Delphi. Вот небольшая совок.

При использовании CDS в delphi вы фактически создаете таблицу памяти. Ваш запрос, вероятно, пошёл наперекосяк.

Чтобы получить максимальную отдачу от CDS, используйте компоненты DBX для захвата данных. Это так называемые курсоры «быстрой перемотки вперед», которые не создают временную таблицу с курсором в базе данных. forward only не делает тех причудливых вещей, которые делает ADO. Если вам нужны массивные наборы данных с полными уведомлениями об обновлениях и полным контролем, используйте ADO. Если вам нужно в спешке обрабатывать тонны данных с небольшой нагрузкой на сервер, то CDS/DBX сияет.

Путь DBX сложнее. Это просто драгстер. First и Next — единственное, что им подходит. Никаких обновлений, никаких обязательств, только быстрые односторонние отношения. Подключите комбинацию DBX/провайдер/CDS, и у вас есть все. Скорость и возможность редактирования. Используйте номер версии, чтобы обнаружить, что другой пользователь что-то делал с данными, пока вы редактировали. Изучите варианты поставщиков, чтобы узнать, как получить мощность с гибкостью. Это в значительной степени так же сложно, как и в Delphi.

person Michael    schedule 14.02.2016
comment
Извините, вопрос заключался в том, почему CDS работает так медленно с таким количеством записей по сравнению с набором данных Ado. Правильный ответ заключается в том, что он всегда был медленным для более чем нескольких 10 000 записей. Первоначально это было связано с тем, как он выделял память, но, несмотря на то, что с годами он был улучшен, он был разработан для небольших наборов данных на стороне клиента, а 180 000 строк в этом контексте не мало. Кстати, TAdoQuery, загружающий CDS через TDatasetProvider, намного быстрее, чем DBX TSqlQuery, извлекающий те же данные. - person MartynA; 14.02.2016
comment
И при правильном использовании TAdoQuery может делать большинство изящных вещей, которые может CDS (например, его модель портфеля), не требуя (в отличие от DBX) от CDS для клиентской части, и это также будет намного быстрее. , име. - person MartynA; 14.02.2016

Попробуйте установить для свойства CDS1.LogChanges значение False перед загрузкой данных. Это необходимо сделать в коде, поскольку это не опубликованное свойство.

Из файла справки: для больших наборов данных значение True для LogChanges может серьезно повлиять на производительность приложения.

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

person Larsdk    schedule 15.01.2015
comment
отключение журнала изменений не должно влиять на время загрузки, поскольку данные передаются через провайдера, а не через вставку/добавление - person vavan; 16.01.2015