Saya memiliki ~ 10M record tabel MySQL yang saya antarmuka dengan menggunakan SqlAlchemy. Saya telah menemukan bahwa kueri pada subset besar dari tabel ini akan menghabiskan terlalu banyak memori meskipun saya pikir saya menggunakan generator bawaan yang secara cerdas mengambil potongan set data berukuran gigitan:
for thing in session.query(Things):
analyze(thing)
Untuk menghindari ini, saya merasa saya harus membangun iterator saya sendiri yang menggigit menjadi beberapa bagian:
lastThingID = None
while True:
things = query.filter(Thing.id < lastThingID).limit(querySize).all()
if not rows or len(rows) == 0:
break
for thing in things:
lastThingID = row.id
analyze(thing)
Apakah ini normal atau ada sesuatu yang saya lewatkan tentang generator built-in SA?
Jawaban atas pertanyaan ini tampaknya menunjukkan bahwa konsumsi memori tidak diharapkan.
python
mysql
sqlalchemy
Paul
sumber
sumber
Jawaban:
Sebagian besar implementasi DBAPI sepenuhnya buffer baris saat diambil - jadi biasanya, bahkan sebelum SQLAlchemy ORM mendapatkan satu hasil, seluruh kumpulan hasil ada di memori.
Tapi kemudian, cara
Query
kerjanya adalah itu sepenuhnya memuat hasil yang diberikan set secara default sebelum mengembalikan kepada Anda objek Anda. Alasan di sini menganggap kueri yang lebih dari sekadar pernyataan SELECT. Misalnya, dalam penggabungan ke tabel lain yang mungkin mengembalikan identitas objek yang sama beberapa kali dalam satu set hasil (umum dengan eager loading), set baris lengkap perlu berada dalam memori sehingga hasil yang benar dapat dikembalikan jika tidak koleksi dan semacamnya. mungkin hanya terisi sebagian.Jadi
Query
menawarkan opsi untuk mengubah perilaku iniyield_per()
. Panggilan ini akan menyebabkanQuery
untuk menghasilkan baris dalam kelompok, di mana Anda memberinya ukuran kelompok. Seperti yang dinyatakan oleh dokumen, ini hanya sesuai jika Anda tidak melakukan pemuatan koleksi apa pun sehingga pada dasarnya Anda benar-benar tahu apa yang Anda lakukan. Selain itu, jika baris pra-buffer DBAPI yang mendasari, masih akan ada overhead memori sehingga pendekatannya hanya berskala sedikit lebih baik daripada tidak menggunakannya.Saya hampir tidak pernah menggunakan
yield_per()
; sebaliknya, saya menggunakan versi yang lebih baik dari pendekatan LIMIT yang Anda sarankan di atas menggunakan fungsi jendela. LIMIT dan OFFSET memiliki masalah besar sehingga nilai OFFSET yang sangat besar menyebabkan kueri menjadi lebih lambat dan lebih lambat, karena OFFSET dari N menyebabkannya halaman melalui baris N - ini seperti melakukan kueri yang sama lima puluh kali daripada satu, setiap kali membaca a jumlah baris yang lebih besar dan lebih besar. Dengan pendekatan fungsi jendela, saya mengambil terlebih dahulu satu set nilai "jendela" yang mengacu pada potongan tabel yang ingin saya pilih. Saya kemudian memancarkan pernyataan SELECT individu yang masing-masing menarik dari salah satu jendela itu pada satu waktu.Pendekatan fungsi jendela ada di wiki dan saya menggunakannya dengan sangat sukses.
Perhatikan juga: tidak semua database mendukung fungsi jendela; Anda membutuhkan Postgresql, Oracle, atau SQL Server. IMHO menggunakan setidaknya Postgresql pasti sepadan - jika Anda menggunakan database relasional, Anda mungkin juga menggunakan yang terbaik.
sumber
Saya bukan ahli database, tetapi ketika menggunakan SQLAlchemy sebagai lapisan abstraksi Python sederhana (yaitu, tidak menggunakan objek ORM Query), saya telah menemukan solusi yang memuaskan untuk meminta tabel baris 300M tanpa ledakan penggunaan memori ...
Berikut adalah contoh dummy:
from sqlalchemy import create_engine, select conn = create_engine("DB URL...").connect() q = select([huge_table]) proxy = conn.execution_options(stream_results=True).execute(q)
Kemudian, saya menggunakan
fetchmany()
metode SQLAlchemy untuk mengulang hasil dalamwhile
loop tak terbatas :while 'batch not empty': # equivalent of 'while True', but clearer batch = proxy.fetchmany(100000) # 100,000 rows at a time if not batch: break for row in batch: # Do your stuff here... proxy.close()
Metode ini memungkinkan saya melakukan semua jenis agregasi data tanpa overhead memori yang berbahaya.
NOTE
yangstream_results
bekerja dengan Postgres danpyscopg2
adaptor, tapi saya kira itu tidak akan bekerja dengan DBAPI apapun, juga dengan database driver ...Ada kasus penggunaan yang menarik dalam posting blog ini yang menginspirasi metode saya di atas.
sumber
pymysql
), ini harus menjadi jawaban yang diterima IMHO.Saya telah mencari traversal / paging yang efisien dengan SQLAlchemy dan ingin memperbarui jawaban ini.
Saya rasa Anda dapat menggunakan panggilan slice untuk membatasi cakupan kueri dengan benar dan Anda dapat menggunakannya kembali secara efisien.
Contoh:
window_size = 10 # or whatever limit you like window_idx = 0 while True: start,stop = window_size*window_idx, window_size*(window_idx+1) things = query.slice(start, stop).all() if things is None: break for thing in things: analyze(thing) if len(things) < window_size: break window_idx += 1
sumber
.all()
itu perlu. Saya perhatikan kecepatannya meningkat pesat setelah panggilan pertama..all()
variabel hal-hal adalah kueri yang tidak mendukung len ()Dalam semangat jawaban Joel, saya menggunakan yang berikut ini:
WINDOW_SIZE = 1000 def qgen(query): start = 0 while True: stop = start + WINDOW_SIZE things = query.slice(start, stop).all() if len(things) == 0: break for thing in things: yield thing start += WINDOW_SIZE
sumber
Menggunakan LIMIT / OFFSET itu buruk, karena Anda perlu menemukan semua kolom {OFFSET} sebelumnya, jadi semakin besar OFFSET - semakin lama permintaan yang Anda dapatkan. Menggunakan kueri berjendela untuk saya juga memberikan hasil yang buruk pada tabel besar dengan sejumlah besar data (Anda menunggu hasil pertama terlalu lama, itu tidak baik dalam kasus saya untuk respons web yang terpotong).
Pendekatan terbaik diberikan di sini https://stackoverflow.com/a/27169302/450103 . Dalam kasus saya, saya menyelesaikan masalah hanya dengan menggunakan indeks pada bidang datetime dan mengambil kueri berikutnya dengan datetime> = before_datetime. Bodoh, karena saya menggunakan indeks itu dalam kasus yang berbeda sebelumnya, tetapi berpikir bahwa untuk mengambil semua data jendela kueri akan lebih baik. Dalam kasus saya, saya salah.
sumber
AFAIK, varian pertama masih mendapatkan semua tupel dari tabel (dengan satu kueri SQL) tetapi membangun presentasi ORM untuk setiap entitas saat melakukan iterasi. Jadi, ini lebih efisien daripada membuat daftar semua entitas sebelum melakukan iterasi, tetapi Anda masih harus mengambil semua data (mentah) ke dalam memori.
Jadi, menggunakan LIMIT pada tabel besar sepertinya ide yang bagus bagi saya.
sumber