2009-11-10 14 views
6

Cada vez que escribo un programa del siguiente formulario usando LINQ to SQL, termino con un programa que solo agarra más y más memoria mientras corre y cae en un montón que consume 2GB después de quizás como poco como 25,000 registros. Siempre termino reescribiéndolo usando ADO.NET. ¿Qué estoy haciendo mal?Procesar grandes conjuntos de datos usando LINQ

Aclaración: Esta pregunta no trata sobre la velocidad de procesamiento; las respuestas sobre hacer que avance más rápido no son relevantes.

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

Creo que la respuesta a "¿Qué estoy haciendo mal?" es "Hacer lo mismo y esperar una respuesta diferente". Esto también es un signo de locura. Solo por patadas prueba algo más. Por ejemplo, escriba su código de acceso sql en ADO.Net FIRST y simplemente saltee toda esa mierda de linq. – NotMe

+4

Pero LINQ no es excremento, como usted sugiere. Es la tecnología más elegante y persiste con eso es mi testimonio de eso, en lugar de un signo de locura. – Nestor

Respuesta

6

Está presionando el contexto de datos para generar la consulta desde cero cada vez.

Intente utilizar una consulta compilada en su lugar.

+0

Pasar a las consultas compiladas ha marcado la diferencia, reduciendo el consumo de memoria en al menos un 95%. Supongo que continuamente recompilar las consultas es una mala idea de todos modos, pero aún me queda curiosidad por el uso de tanta memoria. Para el beneficio de los demás, aquí está una guía de introducción a consultas compiladas: http://linqinaction.net/blogs/jwooley/archive/2007/09/04/linq-to-sql-compiled-queries. aspx – Nestor

+0

Las consultas compiladas no son reemplazos directos para las no compiladas, debe cambiar el código un poco. Este artículo me ayudó a entender por qué eso es: http://linqinaction.net/blogs/jwooley/archive/2007/09/04/linq-to-sql-compiled-queries.aspx – Nestor

+1

7 años después, y esto demostró ser un regalo del cielo para solucionar un problema de mal rendimiento en una aplicación heredada. Muchas gracias. –

0

podría intentar poner la creación de la DataContext fuera del bucle, no está seguro de la cantidad de memoria que le ahorraría sin embargo.

Quizás pueda llamar a GC.Collect() después de cada ciclo, vea si puede causar la recolección de basura manualmente.

+1

Creo que si algo empeora las cosas, ya que el contexto de datos vive lo suficiente como para acumular miles de registros en caché. Pero estoy claramente malentendiendo algo ya que algo se está acumulando. – Nestor

+0

@maxc - ¿Al menos ha intentado mover la llamada al contexto de datos fuera del lazo solo para ver si hace alguna diferencia? – Breadtruck

+0

tengo, sí. he estado rasgándome el pelo periódicamente en este durante un año. – Nestor

3

Va a la base de datos dos veces para cada iteración de bucle, una para recuperar la fila y otra para actualizar la fila. Esto no es muy eficiente.

Debe operar en lotes:

  • Obtener un conjunto de filas en la delantera al seleccionar en un rango en lugar de un único valor, es decir, 0-100 para el primer lote, 101-200 para el siguiente lote, etc. Esto será más rápido si tiene un índice agrupado definido en la columna Código.

  • Crear el contexto de datos antes de entrar en el bucle

  • Dentro del bucle, simplemente actualizar los objetos

  • SubmitChanges de llamada() después del bucle ha terminado, esto enviará todas las actualizaciones de la base de datos en una sola conexión/transacción

  • Repita para el siguiente lote

Debe hacer que el tamaño del lote sea configurable, ya que no puede estar seguro de qué tamaño de lote generará el mejor rendimiento; no lo codifique en la aplicación.

Además, utilizaría SingleOrDefault() con null-checking en lugar de Single(), a menos que pueda garantizar que siempre habrá una fila para cualquier valor de i.

EDIT:

En términos de uso de la memoria, eso es mucho más difícil de controlar, pero no es peculiar de LINQ a SQL, cualquier algoritmo de procesamiento por lotes tiene que lidiar con esto. Si bien no recomiendo usar GC.Collect() en la práctica, generalmente es suficiente como una solución después de procesar un lote grande.

También podría considerar reducir la cantidad de datos que recupera por fila (dependiendo de cuánto sea esto para empezar). Puede crear una nueva entidad que se correlacione con un conjunto mucho más pequeño de columnas de la misma tabla, potencialmente solo una o dos, de modo que cuando seleccione esa entidad solo estará recuperando las columnas con las que pretende trabajar. Esto mejoraría tanto la velocidad como la huella de memoria a medida que menos datos viajan por el cable y los objetos son mucho más pequeños.

+0

Claro, todas son buenas optimizaciones de velocidad pero la optimización debería venir después de que el programa se haya hecho funcionar. Por el momento, toda la memoria disponible se consume rápidamente, por lo que no puede continuar. – Nestor

+1

+1 para "Llamar SubmitChanges() DESPUÉS de que el bucle haya terminado" – Breadtruck

+0

Y al llamar a SubmitChanges() una vez que el bucle haya finalizado, ¿qué hará exactamente para la utilización de la memoria? No estamos hablando de velocidad aquí. – Nestor

1

No he podido replicar el problema. El uso de la memoria era plano. Rendimiento lento, pero memoria constante.

¿Estás seguro de que no estás goteando en otro lado? ¿Puedes producir una muestra mínima de código que reproduzca el problema?

Editar:

que utiliza el prácticamente el mismo código de ejemplo:

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(); 
     } 
    } 
} 

sin problema.

Así las cosas para descartar:

  • Framework versión. Estoy en la última.
  • DataContext/entity complexity. Mi tabla de prueba es solo dos campos, Id (int) y Name (nvarchar (max)).

¿Se puede reproducir el problema con el último FW, con una pequeña muestra DataContext?

+0

Acepto que creo que necesitaríamos ver más de su código para diagnosticar el problema. También utilicé LTS en grandes bucles gigantes como este y aún no me he encontrado con este problema de "fuga de memoria" que mencionas ... – Funka

+0

Aquí tienes una muestra completa, aunque por supuesto no he podido incluir el contexto de datos en sí. En mi máquina, consume más de 1MB por segundo y está usando 720MB mientras escribo esto. for (int run = 0; ejecutar <1,000; run ++) for (int i = 0; i <50000; i ++) { usando (MyDC myDC = new MYDC()) registro { tblRecords = (de r en myDC.tblRecords donde r.Code == yo selecciono r) .SingleOrDefault(); } } – Nestor

Cuestiones relacionadas