2010-08-04 2 views
3

SQL 스크립트를 실행하고 결과를 파일로 덤프하는 서비스 프로그램에서 메모리 누수 문제가 있습니다. 많은 결과 행을 생성하는 쿼리를 실행 한 후에는 프로세스의 메모리 사용량이 매회 50MB 이상 올라가고 내려 가지 않습니다. 여기 .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(); 
     } 
    } 
} 

I가 실행을 완료 한 후에 유형별 가기 객체를 나열 sos.dll와 WinDbg를 사용하고, 처리가 많은 시간이 있었다 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

이상하게 보입니다. 그냥 펀트,'using' 문에서'SqlCommand'를 래핑 해 보았습니까? – kbrimington

+0

GC.Collect()를 직접 호출하여 DataRow를 처음부터 수집 할 수 있는지 확인해 봤습니까? IIRC를 사용하면 메모리가 부족할 때까지 GC가 수집되지 않습니다. – Amy

+0

어떤 이유로 저는 SqlCommand가 일회용이 아니라고 생각했습니다.나는 using 절에 넣을 것이지만,이 문제는 쿼리 크기에 비례하는 것으로 보인다. 나는 GC가 실제로 일어나지 않는다는 제안이 옳을 수도 있다고 생각한다. 내가 기회를 얻 자마자 확인해 볼게. –

답변

2

LineFormatter가 프로그램 수명 동안 참조를 보유하지 않는 한, 여기에는 아무런 문제가 없습니다.

가비지 수집기가 작동하는 방식에 대해 몇 가지 큰 가정을하고 있습니다. AFAIK, 그것은 시간이 아닌 기억력에 기초하여 작동합니다. 만약 당신이 정말로 편집증적인 느낌이 들었다면, 코드에서 GC.Collect()를 실행하여 메모리 사용량을 줄이는 지 확인할 수 있습니다. 그러나 프로덕션 코드에서 GC.Collect()를 호출하지 않습니다. .

또한 작업 관리자를 사용하여 .NET 힙에서 예약되는 메모리 양을 알려주지 않도록하십시오. 대신 관리되는 세계에서 진행중인 작업을 확인하려면 performance counters in PerfMon을 확인해야합니다.

+0

수동 GC.Collect()를 실행하면 184MB 개인 바이트에서 33MB (시작 지점 근처)까지 메모리를 확보 할 수있었습니다. GC.Collect()를 프로덕션 코드로 사용하지 않아도된다면 어떻게해야합니까? 이 프로세스는 매일 몇 분만 실행됩니다. 나는 SQL Server 나 다른 캐시에서 더 잘 사용될 메모리를 차지하지 않기를 바란다. CLR의 이점은 하루 종일 저 참조되지 않은 메모리를 모두 유지하는 것입니다. –

+0

GC.Collect()는 작업을 수행 할 때 실제로 메모리를 통과하고 조각 모음을 수행합니다. 이것은로드 관점에서 꽤 강렬 할 수 있습니다. 따라서 콜렉터는 메모리가 부족할 때만 콜렉터를 수행합니다. 나는 무엇을 할 것이냐? 프로세스가 실제로 일정에 따라 몇 분 동안 만 실행되면 예약 된 작업으로 처리 할 것입니다. SQL Server에 관해서는 일정량의 RAM을 사용하도록 SQL Server를 구성해야합니다. 그렇게함으로써 SQL Server는 메모리를 절약하기 위해 다른 앱에 압력을가합니다. –

+0

서비스는 외부 이벤트가 응답하기를 기다립니다. 매일 몇 분만 운영 되더라도 실제로는 예정된 작업으로 작동하지 않습니다. 그것은 언제나 일어나야 만합니다. 불행히도 SQL Server 구성을 제어하지는 않습니다. 이 서비스는 배경에 눈에 띄지 않게 앉아서 같은 상자에있는 다른 서비스에 영향을 미치지 않아야합니다. 내가 끝내게 된 것은 활성 내부 직원 수가 0에 이른 후 1 분 동안 수동 GC를 실행하게하는 것이 었습니다. 수동 GC 동안 발생하는 CPU 사용은 무시할 수 있습니다. –

2

DataRow를 선택하고 !gcroot을 사용하여 누가 행에서 참조를 유지하는지 확인하십시오. Tracking down managed memory leaks (how to find a GC leak)을 참조하십시오.

+0

나는 그들 중 몇 개를 확인했는데 아무 결과도 얻지 못했다. 다른 곳에서 컬렉션이 실제로 발생하지 않는 경우가있을 수 있습니다. 나는 dev machine으로 돌아 가면 수동 GC로 점검 할 것이다. –

0

메모리 누수를 추적하는 가장 좋은 방법은 Nant 또는 .Net 메모리 프로파일 러와 같은 프로파일 러를 사용하는 것입니다. 나는 둘 다 적어도 15 일간의 시험 사용 기간이 필요하다고 생각하는데, 이는 당신이 필요로하는 것을 배우고 메모리 누출을 진단하기에 충분합니다.

.Net 메모리 프로파일 러를 사용했습니다. 보관되는 내용과 AppDomain 또는 정적 개체에서 누출 된 메모리로 전달되는 경로를 정확하게 추적하는 것은 매우 좋습니다. 앱을 실행하고 메타 데이터를 가져와 작동합니다. 스냅 샷 (프로파일 러 사용)을 실행하고 emory를 유출 한 작업을 실행 한 다음 두 번째 스냅 샷을 가져와 비교합니다. 두 스냅 샷간에 다른 점을 격리하고 크기별로 정렬 할 수 있으므로 문제를 매우 빨리 해결할 수 있습니다. 아주 좋은 도구!

0

SqlCommand를 using 블록에 넣거나 수동으로 처리해야 할 수도 있습니다.

+0

예제에서 SqlCommand를 처리하는 것을 잊었습니다. 원래 코드에서는 다른 곳에서 만들어지고 폐기되었습니다. 콜렉션이 일어나지 않았다는 것이 밝혀졌습니다. 수락 된 대답을보십시오. –