2012-09-17 10 views
5

Mam ogromny stół, który muszę przeczytać w określonej kolejności i obliczyć niektóre zbiorcze statystyki. Tabela ma już indeks klastrowy dla prawidłowej kolejności, więc sama rejestracja jest dość szybka. Próbuję użyć LINQ do SQL, aby uprościć kod, który muszę napisać. Problem polega na tym, że nie chcę ładować wszystkich obiektów do pamięci, ponieważ wydaje się, że DataContext utrzymuje je w pobliżu - ale próba ich przesłania skutkuje okropnymi problemami z wydajnością.Przeczytaj ogromną tabelę z LINQ do SQL: Wyczerpanie pamięci vs powolne stronicowanie

Oto podział. Oryginalna próba była to:

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

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

Jest to dość szybko, i strumienie w dobrym tempie, ale problemem jest to, że użycie pamięci aplikacji ciągle dzieje się nigdy nie zatrzyma. Domyślam się, że obiekty LINQ do SQL są przechowywane w pamięci i nie są prawidłowo usuwane. Po przeczytaniu Out of memory when creating a lot of objects C# spróbowałem następującego podejścia. Wydaje się, że jest to powszechny paradygmat używany przez wiele osób, z dodatkową funkcją oszczędzania pamięci.

Należy pamiętać, że wcześniej utworzono _conn, a dla każdego zapytania tworzony jest kontekst danych tymczasowych, co powoduje, że skojarzone elementy są usuwane ze śmieci.

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

Teraz mam pożądane zachowanie, że użycie pamięci nie zwiększa się w ogóle, ponieważ przesyłam dane strumieniowo. Jednak mam o wiele gorszy problem: każdy proces Skip powoduje, że dane ładują się coraz wolniej, ponieważ podstawowe zapytanie wydaje się w rzeczywistości spowodować, że serwer przejdzie przez wszystkie dane dla wszystkich poprzednich stron. Podczas uruchamiania zapytania każda strona ładuje się dłużej i dłużej, i mogę powiedzieć, że zamienia się ona w operację kwadratową. Problem ten pojawił się w następujących stanowisk:

I nie wydają się znaleźć sposób, aby to zrobić z LINQ, która pozwala mi mieć ograniczone zastosowanie pamięci przez stronicowania danych, a mimo to każda strona ładuje się w stałym czasie. Czy istnieje sposób, aby to zrobić właściwie? Moje przeczucie polega na tym, że może istnieć jakiś sposób, aby powiedzieć DataContext, aby wyraźnie zapomniał o obiekcie w pierwszym podejściu powyżej, ale nie mogę się dowiedzieć, jak to zrobić.

+0

"Mam ogromny stół, który muszę przeczytać w określonej kolejności i obliczyć niektóre statystyki zbiorcze." - Zrób to na serwerze w TSQL .... To jest to, co jest dobre w! –

+0

Nie, statystyki są bardziej skomplikowane i nie można ich obliczać za pomocą zapytań SQL. Dane muszą być iterowane za pomocą w określonej kolejności i obliczone rzeczy, które są tymczasowo poprawne, itp. –

+0

"Nie, statystyki są bardziej skomplikowane i nie są obliczalne z zapytaniami SQL" - naprawdę? Czy da się podać pełny przykład? –

Odpowiedz

15

Po szaleńczym chwytaniu się niektórych słomek, stwierdziłem, że DataContext 's ObjectTrackingEnabled = false może być dokładnie tym, co zalecił lekarz. Jest to, co nie powinno dziwić, przeznaczone specjalnie do tego typu przypadku.

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 
    }     
} 

Powyższe podejście nie wykorzystuje pamięci podczas przesyłania strumieniowego przez obiekty. Podczas zapisywania danych mogę użyć innego DataContext, który ma włączone śledzenie obiektów i wydaje się działać poprawnie. Jednak to podejście ma problem z zapytaniem SQL, które może trwać godzinę lub dłużej, aby przesyłać strumieniowo i kompletować, więc jeśli istnieje sposób na wykonanie stronicowania, jak powyżej, bez wydajności, jestem otwarty na inne alternatywy.

ostrzeżenie o włączeniu śledzenia obiektu off: okazało się, że podczas próby zrobienia wielu jednoczesnych czyta o tej samej DataContext, nie pojawia się błąd There is already an open DataReader associated with this Command which must be closed first. Aplikacja po prostu idzie do nieskończonej pętli przy 100% Użycie procesora. Nie jestem pewien, czy jest to błąd C#, czy funkcja.

+0

Bardzo przydatny post! +2 – craastad

+0

Miałem dokładnie ten sam problem, czytanie w 1m wierszy i brakowało pamięci. Po ustawieniu "ObjectTrackingEnabled" na wartość "fałszowanie" * zwolniło pamięć, której ta funkcja użyła. Bez tego około 300Mb pamięci nie było zwolnione. To małe ustawienie może mieć ogromny wpływ. –