2009-11-10 16 views
6

Za każdym razem, gdy piszę program w poniższym formularzu, używając LINQ do SQL, otrzymuję program, który po prostu pobiera coraz więcej pamięci podczas działania i przewraca się w stertach zużywających 2 GB po zaledwie 25 000 rekordów. Zawsze kończę przepisywanie go za pomocą ADO.NET. Co ja robię źle?Przetwarzanie dużych zestawów danych przy użyciu LINQ

Wyjaśnienie: To pytanie nie dotyczy szybkości przetwarzania; odpowiedzi na temat tego, jak przyspieszyć działanie, nie mają żadnego znaczenia.

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

Myślę, że odpowiedź na pytanie "Co robię źle?" "Robi to samo i oczekuje innej reakcji." Jest to także oznaką szaleństwa. Tylko dla kopnięć wypróbuj coś innego. Na przykład napisz swój kod dostępu sql w ADO.Net FIRST i po prostu pomiń wszystkie te bzdury linq. – NotMe

+4

Ale LINQ nie jest ekskrementem, jak sugerujesz. Jest to najbardziej elegancka technologia i jej przetrwanie jest moim świadectwem, a nie oznaką szaleństwa. – Nestor

Odpowiedz

6

Naciskasz kontekst danych, aby generować zapytanie od początku za każdym razem.

Spróbuj użyć zamiast tego skompilowanego zapytania.

+0

Przejście na skompilowane zapytania spowodowało ogromną różnicę, zmniejszając zużycie pamięci o co najmniej 95%. Myślę, że ciągłe rekompilowanie zapytań i tak jest złym pomysłem, ale wciąż jestem ciekawy, dlaczego zużywa tyle pamięci. Dla dobra innych osób, oto przewodnik do rozpoczęcia pracy z kompilowanymi zapytaniami: http://linqinaction.net/blogs/jwooley/archive/2007/09/04/linq-to-sql-compiled-queries. aspx – Nestor

+0

Skompilowane zapytania nie są zamiennikami dla niewspełnionych, trzeba trochę zmienić kod. Ten artykuł pomógł mi zrozumieć, dlaczego to jest: http://linqinaction.net/blogs/jwooley/archive/2007/09/04/linq-to-sql-compiled-queries.aspx – Nestor

+1

7 lat później, a to okazało się wybawieniem dla naprawienia źle działającego problemu w starszej aplikacji. Dzięki wielkie. –

0

można spróbować umieścić utworzenie datacontext poza pętli for, nie wiem, ile pamięci, które można zaoszczędzić choć.

Może można wywołać GC.Collect() po każdej pętli, sprawdź, czy możesz ręcznie wywołać funkcję czyszczenia pamięci.

+1

Myślę, że jeśli cokolwiek, co pogarsza sytuację, ponieważ kontekst danych żyje wystarczająco długo, aby zbudować tysiące zapisanych w pamięci podręcznej rekordów. Ale wyraźnie coś nie rozumiem, bo coś się nie rozwija. – Nestor

+0

@maxc - Czy próbowałeś przynajmniej przenieść wywołanie danych tekstowych poza pętlę, aby sprawdzić, czy ma to jakieś znaczenie? – Breadtruck

+0

Mam, tak. od tego czasu od czasu do czasu rozdziera mi włosy na tę fryzurę. – Nestor

3

Przechodzisz do bazy danych dwukrotnie dla każdej iteracji pętli - raz, aby pobrać wiersz, a następnie ponownie, aby zaktualizować wiersz. To nie jest bardzo wydajne.

powinien działać w partiach:

  • Uzyskaj zbiór wierszy w górę przed wybierając się na szeregu zamiast pojedynczej wartości, tj 0-100 dla pierwszej partii, 101-200 następna partia i tak dalej. Będzie to najszybsze, jeśli indeks klastrowany zostanie zdefiniowany w kolumnie Kod.

  • Tworzenie kontekstu danych przed wejściem do pętli

  • Wewnątrz pętli, wystarczy zaktualizować obiektów

  • połączeń SubmitChanges() po pętli zakończył, to wysłać wszystkie aktualizacje do bazy w jednym połączenia/transakcji

  • Powtórz dla kolejnej partii

Powinieneś skonfigurować rozmiar wsadu konfigurowalny, ponieważ nie możesz być pewny, jaki rozmiar wsadu zapewnia najlepszą wydajność - nie koduj go w aplikacji.

Również użyłbym SingleOrDefault() z zerowym sprawdzaniem zamiast Single(), chyba że można zagwarantować, że zawsze będzie wiersz dla dowolnej wartości i.

EDIT:

Pod względem zużycia pamięci, to znacznie trudniej kontrolować, ale to nie jest charakterystyczne dla LINQ to SQL, każdy algorytm przetwarzania wsadowego ma do czynienia z tym. Chociaż nie polecam używania GC.Collect() w praktyce, zwykle jest to wystarczające jako obejście po przetworzeniu dużej partii.

Można również rozważyć zmniejszenie ilości danych pobieranych z wiersza (w zależności od tego, jak dużo to się zaczyna). Możesz utworzyć nowy obiekt, który odwzorowuje na znacznie mniejszy zestaw kolumn z tej samej tabeli, potencjalnie tylko jeden lub dwa, tak aby po wybraniu tego obiektu tylko pobierać kolumny, z którymi zamierzasz pracować. Poprawiłoby to zarówno prędkość, jak i ilość pamięci, ponieważ mniej danych przesuwa się po drucie, a obiekty są znacznie mniejsze.

+0

Oczywiście, wszystkie są dobrymi optymalizacjami prędkości, ale optymalizacja powinna nastąpić po uruchomieniu programu. W tej chwili cała dostępna pamięć szybko zostaje zjedzona, aby nie mogła w ogóle trwać. – Nestor

+1

+1 dla "Zadzwoń do SubmitChanges() PO zakończeniu pętli" – Breadtruck

+0

A wywołanie SubmitChanges() po zakończeniu pętli zrobi to, co dokładnie do wykorzystania pamięci? Nie mówimy tutaj o prędkości. – Nestor

1

Nie udało mi się odtworzyć problemu. Wykorzystanie pamięci było płaskie. Mała wydajność, ale stała pamięć.

Czy na pewno nie wyciekłeś gdzie indziej? Czy możesz wytworzyć minimalną próbkę kodu, która odtwarza problem?

Edit:

użyłem praktycznie ten sam kod przykładowy:

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

bez problemu.

więc rzeczy, aby wykluczyć:

  • wersję ramowego. Jestem ostatni.
  • Złożoność DataContext/encji. Moja tabela testowa to tylko dwa pola, a Id (int) i nazwa (nvarchar (max)).

Czy można odtworzyć problem za pomocą najnowszej wersji FW z małą próbką DataContext?

+0

Zgadzam się, że uważam, że powinniśmy zobaczyć więcej kodu w celu zdiagnozowania problemu. Używałem LTS również w dużych gigantycznych pętlach i jeszcze nie wpadłem na ten problem "przecieku pamięci", o którym wspomniałeś ... – Funka

+0

Oto kompletna próbka dla ciebie, chociaż oczywiście nie udało mi się dołączyć do sam kontekst danych. Na moim komputerze zużywa on więcej niż 1 MB na sekundę i używa 720 MB podczas pisania tego. do (Int prowadzony = 0; prowadzony <1000; przebieg ++) do (int i = 0; i <50000; i ++) { stosując (MyDC myDC = nowy MyDC()) { tblRecords zapis = (z r w myDC.tblRecords gdzie r.Code == i wybierz r) .SingleOrDefault(); } } – Nestor

Powiązane problemy