2012-09-17 5 views
5

tengo una enorme mesa que tengo que leer a través de un cierto orden y calcular algunas estadísticas agregadas. La tabla ya tiene un índice agrupado para el orden correcto, por lo que obtener los registros es bastante rápido. Estoy tratando de usar LINQ to SQL para simplificar el código que necesito escribir. El problema es que no quiero cargar todos los objetos en la memoria, ya que el DataContext parece mantenerlos en todo - sin embargo, tratar de página que se traduce en problemas de rendimiento horrible.Leer enorme mesa con LINQ a SQL: El ejecutarse de memoria vs paginación lenta

Aquí está el desglose. intento original era la siguiente:

var logs = 
    (from record in dataContext.someTable 
    where [index is appropriate] 
    select record); 

foreach(linqEntity l in logs) 
{ 
    // Do stuff with data from l 
} 

Esto es bastante rápido, y arroyos a buen ritmo, pero el problema es que el uso de memoria de la aplicación sigue subiendo nunca se detiene. Mi suposición es que las entidades LINQ to SQL se guardan en la memoria y no se eliminan correctamente. Entonces, después de leer Out of memory when creating a lot of objects C#, probé el siguiente enfoque. Este parece ser el paradigma común Skip/Take que muchas personas utilizan, con la característica adicional de ahorro de memoria.

Tenga en cuenta que _conn se crea de antemano, y se crea un contexto temporal de datos para cada consulta, lo que da como resultado que las entidades asociadas sean basura recolectada.

int skipAmount = 0; 
bool finished = false; 

while (!finished) 
{ 
    // Trick to allow for automatic garbage collection while iterating through the DB 
    using (var tempDataContext = new MyDataContext(_conn) {CommandTimeout = 600}) 
    {    
     var query = 
      (from record in tempDataContext.someTable 
      where [index is appropriate] 
      select record); 

     List<workerLog> logs = query.Skip(skipAmount).Take(BatchSize).ToList(); 
     if (logs.Count == 0) 
     { 
      finished = true; 
      continue; 
     } 

     foreach(linqEntity l in logs) 
     { 
      // Do stuff with data from l 
     } 

     skipAmount += logs.Count; 
    } 
} 

ahora tengo el comportamiento deseado que el uso de memoria no aumenta en absoluto, ya que estoy streaming a través de los datos. Sin embargo, tengo un problema mucho peor: cada Skip está causando los datos que se cargan más y más lentamente a medida que la consulta subyacente parece ser la causa realmente el servidor para pasar por todos los datos de todas las páginas anteriores. Al ejecutar la consulta, cada página tarda más y más tiempo en cargarse, y puedo decir que esto se está convirtiendo en una operación cuadrática. Este problema ha aparecido en las siguientes publicaciones:

Me parece que no puede encontrar una manera de hacer esto con LINQ que me permite tener el uso de memoria limitada por la paginación datos, y todavía tienen cada carga de página en tiempo constante. ¿Hay alguna manera de hacer esto correctamente? Mi impresión es que podría haber alguna manera de contar la DataContext olvidar explícitamente sobre el objeto en el primer enfoque anterior, pero no puedo encontrar la manera de hacer eso.

+0

"Tengo una tabla enorme que necesito leer en cierto orden y calcular algunas estadísticas agregadas." - Hágalo en el servidor en TSQL ... ¡Eso es lo que es bueno! –

+0

No, las estadísticas son más complicadas que eso, y no son computables con consultas SQL. Los datos deben repetirse en un orden determinado y las cosas calculadas que son temporalmente correctas, etc. –

+0

"No, las estadísticas son más complicadas que eso, y no son computables con consultas SQL" - ¿en serio? ¿Es posible dar un ejemplo completo? –

Respuesta

15

Después locamente se aferra a algunas pajas, he encontrado que la DataContext 's ObjectTrackingEnabled = false podría ser justo lo que recetó el doctor. No es sorprendente que esté específicamente diseñado para un caso de solo lectura como este.

using (var readOnlyDataContext = 
    new MyDataContext(_conn) {CommandTimeout = really_long, ObjectTrackingEnabled = false}) 
{             
    var logs = 
     (from record in readOnlyDataContext.someTable 
     where [index is appropriate] 
     select record); 

    foreach(linqEntity l in logs) 
    { 
     // Do stuff with data from l 
    }     
} 

El enfoque anterior no utiliza ninguna memoria cuando se transmite a través de objetos. Al escribir datos, puedo usar un DataContext diferente que tiene habilitado el seguimiento de objetos, y parece funcionar bien. Sin embargo, este enfoque tiene el problema de una consulta SQL que puede tardar una hora o más en transmitirse y completarse, por lo que si hay una forma de hacer la búsqueda como la anterior sin el impacto en el rendimiento, estoy abierto a otras alternativas.

Una advertencia sobre girando seguimiento de objetos fuera: He encontrado que cuando se intenta hacer varias lecturas simultáneas con el mismo DataContext, no obtiene el error There is already an open DataReader associated with this Command which must be closed first. La aplicación simplemente entra en un bucle infinito con el 100% Uso de CPU. No estoy seguro de si esto es un error de C# o una característica.

+0

¡Publicación muy útil! +2 – craastad

+0

Tenía exactamente el mismo problema, leyendo en filas de 1 m y quedándome sin memoria. Con "ObjectTrackingEnabled" establecido en falso, la recolección de basura * no * liberaba la memoria que esta función había utilizado. Sin él, no se lanzaron aproximadamente 300Mb de memoria. Entonces este pequeño escenario puede tener un gran impacto. –