Omawiana tabela zawiera około dziesięciu milionów wierszy.
for event in Event.objects.all():
print event
Powoduje to, że użycie pamięci stale rośnie do około 4 GB, w którym to momencie wiersze są drukowane szybko. Zaskoczyło mnie długie opóźnienie przed wydrukowaniem pierwszego wiersza - spodziewałem się, że wydrukuje się niemal natychmiast.
Próbowałem też, Event.objects.iterator()
który zachowywał się w ten sam sposób.
Nie rozumiem, co Django ładuje do pamięci ani dlaczego to robi. Spodziewałem się, że Django przejrzy wyniki na poziomie bazy danych, co oznaczałoby, że wyniki będą drukowane z mniej więcej stałą szybkością (a nie wszystkie naraz po długim oczekiwaniu).
Co ja źle zrozumiałem?
(Nie wiem, czy jest to istotne, ale używam PostgreSQL).
sql
django
postgresql
django-orm
davidchambers
źródło
źródło
Odpowiedzi:
Nate C był blisko, ale niezupełnie.
Z dokumentów :
Tak więc dziesięć milionów wierszy jest pobieranych naraz, kiedy po raz pierwszy wchodzisz do tej pętli i uzyskujesz iteracyjną postać zestawu zapytań. Poczekanie, którego doświadczasz, to ładowanie przez Django wierszy bazy danych i tworzenie obiektów dla każdego z nich, przed zwróceniem czegoś, co można faktycznie iterować. Wtedy masz wszystko w pamięci, a wyniki się rozlewają.
Z mojego czytania dokumentacji
iterator()
wynika , że nie robi nic poza obejściem wewnętrznych mechanizmów buforowania QuerySet. Myślę, że może to mieć sens, aby zrobić jedną rzecz po drugiej, ale to z kolei wymagałoby dziesięciu milionów pojedynczych trafień w twojej bazie danych. Może nie wszystko to pożądane.Wydajne iterowanie po dużych zbiorach danych to coś, czego wciąż nie udało nam się osiągnąć, ale jest tam kilka fragmentów, które mogą się przydać:
źródło
Może nie być szybszym lub najbardziej wydajnym, ale jako gotowe rozwiązanie, dlaczego nie skorzystać z obiektów Paginator i Page w django core, udokumentowanych tutaj:
https://docs.djangoproject.com/en/dev/topics/pagination/
Coś takiego:
źródło
Paginator
ma terazpage_range
właściwość pozwalającą uniknąć schematu. Jeśli szukasz minimalnego narzutu pamięci, możesz użyć,object_list.iterator()
który nie wypełni pamięci podręcznej zestawu zapytań .prefetch_related_objects
jest wtedy wymagany do pobrania wstępnegoDomyślnym zachowaniem Django jest buforowanie całego wyniku QuerySet podczas oceny zapytania. Aby uniknąć tego buforowania, możesz użyć metody iteratora QuerySet:
https://docs.djangoproject.com/en/dev/ref/models/querysets/#iterator
Metoda iterator () ocenia zestaw zapytań, a następnie bezpośrednio odczytuje wyniki bez wykonywania buforowania na poziomie QuerySet. Ta metoda zapewnia lepszą wydajność i znaczne zmniejszenie ilości pamięci podczas iteracji na dużej liczbie obiektów, do których wystarczy uzyskać dostęp tylko raz. Zwróć uwagę, że buforowanie jest nadal wykonywane na poziomie bazy danych.
Korzystanie z iteratora () zmniejsza zużycie pamięci, ale nadal jest wyższe niż się spodziewałem. Korzystanie z metody paginatora sugerowanej przez mpaf zużywa znacznie mniej pamięci, ale w przypadku mojego przypadku testowego jest 2-3 razy wolniejsze.
źródło
To pochodzi z dokumentacji: http://docs.djangoproject.com/en/dev/ref/models/querysets/
Po
print event
uruchomieniu zapytania zostaje uruchomione zapytanie (co oznacza pełne skanowanie tabeli zgodnie z poleceniem) i wczytuje wyniki. Prosisz o wszystkie przedmioty i nie ma sposobu, aby zdobyć pierwszy przedmiot bez zdobycia ich wszystkich.Ale jeśli zrobisz coś takiego:
http://docs.djangoproject.com/en/dev/topics/db/queries/#limiting-querysets
Następnie wewnętrznie doda przesunięcia i ograniczenia do sql.
źródło
W przypadku dużej liczby rekordów kursor bazy danych działa jeszcze lepiej. Potrzebujesz surowego SQL w Django, kursor Django jest czymś innym niż kursor SQL.
Metoda LIMIT - OFFSET zasugerowana przez Nate'a C. może być wystarczająco dobra w twojej sytuacji. W przypadku dużych ilości danych jest wolniejszy niż kursor, ponieważ musi w kółko uruchamiać to samo zapytanie i przeskakiwać coraz więcej wyników.
źródło
Django nie ma dobrego rozwiązania do pobierania dużych elementów z bazy danych.
Value_list może służyć do pobierania wszystkich identyfikatorów z baz danych, a następnie pobierania każdego obiektu oddzielnie. Z biegiem czasu w pamięci będą tworzone duże obiekty, które nie będą zbierane do czasu zakończenia pętli for. Powyższy kod wykonuje ręczne czyszczenie pamięci po zużyciu każdej setnej pozycji.
źródło
Ponieważ w ten sposób obiekty dla całego zestawu zapytań są ładowane do pamięci jednocześnie. Musisz podzielić swój zestaw zapytań na mniejsze, strawne kawałki. Sposób na to nazywa się karmieniem łyżką. Oto krótka realizacja.
Aby tego użyć, napiszesz funkcję, która wykonuje operacje na twoim obiekcie:
a następnie uruchom tę funkcję na swoim zestawie zapytań:
Można to dodatkowo ulepszyć dzięki wieloprocesorowemu wykonywaniu
func
na wielu obiektach równolegle.źródło
Oto rozwiązanie zawierające len i count:
Stosowanie:
źródło
Zwykle używam surowego zapytania MySQL zamiast Django ORM do tego rodzaju zadań.
MySQL obsługuje tryb przesyłania strumieniowego, dzięki czemu możemy bezpiecznie i szybko przechodzić przez wszystkie rekordy bez błędu braku pamięci.
Odniesienie:
źródło
queryset.query
do wykonania.