Tabel tersebut berisi kira-kira sepuluh juta baris.
for event in Event.objects.all():
print event
Hal ini menyebabkan penggunaan memori terus meningkat hingga 4 GB atau lebih, pada saat itu baris dicetak dengan cepat. Penundaan yang lama sebelum baris pertama dicetak mengejutkan saya - saya berharap itu dicetak hampir seketika.
Saya juga mencoba Event.objects.iterator()
yang berperilaku sama.
Saya tidak mengerti apa yang sedang dimuat Django ke dalam memori atau mengapa ia melakukan ini. Saya mengharapkan Django mengulang melalui hasil pada tingkat basis data, yang berarti hasil akan dicetak secara kasar pada tingkat yang konstan (daripada sekaligus setelah menunggu lama).
Apa yang telah saya salah paham?
(Saya tidak tahu apakah itu relevan, tetapi saya menggunakan PostgreSQL.)
sql
django
postgresql
django-orm
davidchambers.dll
sumber
sumber
Jawaban:
Nate C memang dekat, tapi tidak sepenuhnya.
Dari dokumen :
Jadi sepuluh juta baris Anda diambil, semuanya sekaligus, saat Anda pertama kali memasuki loop itu dan mendapatkan bentuk iterasi dari queryset. Penantian yang Anda alami adalah Django memuat baris basis data dan membuat objek untuk masing-masing, sebelum mengembalikan sesuatu yang sebenarnya Anda dapat mengulanginya. Kemudian Anda memiliki segalanya dalam ingatan, dan hasilnya akan keluar.
Dari saya membaca dokumen,
iterator()
tidak lebih dari melewati mekanisme cache internal QuerySet. Saya pikir mungkin masuk akal untuk melakukan satu per satu hal, tetapi sebaliknya itu akan membutuhkan sepuluh juta klik individu di database Anda. Mungkin tidak semua yang diinginkan.Mengulangi kumpulan data besar secara efisien adalah sesuatu yang masih belum kami lakukan dengan benar, tetapi ada beberapa cuplikan di luar sana yang mungkin berguna untuk tujuan Anda:
sumber
Mungkin bukan yang lebih cepat atau paling efisien, tetapi sebagai solusi siap pakai mengapa tidak menggunakan Paginator dan objek Halaman dari django core yang didokumentasikan di sini:
https://docs.djangoproject.com/en/dev/topics/pagination/
Sesuatu seperti ini:
sumber
Paginator
sekarang memilikipage_range
properti untuk menghindari boilerplate. Jika mencari overhead memori minimal, Anda dapat menggunakanobject_list.iterator()
yang tidak akan mengisi cache queryset .prefetch_related_objects
kemudian diperlukan untuk pengambilan sebelumnyaPerilaku standar Django adalah menyimpan seluruh hasil QuerySet saat mengevaluasi query. Anda bisa menggunakan metode iterator QuerySet untuk menghindari caching ini:
https://docs.djangoproject.com/en/dev/ref/models/querysets/#iterator
Metode iterator () mengevaluasi queryset dan kemudian membaca hasilnya secara langsung tanpa melakukan caching pada level QuerySet. Metode ini menghasilkan kinerja yang lebih baik dan pengurangan memori yang signifikan saat melakukan iterasi pada sejumlah besar objek yang hanya perlu Anda akses sekali. Perhatikan bahwa caching masih dilakukan di tingkat database.
Menggunakan iterator () mengurangi penggunaan memori untuk saya, tetapi masih lebih tinggi dari yang saya harapkan. Menggunakan pendekatan paginator yang disarankan oleh mpaf menggunakan lebih sedikit memori, tetapi 2-3x lebih lambat untuk kasus pengujian saya.
sumber
Ini dari dokumen: http://docs.djangoproject.com/en/dev/ref/models/querysets/
Jadi ketika
print event
dijalankan, kueri akan aktif (yang merupakan pemindaian tabel lengkap sesuai dengan perintah Anda.) Dan memuat hasilnya. Permintaan Anda untuk semua objek dan tidak ada cara untuk mendapatkan objek pertama tanpa mendapatkan semuanya.Tetapi jika Anda melakukan sesuatu seperti:
http://docs.djangoproject.com/en/dev/topics/db/queries/#limiting-querysets
Kemudian itu akan menambahkan offset dan batas ke sql secara internal.
sumber
Untuk rekaman dalam jumlah besar, kursor database bekerja lebih baik. Anda memang membutuhkan SQL mentah di Django, kursor-Django adalah sesuatu yang berbeda dari kursor SQL.
Metode LIMIT - OFFSET yang disarankan oleh Nate C mungkin cukup baik untuk situasi Anda. Untuk data dalam jumlah besar, ini lebih lambat daripada kursor karena harus menjalankan kueri yang sama berulang kali dan harus melompati lebih banyak hasil.
sumber
Django tidak mempunyai solusi yang baik untuk mengambil item besar dari database.
values_list dapat digunakan untuk mengambil semua id dalam database dan kemudian mengambil setiap objek secara terpisah. Seiring waktu, objek besar akan dibuat dalam memori dan tidak akan dikumpulkan sampah sampai loop keluar. Kode di atas melakukan pengumpulan sampah secara manual setelah setiap item ke-100 dikonsumsi.
sumber
Karena dengan cara itu objek untuk seluruh queryset dimuat di memori sekaligus. Anda perlu membagi queryset Anda menjadi bit yang lebih kecil dan mudah dicerna. Pola untuk melakukan ini disebut pemberian makan sendok. Berikut implementasi singkatnya.
Untuk menggunakan ini, Anda menulis fungsi yang melakukan operasi pada objek Anda:
dan kemudian menjalankan fungsi itu di queryset Anda:
Hal ini dapat ditingkatkan lebih lanjut dengan multiprocessing untuk mengeksekusi
func
beberapa objek secara paralel.sumber
Berikut solusi termasuk len dan hitung:
Pemakaian:
sumber
Saya biasanya menggunakan kueri mentah MySQL mentah daripada Django ORM untuk tugas semacam ini.
MySQL mendukung mode streaming sehingga kami dapat melakukan loop melalui semua catatan dengan aman dan cepat tanpa kesalahan kehabisan memori.
Ref:
sumber
queryset.query
untuk eksekusi Anda.