Apakah ada cara agar SQLAlchemy melakukan penyisipan massal daripada memasukkan setiap objek individual. yaitu,
perbuatan:
INSERT INTO `foo` (`bar`) VALUES (1), (2), (3)
daripada:
INSERT INTO `foo` (`bar`) VALUES (1)
INSERT INTO `foo` (`bar`) VALUES (2)
INSERT INTO `foo` (`bar`) VALUES (3)
Saya baru saja mengonversi beberapa kode untuk menggunakan sqlalchemy daripada sql mentah dan meskipun sekarang jauh lebih baik untuk bekerja dengannya tampaknya lebih lambat sekarang (hingga faktor 10), saya bertanya-tanya apakah ini alasannya.
Mungkin saya bisa memperbaiki situasi menggunakan sesi dengan lebih efisien. Saat ini saya memiliki autoCommit=False
dan melakukan session.commit()
setelah saya menambahkan beberapa hal. Meskipun hal ini tampaknya menyebabkan data menjadi basi jika DB diubah di tempat lain, seperti meskipun saya melakukan kueri baru, saya masih mendapatkan hasil lama kembali?
Terima kasih atas bantuan Anda!
Jawaban:
SQLAlchemy memperkenalkan itu dalam versi
1.0.0
:Operasi massal - dokumen SQLAlchemy
Dengan operasi ini, Anda sekarang dapat melakukan penyisipan atau pembaruan massal!
Misalnya, Anda dapat melakukan:
Di sini, penyisipan massal akan dibuat.
sumber
\copy
dengan psql (dari klien yang sama ke server yang sama), saya melihat perbedaan besar dalam kinerja di sisi server yang menghasilkan sekitar 10x lebih banyak sisipan / s. Rupanya pemuatan massal menggunakan\copy
(atauCOPY
di server) menggunakan pengepakan dalam berkomunikasi dari klien-ke-server jauh lebih baik daripada menggunakan SQL melalui SQLAlchemy. Info lebih lanjut: bulk besar insert perbedaan kinerja PostgreSQL vs ... .Dokumentasi SQLAlchemy memiliki Langgan pada kinerja berbagai teknik yang dapat digunakan untuk menyisipkan massal:
sumber
Sejauh yang saya tahu, tidak ada cara agar ORM mengeluarkan sisipan massal. Saya percaya alasan yang mendasarinya adalah bahwa SQLAlchemy perlu melacak identitas setiap objek (yaitu, kunci primer baru), dan sisipan massal mengganggu itu. Misalnya, dengan asumsi
foo
tabel Anda berisiid
kolom dan dipetakan keFoo
kelas:Karena SQLAlchemy mengambil nilainya
x.id
tanpa mengeluarkan kueri lain, kita dapat menyimpulkan bahwa SQLAlchemy mendapatkan nilainya langsung dariINSERT
pernyataan. Jika Anda tidak memerlukan akses berikutnya ke objek yang dibuat melalui instance yang sama , Anda dapat melewati lapisan ORM untuk penyisipan Anda:SQLAlchemy tidak dapat mencocokkan baris baru ini dengan objek yang sudah ada, jadi Anda harus menanyakannya lagi untuk operasi selanjutnya.
Sejauh menyangkut data usang, perlu diingat bahwa sesi tidak memiliki cara bawaan untuk mengetahui kapan database diubah di luar sesi. Untuk mengakses data yang diubah secara eksternal melalui instance yang ada, instance tersebut harus ditandai sebagai kedaluwarsa . Ini terjadi secara default aktif
session.commit()
, tetapi dapat dilakukan secara manual dengan meneleponsession.expire_all()
atausession.expire(instance)
. Contoh (SQL dihilangkan):session.commit()
kedaluwarsax
, jadi pernyataan cetak pertama secara implisit membuka transaksi baru danx
atribut kueri ulang . Jika Anda mengomentari pernyataan cetak pertama, Anda akan melihat bahwa yang kedua sekarang mengambil nilai yang benar, karena kueri baru tidak dikeluarkan hingga setelah pembaruan.Ini masuk akal dari sudut pandang isolasi transaksional - Anda hanya boleh mengambil modifikasi eksternal antar transaksi. Jika hal ini menyebabkan masalah bagi Anda, saya sarankan untuk mengklarifikasi atau memikirkan ulang batasan transaksi aplikasi Anda daripada segera meraihnya
session.expire_all()
.sumber
autocommit=False
, saya yakin Anda harus meneleponsession.commit()
setelah permintaan selesai (saya tidak terbiasa dengan TurboGears, jadi abaikan ini jika itu ditangani untuk Anda di tingkat kerangka kerja). Selain memastikan perubahan Anda telah dilakukan ke database, ini akan membuat semua yang ada di sesi tidak berlaku lagi. Transaksi berikutnya tidak akan dimulai hingga penggunaan sesi itu berikutnya, jadi permintaan di masa mendatang pada utas yang sama tidak akan melihat data yang sudah usang.session.execute(Foo.__table__.insert(), values)
Saya biasanya melakukannya dengan menggunakan
add_all
.sumber
.add
dengan memasukkan mereka ke sesi satu per satu?Add the given collection of instances to this Session.
Apakah Anda punya alasan untuk percaya itu tidak melakukan penyisipan massal?.add
setiap item satu per satu.bulk_save_objects()
, dengan aflush()
, kita bisa mendapatkan ID objek, tetapibulk_save_objects()
tidak bisa (acara denganflush()
panggilan).Dukungan langsung telah ditambahkan ke SQLAlchemy pada versi 0.8
Sesuai dokumen ,
connection.execute(table.insert().values(data))
harus melakukan trik. (Perhatikan bahwa ini tidak sama denganconnection.execute(table.insert(), data)
yang menghasilkan banyak baris yang disisipkan melalui panggilan keexecutemany
). Dalam hal apa pun selain koneksi lokal, perbedaan kinerja bisa sangat besar.sumber
SQLAlchemy memperkenalkan itu dalam versi
1.0.0
:Operasi massal - dokumen SQLAlchemy
Dengan operasi ini, Anda sekarang dapat melakukan penyisipan atau pembaruan massal!
Misalnya (jika Anda menginginkan overhead terendah untuk INSERT tabel sederhana), Anda dapat menggunakan
Session.bulk_insert_mappings()
:Atau, jika Anda mau, lewati
loadme
tupel dan tulis kamus langsung kedicts
dalamnya (tetapi saya merasa lebih mudah untuk meninggalkan semua kata-kata dari data dan memuat daftar kamus dalam satu lingkaran).sumber
Jawaban Piere benar tetapi satu masalah adalah bahwa
bulk_save_objects
secara default tidak mengembalikan kunci utama objek, jika itu menjadi perhatian Anda. Aturreturn_defaults
untukTrue
mendapatkan perilaku ini.Dokumentasinya ada di sini .
sumber
Semua Jalan Menuju Roma , tetapi beberapa di antaranya melintasi pegunungan, membutuhkan kapal feri tetapi jika ingin cepat ke sana, gunakan saja jalan raya.
Dalam hal ini jalan tol akan menggunakan fitur execute_batch () dari psycopg2 . Dokumentasi mengatakan yang terbaik:
Implementasi saat
executemany()
ini (menggunakan pernyataan yang sangat amal) tidak terlalu berhasil. Fungsi ini dapat digunakan untuk mempercepat pengulangan eksekusi pernyataan terhadap sekumpulan parameter. Dengan mengurangi jumlah perjalanan pulang pergi server, kinerja dapat menjadi lipat lebih baik daripada menggunakanexecutemany()
.Dalam pengujian saya sendiri
execute_batch()
adalah sekitar dua kali lebih cepat sebagaiexecutemany()
, dan memberikan pilihan untuk mengkonfigurasi page_size untuk tweaker lebih lanjut (jika Anda ingin memeras terakhir 2-3% dari kinerja dari pengemudi).Fitur yang sama dapat dengan mudah diaktifkan jika Anda menggunakan SQLAlchemy dengan menyetel
use_batch_mode=True
sebagai parameter saat Anda membuat instance mesin dengancreate_engine()
sumber
execute_values
adalah lebih cepat daripada psycopg2 iniexecute_batch
ketika melakukan menyisipkan massal!Ini caranya:
Ini akan menyisipkan seperti ini:
Referensi: FAQ SQLAlchemy menyertakan tolok ukur untuk berbagai metode komit.
sumber
Jawaban terbaik yang saya temukan sejauh ini adalah di dokumentasi sqlalchemy:
http://docs.sqlalchemy.org/en/latest/faq/performance.html#im-inserting-400-000-rows-with-the-orm-and-it-s-really-slow
Ada contoh lengkap dari tolok ukur solusi yang mungkin.
Seperti yang ditunjukkan dalam dokumentasi:
bulk_save_objects bukanlah solusi terbaik tetapi kinerjanya benar.
Implementasi terbaik kedua dalam hal keterbacaan yang saya pikir adalah dengan SQLAlchemy Core:
Konteks fungsi ini diberikan dalam artikel dokumentasi.
sumber