2010-08-04 3 views
3

У меня проблема с утечкой памяти в сервисной программе, которая запускает SQL-скрипты и выгружает результаты в файлы. После выполнения запросов, которые производят множество строк результатов, использование памяти в процессе увеличивается на 50 + МБ каждый раз и не уменьшается.Утечка памяти с .NET SqlConnection и DataSet

Вот код, который открывает соединение и возвращает результаты:

using (var conn = new SqlConnection(DataSourceInfo.ConnectionString)) 
{ 
    conn.Open(); 

    var scmd = new SqlCommand(query_string, conn); 
    scmd.CommandTimeout = 86400; 

    var writer = dest.GetStream(); //the writer is disposed of elsewhere 

    using (var da = new SqlDataAdapter(scmd)) 
    using (var ds = new DataSet()) 
    { 
     da.Fill(ds); 
     var table = ds.Tables[0]; 
     var rows = table.Rows; 

     if (TaskInfo.IncludeColNames.Value) 
     { 
      object[] cols = new object[table.Columns.Count]; 

      for(int i = 0; i < table.Columns.Count; i++) 
       cols[i] = table.Columns[i]; 

      LineFormatter(writer, TaskInfo.FieldDelimiter, null, false, cols); 
      writer.WriteLine(); 
     } 

     foreach(System.Data.DataRow r in rows) 
     { 
      var fields = r.ItemArray; 

      LineFormatter(writer, TaskInfo.FieldDelimiter, TaskInfo.TextQualifier, TaskInfo.TrimFields.Value, fields); 
      writer.WriteLine(); 
     } 
    } 
} 

Я использовал WinDbg с SOS.dll в список лучших объектов по типу после выполнения было завершено, и процесс было достаточно времени, чтобы GC:

79333470  101  166476 System.Byte[] 
65245dcc  177  3897420 System.Data.RBTree`1+Node[[System.Data.DataRow, System.Data]][] 
0015e680  5560  3968936  Free 
79332b9c  342  3997304 System.Int32[] 
6524508c 120349  7702336 System.Data.DataRow 
793041d0  984  22171736 System.Object[] 
7993bec4  70  63341660 System.Decimal[] 
79330a00 2203630  74522604 System.String 

Второй столбец - это количество объектов, а третий - общий размер.

Не должно быть никаких объектов System.Data.DataRow. Похоже, что они каким-то образом просочились, но я не знаю, как это сделать.

Что я делаю неправильно?

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

+0

Мне кажется странным. Просто пунт, попробовали ли вы обернуть свой 'SqlCommand' в' using' statement? – kbrimington

+0

Вы пытались позвонить GC.Collect(), чтобы убедиться, что DataRows являются коллекционируемыми в первую очередь? IIRC, GC не собирает, пока у него не будет давления памяти для этого. – Amy

+0

По какой-то причине я думал, что SqlCommand не является одноразовым.Я поставлю его в разделе using, но проблема, похоже, пропорциональна размеру запроса. Я думаю, что предложения о том, что GC фактически не происходят, могут быть правильными. Я проверю его, как только у меня появится шанс. –

ответ

2

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

Вы делаете некоторые большие предположения о том, как работает сборщик мусора. AFAIK, он работает на основе давления памяти, а не времени. Если вы чувствовали себя параноиком, вы могли бы запустить GC.Collect() в коде и посмотреть, не приведет ли это к сокращению использования памяти, но я бы никогда не назвал GC.Collect() в производственном коде - просто сделайте это как тест ,

Также убедитесь, что вы не полагаетесь на диспетчер задач, чтобы рассказать вам, сколько памяти зарезервировано в куче .NET. Вместо этого вы должны посмотреть на performance counters in PerfMon, чтобы изучить, что происходит в управляемом мире.

+0

Запуск руководства GC.Collect() освободил память - от 184 МБ частных байтов до 33 МБ (около того, где он был запущен). Итак, если я не должен использовать GC.Collect() в производственном коде, что я должен делать? Этот процесс проходит всего несколько минут каждый день. Я не хочу, чтобы он занимал память, которая лучше использовалась бы в SQL Server или других кешах. Какая польза от CLR, сохраняющей всю эту незарегистрированную память, совершенную весь день? –

+0

Когда GC.Collect() выполняет свою работу, он фактически проходит и дефрагментирует память. Это может быть довольно интенсивным с точки зрения нагрузки, поэтому сборщик делает это только тогда, когда он находится под давлением памяти. Что бы я сделал? Если процесс действительно выполнялся всего несколько минут в расписании, я бы поставил его в качестве запланированной задачи. Что касается SQL Server, вы должны настроить его на использование фиксированного объема ОЗУ. Делая это, SQL Server будет оказывать давление на ваши другие приложения, чтобы сохранить память, а не наоборот. –

+0

Служба ожидает внешних событий для ответа. Хотя он работает всего несколько минут каждый день, он действительно не будет работать как запланированная работа. Это должно быть всегда. К сожалению, я не контролирую конфигурацию SQL Server. Служба должна сидеть ненавязчиво в фоновом режиме и не воздействовать на другие службы в одном окне. То, что я закончил, состояло в том, чтобы запустить ручную GC через 1 минуту после того, как число активных внутренних работников достигнет 0. Использование ЦП, которое происходит во время ручного GC, незначительно. –

2

Выберите DataRow и используйте !gcroot, чтобы узнать, кто держит ссылку в строках. См. Tracking down managed memory leaks (how to find a GC leak).

+0

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

0

Лучший способ отследить утечку памяти - это профилировщик, например, Nant или .Net Memory Profiler. Я думаю, что у обоих есть как минимум 15-дневный пробный период, которого достаточно, чтобы узнать, что вам нужно, и диагностировать утечку памяти.

Я использовал .Net Profiler Profiler. Очень хорошо отслеживать то, что удерживается, и каковы пути доступа к утечке памяти из AppDomain или любых статических объектов. Он работает, запустив приложение и захватив метаданные; вы делаете снимок (с профайлером), запускайте одну операцию, которая утечки emory, затем возьмите второй снимок и сравните. Вы можете выделить то, что отличается между двумя моментальными снимками, и отсортировать по размеру, поэтому вы быстро закроете проблему. Очень хороший инструмент!

0

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

+0

В этом примере я забыл утилизировать SqlCommand. В исходном коде его создавали и удаляли в другом месте. Оказывается, коллекции не происходили; см. принятый ответ. –

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