2009-11-10 2 views
6

Каждый раз, когда я пишу программу формы, использующую LINQ to SQL, я получаю программу, которая просто захватывает все больше и больше памяти при ее запуске и падает в куче, потребляя 2 ГБ после, возможно, как всего 25 000 записей. Я всегда переписываю его с помощью ADO.NET. Что я делаю не так?Обработка больших наборов данных с использованием LINQ

Уточнение: этот вопрос касается не скорости обработки; ответы о том, чтобы сделать это быстрее, не имеют никакого отношения.

foreach (int i=0; i<some_big_number; i++) 
{ 
    using (myDC dc = new myDC()) // my DataContext 
    { 
     myRecord record = (from r in dc.myTable where r.Code == i select r).Single(); 

     // do some LINQ queries using various tables from the data context 
     // and the fields from this 'record'. i carefully avoid referencing 
     // any other data context than 'dc' in here because I want any cached 
     // records to get disposed of when 'dc' gets disposed at the end of 
     // each iteration. 

     record.someField = newValueJustCalculatedAbove; 
     dc.SubmitChanges(); 
    } 
} 
+3

Я думаю, что ответ на вопрос «Что я делаю неправильно?» «Выполнять то же самое и ожидать другого ответа». Это также признак безумия. Просто для пинков попробовать что-то еще. Например, напишите свой код доступа sql в ADO.Net FIRST и просто пропустите все, что связано с linq. – NotMe

+4

Но LINQ - это не экскремент, как вы предлагаете. Это самая элегантная из технологий и упорство в этом - это мое свидетельство этого, а не признак безумия. – Nestor

ответ

6

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

Попробуйте использовать скомпилированный запрос.

+0

Переключение на скомпилированные запросы имеет большое значение, уменьшая потребление памяти не менее чем на 95%. Я думаю, что постоянно перекомпилировать запросы - это плохая идея, но мне все еще остается любопытно, почему в ней так много памяти. В интересах других, вот руководство по началу работы с скомпилированными запросами: http://linqinaction.net/blogs/jwooley/archive/2007/09/04/linq-to-sql-compiled-queries. aspx – Nestor

+0

Скомпилированные запросы не являются заменой замены для некомпилированных, вам нужно немного изменить код. Эта статья помогла мне понять, почему это так: http://linqinaction.net/blogs/jwooley/archive/2007/09/04/linq-to-sql-compiled-queries.aspx – Nestor

+1

7 лет спустя, и это оказалось находкой для исправления плохой проблемы в устаревшем приложении. Спасибо. –

0

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

Возможно, после каждого цикла вы можете вызвать GC.Collect(), посмотреть, можно ли вручную вывести сборку мусора.

+1

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

+0

@maxc - Вы хотя бы пытались переместить вызов datacontext за пределы цикла, чтобы убедиться, что это имеет значение? – Breadtruck

+0

У меня есть, да. периодически отрывал мои волосы на этом году уже год. – Nestor

3

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

Вы должны работать в пакетном режиме:

  • Получить набор строк вверх перед выбором на диапазон, а не одно значение, т.е. 0-100 для первой партии, 101-200 для следующего партии и т. д. Это будет быстрее, если у вас есть кластерный индекс, определенный в столбце Код.

  • Создать контекст данных перед входом в цикл

  • Внутри цикла, просто обновить объекты необходимо

  • Вызов SubmitChanges() после цикла закончится, это будет отправлять все обновления базы данных в одной транзакции подключения/

  • Повторите для следующей партии

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

Кроме того, я бы использовал SingleOrDefault() с нулевой проверкой вместо Single(), если вы не можете гарантировать, что всегда будет строка для любого значения i.

EDIT:

С точки зрения использования памяти, что гораздо труднее контролировать, но это не свойственно LINQ к SQL, любой алгоритм пакетной обработки данных приходится иметь дело с этим. Хотя я не рекомендую использовать GC.Collect() на практике, обычно это достаточно для решения проблемы после обработки большой партии.

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

+0

Уверены, все они оптимизированы по скорости, но оптимизация должна появиться после того, как программа была готова к работе. В настоящий момент вся доступная память быстро съедается, так что она не может продолжаться вообще. – Nestor

+1

+1 для "Call SubmitChanges() ПОСЛЕ завершения цикла" – Breadtruck

+0

И вызов метода SubmitChanges() после завершения цикла сделает то же, что и для использования памяти? Мы не говорим о скорости здесь. – Nestor

1

Я не смог воспроизвести проблему. Использование памяти было плоским. Медленная производительность, но постоянная память.

Вы уверены, что не утечка в другом месте? Можете ли вы создать минимальный образец кода, который воспроизводит проблему?

Edit:

Я использовал практически тот же образец кода:

for (int ii = 1; ii < 200000; ii++) 
{ 
    using (var dc = new PlayDataContext()) 
    { 
     var record = 
      (from r in dc.T1s where r.Id == ii select r).SingleOrDefault(); 
     if (record != null) 
     { 
      record.Name = "S"; 
      dc.SubmitChanges(); 
     } 
    } 
} 

без проблем.

Так вещи, чтобы исключить:

  • Framework версии. Я на последнем месте.
  • Спецификация DataContext/entity. Моя тестовая таблица - это всего лишь два поля и Id (int) и имя (nvarchar (max)).

Можете ли вы воспроизвести проблему с последним FW с небольшим образцом DataContext?

+0

Я согласен с тем, что, думаю, нам нужно будет увидеть больше вашего кода, чтобы диагностировать проблему. Я тоже использовал LTS в больших гигантских циклах и еще не столкнулся с этой проблемой «утечки памяти», которую вы упоминаете ... – Funka

+0

Вот вам полный образец, хотя, конечно, я не смог включить сам контекст данных. На моей машине он потребляет более 1 МБ в секунду и использует 720 МБ, когда я пишу это. для (INT пробег = 0; запускать <1000; бег ++) для (INT I = 0; я <50000; я ++) { используя (MyDC myDC = новый MyDC()) { tblRecords запись = (от r в myDC.tblRecords, где r.Code == i выбирает r) .SingleOrDefault(); } } – Nestor

Смежные вопросы