SQLAlchemy: Apa perbedaan antara flush () dan commit ()?

422

Apa perbedaan antara flush()dan commit()dalam SQLAlchemy?

Saya sudah membaca dokumen, tetapi tidak ada yang lebih bijak - mereka tampaknya menganggap pra-pemahaman yang tidak saya miliki.

Saya sangat tertarik pada dampaknya pada penggunaan memori. Saya memuat beberapa data ke dalam basis data dari serangkaian file (total sekitar 5 juta baris) dan sesi saya terkadang gagal - ini adalah basis data besar dan mesin dengan tidak banyak memori.

Saya bertanya-tanya apakah saya menggunakan terlalu banyak commit()dan tidak cukup flush()panggilan - tetapi tanpa benar-benar memahami apa perbedaannya, sulit untuk mengatakannya!

AP257
sumber

Jawaban:

534

Objek Sesi pada dasarnya adalah transaksi perubahan yang sedang berlangsung untuk suatu basis data (pembaruan, masukkan, hapus). Operasi-operasi ini tidak bertahan hingga basis data sampai mereka berkomitmen (jika program Anda dibatalkan karena suatu alasan dalam transaksi pertengahan sesi, setiap perubahan yang tidak dikomit di dalam hilang).

Objek sesi mendaftar operasi transaksi dengan session.add(), tetapi belum mengkomunikasikannya ke database sampai session.flush()dipanggil.

session.flush()mengkomunikasikan serangkaian operasi ke database (masukkan, perbarui, hapus). Basis data mempertahankannya sebagai operasi yang tertunda dalam suatu transaksi. Perubahan tidak bertahan secara permanen ke disk, atau terlihat oleh transaksi lain sampai database menerima KOMIT untuk transaksi saat ini (yang merupakan apa session.commit()).

session.commit() melakukan (tetap) perubahan tersebut ke database.

flush()adalah selalu disebut sebagai bagian dari panggilan untuk commit()( 1 ).

Saat Anda menggunakan objek Sesi untuk kueri basis data, kueri akan mengembalikan hasil baik dari basis data dan dari bagian memerah dari transaksi tidak mengikat yang dimilikinya. Secara default, Sesi menolak autoflushoperasi mereka, tetapi ini dapat dinonaktifkan.

Semoga contoh ini akan membuat ini lebih jelas:

#---
s = Session()

s.add(Foo('A')) # The Foo('A') object has been added to the session.
                # It has not been committed to the database yet,
                #   but is returned as part of a query.
print 1, s.query(Foo).all()
s.commit()

#---
s2 = Session()
s2.autoflush = False

s2.add(Foo('B'))
print 2, s2.query(Foo).all() # The Foo('B') object is *not* returned
                             #   as part of this query because it hasn't
                             #   been flushed yet.
s2.flush()                   # Now, Foo('B') is in the same state as
                             #   Foo('A') was above.
print 3, s2.query(Foo).all() 
s2.rollback()                # Foo('B') has not been committed, and rolling
                             #   back the session's transaction removes it
                             #   from the session.
print 4, s2.query(Foo).all()

#---
Output:
1 [<Foo('A')>]
2 [<Foo('A')>]
3 [<Foo('A')>, <Foo('B')>]
4 [<Foo('A')>]
snapshoe
sumber
Hanya satu hal lagi: apakah Anda tahu apakah memanggil commit () meningkatkan memori yang digunakan, atau menurunkannya?
AP257
2
Ini juga salah untuk mesin db yang tidak mendukung transaksi seperti myisam. Karena tidak ada transaksi yang sedang berlangsung, flush bahkan lebih sedikit untuk membedakan dirinya dari komit.
underrun
1
@underrun Jadi jika saya lakukan session.query() setelah session.flush(), akankah saya melihat perubahan saya? Mengingat saya menggunakan MyISAM.
Api Beku
1
Apakah itu gaya yang baik atau buruk untuk digunakan flush()dan commit(), atau haruskah saya serahkan pada Alkimia. Saya menggunakan flush()dalam beberapa kasus karena permintaan berikutnya diperlukan untuk mengambil data baru.
Jens
1
@Jens Use autoflush( Truesecara default). Ini akan secara otomatis menyiram sebelum semua permintaan, jadi Anda tidak harus mengingat setiap waktu.
Kiran Jonnalagadda
24

Seperti yang dikatakan @snapshoe

flush() mengirimkan pernyataan SQL Anda ke database

commit() melakukan transaksi.

Kapan session.autocommit == False :

commit()akan memanggil flush()jika Anda mengaturautoflush == True .

Kapan session.autocommit == True :

Kamu tidak bisa menelepon commit() jika Anda belum melakukan transaksi (yang mungkin belum Anda lakukan karena Anda mungkin hanya akan menggunakan mode ini untuk menghindari mengelola transaksi secara manual).

Dalam mode ini, Anda harus menelepon flush()untuk menyimpan perubahan ORM Anda. Siram secara efektif juga melakukan data Anda.

Yakub
sumber
24
"commit () akan memanggil flush () jika autoflush Anda == Benar." tidak sepenuhnya benar, atau hanya menyesatkan. Commit selalu memerah, terlepas dari pengaturan autoflush.
Ilja Everilä
3
The autoflushparam mengontrol apakah SQLAlchemy pertama akan mengeluarkan flush jika ada yang tertunda menulis sebelum mengeluarkan query dan tidak ada hubungannya dengan mengendalikan siram tak terelakkan pada komit.
SuperShoot
4

Mengapa memerah jika Anda dapat melakukan?

Sebagai seseorang yang baru bekerja dengan database dan sqlalchemy, jawaban sebelumnya - yang flush()mengirimkan pernyataan SQL ke DB dan commit()tetap ada - tidak jelas bagi saya. Definisi tersebut masuk akal tetapi tidak segera jelas dari definisi mengapa Anda akan menggunakan flush alih-alih hanya melakukan.

Karena komit selalu memerah ( https://docs.sqlalchemy.org/en/13/orm/session_basics.html#committing ) suara ini sangat mirip. Saya pikir masalah besar untuk disoroti adalah bahwa flush tidak permanen dan dapat dibatalkan, sedangkan komit adalah permanen, dalam arti bahwa Anda tidak dapat meminta database untuk membatalkan komit terakhir (saya pikir)

@snapshoe menyoroti bahwa jika Anda ingin query database dan mendapatkan hasil yang menyertakan objek yang baru ditambahkan, Anda harus terlebih dahulu memerah (atau melakukan, yang akan memerah untuk Anda). Mungkin ini berguna untuk beberapa orang walaupun saya tidak yakin mengapa Anda ingin memerah daripada melakukan komitmen (selain dari jawaban sepele yang dapat dibatalkan).

Dalam contoh lain saya sedang menyinkronkan dokumen antara DB lokal dan server jauh, dan jika pengguna memutuskan untuk membatalkan, semua penambahan / pembaruan / penghapusan harus dibatalkan (yaitu tidak ada sinkronisasi parsial, hanya sinkronisasi penuh). Saat memperbarui satu dokumen, saya memutuskan untuk menghapus baris lama dan menambahkan versi yang diperbarui dari server jarak jauh. Ternyata karena cara sqlalchemy ditulis, urutan operasi saat melakukan tidak dijamin. Ini menghasilkan menambahkan versi duplikat (sebelum mencoba menghapus yang lama), yang mengakibatkan DB gagal kendala unik. Untuk menyiasati ini saya menggunakan flush()agar pesanan tetap dipertahankan, tetapi saya masih bisa membatalkan jika nanti proses sinkronisasi gagal.

Lihat posting saya tentang ini di: Apakah ada pesanan untuk tambah versus hapus ketika melakukan dalam sqlalchemy

Demikian pula, seseorang ingin tahu apakah pesanan ditambahkan dipertahankan ketika melakukan, yaitu jika saya tambahkan object1kemudian tambahkan object2, apakah object1ditambahkan ke database sebelum object2 Apakah SQLAlchemy menyimpan pesanan ketika menambahkan objek ke sesi?

Sekali lagi, di sini mungkin penggunaan flush () akan memastikan perilaku yang diinginkan. Jadi secara ringkas, satu penggunaan untuk flush adalah untuk memberikan jaminan pesanan (saya pikir), sekali lagi sambil tetap membiarkan diri Anda opsi "undo" yang komit tidak menyediakan.

Autoflush dan Autocommit

Catatan, autoflush dapat digunakan untuk memastikan kueri bertindak pada basis data yang diperbarui karena sqlalchemy akan memerah sebelum menjalankan kueri. https://docs.sqlalchemy.org/en/13/orm/session_api.html#sqlalchemy.orm.session.Session.params.autoflush

Autocommit adalah hal lain yang saya tidak sepenuhnya mengerti tetapi sepertinya penggunaannya tidak disarankan: https://docs.sqlalchemy.org/en/13/orm/session_api.html#sqlalchemy.orm.session.Session.params. komentar otomatis

Penggunaan Memori

Sekarang pertanyaan awal sebenarnya ingin tahu tentang dampak flush vs commit untuk tujuan memori. Karena kemampuan untuk bertahan atau tidak adalah sesuatu yang ditawarkan oleh basis data (saya pikir), hanya pembilasan harus cukup untuk membongkar ke basis data - meskipun melakukan itu tidak akan merugikan (sebenarnya mungkin membantu - lihat di bawah) jika Anda tidak peduli tentang kehancuran .

sqlalchemy menggunakan referensi lemah untuk objek yang telah memerah: https://docs.sqlalchemy.org/en/13/orm/session_state_management.html#session-referencing-behavior

Ini berarti jika Anda tidak memiliki objek yang secara eksplisit dipegang di suatu tempat, seperti dalam daftar atau dikt, sqlalchemy tidak akan menyimpannya di memori.

Namun, kemudian Anda memiliki sisi database yang perlu dikhawatirkan. Agaknya pembilasan tanpa komitmen disertai dengan penalti memori untuk mempertahankan transaksi. Sekali lagi, saya baru dalam hal ini, tetapi di sini ada tautan yang tampaknya menyarankan hal ini: https://stackoverflow.com/a/15305650/764365

Dengan kata lain, komit harus mengurangi penggunaan memori, meskipun mungkin ada trade-off antara memori dan kinerja di sini. Dengan kata lain, Anda mungkin tidak ingin melakukan perubahan database tunggal, satu per satu (karena alasan kinerja), tetapi menunggu terlalu lama akan meningkatkan penggunaan memori.

Jimbo
sumber
1

Ini tidak sepenuhnya menjawab pertanyaan asli tetapi beberapa orang telah menyebutkan bahwa dengan session.autoflush = TrueAnda tidak harus menggunakan session.flush()... Dan ini tidak selalu benar.

Jika Anda ingin menggunakan id objek yang baru dibuat di tengah-tengah transaksi , Anda harus menelepon session.flush().

# Given a model with at least this id
class AModel(Base):
   id = Column(Integer, primary_key=True)  # autoincrement by default on integer primary key

session.autoflush = True

a = AModel()
session.add(a)
a.id  # None
session.flush()
a.id  # autoincremented integer

Hal ini karena autoflushtidak tidak otomatis mengisi id (meskipun permintaan dari objek akan, yang kadang-kadang dapat menyebabkan kebingungan seperti dalam "mengapa karya ini di sini tapi tidak ada?" Tapi snapshoe sudah tertutup bagian ini).


Satu aspek terkait yang tampaknya cukup penting bagi saya dan tidak benar-benar disebutkan:

Mengapa Anda tidak melakukan semua waktu? - Jawabannya adalah atomicity .

Sebuah kata mewah untuk diucapkan: sebuah ensemble operasi harus semuanya dijalankan dengan sukses ATAU tidak ada dari mereka yang akan berlaku.

Misalnya, jika Anda ingin membuat / memperbarui / menghapus beberapa objek (A) dan kemudian membuat / memperbarui / menghapus yang lain (B), tetapi jika (B) gagal Anda ingin kembali (A). Ini berarti kedua operasi itu adalah atom .

Karenanya, jika (B) membutuhkan hasil dari (A), Anda ingin memanggil flushafter (A) dan commitafter (B).

Juga, jika session.autoflush is True, kecuali untuk kasus yang saya sebutkan di atas atau yang lain dalam jawaban Jimbo , Anda tidak perlu menelepon flushsecara manual.

Romain Vincent
sumber