SQLAlchemy: ความแตกต่างระหว่าง flush () และ commit () คืออะไร?


422

อะไรคือความแตกต่างระหว่างflush()และcommit()ใน SQLAlchemy?

ฉันอ่านเอกสารแล้ว แต่ไม่มีใครฉลาด - พวกเขาดูเหมือนจะเข้าใจล่วงหน้าว่าฉันไม่มี

ฉันสนใจเป็นพิเศษในผลกระทบต่อการใช้หน่วยความจำ ฉันกำลังโหลดข้อมูลบางส่วนลงในฐานข้อมูลจากชุดของไฟล์ (ประมาณ 5 ล้านแถวโดยรวม) และเซสชันของฉันล้มเหลวเป็นครั้งคราว - เป็นฐานข้อมูลขนาดใหญ่และเครื่องที่มีหน่วยความจำไม่มาก

ฉันสงสัยว่าถ้าฉันใช้มากเกินไปcommit()และไม่เพียงพอที่จะflush()โทร - แต่ถ้าไม่เข้าใจจริงๆว่าความแตกต่างคืออะไรมันยากที่จะบอก!

คำตอบ:


534

วัตถุเซสชั่นนั้นเป็นธุรกรรมอย่างต่อเนื่องของการเปลี่ยนแปลงฐานข้อมูล (ปรับปรุงแทรกลบ) การดำเนินการเหล่านี้จะไม่คงอยู่กับฐานข้อมูลจนกว่าจะมีการยืนยัน (หากโปรแกรมของคุณยกเลิกด้วยเหตุผลบางอย่างในการทำธุรกรรมช่วงกลางการเปลี่ยนแปลงใด ๆ ที่ไม่ได้รับการยอมรับภายในจะหายไป)

วัตถุเซสชันลงทะเบียนการทำธุรกรรมด้วยsession.add()แต่ยังไม่สื่อสารกับฐานข้อมูลจนกว่าsession.flush()จะถูกเรียก

session.flush()สื่อสารชุดของการดำเนินการกับฐานข้อมูล (แทรกอัพเดตลบ) ฐานข้อมูลเก็บรักษาไว้เป็นการดำเนินการที่ค้างอยู่ในธุรกรรม การเปลี่ยนแปลงจะไม่คงอยู่อย่างถาวรในดิสก์หรือสามารถเห็นได้กับธุรกรรมอื่น ๆ จนกว่าฐานข้อมูลจะได้รับ COMMIT สำหรับธุรกรรมปัจจุบัน (ซึ่งเป็นสิ่งที่session.commit()ทำ)

session.commit() กระทำการ (ยังคงมีอยู่) การเปลี่ยนแปลงเหล่านั้นในฐานข้อมูล

flush()ถูกเรียกเสมอว่าเป็นส่วนหนึ่งของการโทรไปที่commit()( 1 )

เมื่อคุณใช้วัตถุเซสชันเพื่อสอบถามฐานข้อมูลแบบสอบถามจะส่งคืนผลลัพธ์ทั้งจากฐานข้อมูลและจากส่วนที่ถูกล้างข้อมูลของธุรกรรมที่ไม่มีข้อผูกมัดที่มีอยู่ ตามค่าเริ่มต้นเซสชันจะคัดค้านautoflushการดำเนินการ แต่สามารถปิดใช้งานได้

หวังว่าตัวอย่างนี้จะทำให้ชัดเจนยิ่งขึ้น:

#---
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')>]

อีกเพียงสิ่งเดียว: คุณรู้หรือไม่ว่าการเรียกใช้ commit () เพิ่มหน่วยความจำที่ใช้หรือลดลง?
AP257

2
สิ่งนี้ยังเป็นเท็จสำหรับเอ็นจิน db ที่ไม่รองรับธุรกรรมเช่น myisam เนื่องจากไม่มีการทำธุรกรรมอย่างต่อเนื่อง flush มีน้อยกว่าที่จะแยกความแตกต่างจากการกระทำ
underrun

1
@underrun ดังนั้นถ้าฉันทำsession.query() หลังจากsession.flush()นั้นฉันจะเห็นการเปลี่ยนแปลงของฉันหรือไม่ ให้ฉันใช้ MyISAM
Flame แช่แข็ง

1
มันเป็นสไตล์ที่ดีหรือไม่ดีในการใช้flush()และcommit()หรือฉันควรปล่อยให้มันถึง Alchemy ฉันใช้flush()ในบางกรณีเนื่องจากคำสั่งต่อมาจำเป็นต้องรับข้อมูลใหม่
เจนส์

1
@Jens Use autoflush( Trueโดยค่าเริ่มต้น) มันจะล้างโดยอัตโนมัติก่อนที่จะค้นหาทั้งหมดดังนั้นคุณไม่จำเป็นต้องจำทุกครั้ง
Kiran Jonnalagadda

24

@snapshoe พูดว่า

flush() ส่งคำสั่ง SQL ของคุณไปยังฐานข้อมูล

commit() กระทำธุรกรรม

เมื่อsession.autocommit == False:

commit()จะเรียกถ้าคุณตั้งค่าflush()autoflush == True

เมื่อsession.autocommit == True:

คุณไม่สามารถโทรได้commit()หากคุณยังไม่ได้เริ่มทำธุรกรรม (ซึ่งคุณอาจไม่ได้ทำเพราะคุณอาจจะใช้โหมดนี้เพื่อหลีกเลี่ยงการจัดการธุรกรรมด้วยตนเอง)

ในโหมดนี้คุณต้องโทรflush()เพื่อบันทึกการเปลี่ยนแปลง ORM ของคุณ ฟลัชได้อย่างมีประสิทธิภาพยังกระทำข้อมูลของคุณ


24
"กระทำ () จะเรียกเปี่ยม () ถ้า autoflush ของคุณ == จริง" ไม่ถูกต้องทั้งหมดหรือเป็นเพียงการทำให้เข้าใจผิด กระทำจะวูบวาบเสมอโดยไม่คำนึงถึงการตั้งค่าการปิดอัตโนมัติ
Ilja Everilä

3
autoflushควบคุมพระรามว่า sqlalchemy แรกจะออกล้างถ้ามีเขียนอยู่ระหว่างดำเนินการก่อนที่จะออกแบบสอบถามและมีอะไรจะทำอย่างไรกับการควบคุมการล้างหลีกเลี่ยงไม่ได้ในการกระทำ
SuperShoot

4

เหตุใดจึงต้องล้างหากคุณสามารถกระทำได้

ในฐานะที่เป็นคนใหม่ในการทำงานกับฐานข้อมูลและ sqlalchemy คำตอบก่อนหน้า - ที่flush()ส่งคำสั่ง SQL ไปยังฐานข้อมูลและยืนยันcommit()พวกเขา - ไม่ชัดเจนสำหรับฉัน คำจำกัดความมีเหตุผล แต่ก็ไม่ชัดเจนในทันทีจากคำนิยามว่าทำไมคุณจึงต้องใช้ flush แทนที่จะเป็นแค่การคอมมิท

เนื่องจากคอมมิชชัน flushes เสมอ ( https://docs.sqlalchemy.org/en/13/orm/session_basics.html#committing ) เสียงเหล่านี้คล้ายกันจริงๆ ฉันคิดว่าประเด็นสำคัญที่ต้องเน้นคือการล้างข้อมูลไม่ถาวรและสามารถยกเลิกได้ในขณะที่การคอมมิทเป็นสิ่งถาวรในแง่ที่ว่าคุณไม่สามารถขอให้ฐานข้อมูลเลิกทำการคอมมิทครั้งล่าสุดได้ (ฉันคิดว่า)

@snapshoe เน้นว่าถ้าคุณต้องการสืบค้นฐานข้อมูลและรับผลลัพธ์ที่มีวัตถุที่เพิ่มเข้ามาใหม่คุณต้องล้างข้อมูลก่อน (หรือกระทำซึ่งจะล้างข้อมูลให้คุณ) บางทีนี่อาจเป็นประโยชน์สำหรับบางคนแม้ว่าฉันจะไม่แน่ใจว่าทำไมคุณถึงต้องการล้างข้อมูลมากกว่าทำ (ไม่ใช่คำตอบที่ไม่สำคัญซึ่งมันสามารถยกเลิกได้)

ในอีกตัวอย่างหนึ่งฉันกำลังซิงค์เอกสารระหว่างฐานข้อมูลท้องถิ่นและเซิร์ฟเวอร์ระยะไกลและหากผู้ใช้ตัดสินใจที่จะยกเลิกการเพิ่ม / ปรับปรุง / ลบทั้งหมดควรจะเลิกทำ (เช่นไม่มีการซิงค์บางส่วนเท่านั้นการซิงค์แบบเต็ม) เมื่ออัปเดตเอกสารเดียวฉันตัดสินใจที่จะลบแถวเก่าและเพิ่มเวอร์ชันที่อัปเดตจากเซิร์ฟเวอร์ระยะไกล ปรากฎว่าเนื่องจากวิธีการเขียน sqlalchemy คำสั่งของการดำเนินการเมื่อไม่รับประกัน สิ่งนี้ส่งผลให้มีการเพิ่มเวอร์ชันที่ซ้ำกัน (ก่อนที่จะพยายามลบเวอร์ชันเก่า) ซึ่งส่งผลให้ฐานข้อมูลล้มเหลวในข้อ จำกัด ที่ไม่ซ้ำกัน เพื่อหลีกเลี่ยงปัญหานี้ฉันใช้flush()เพื่อรักษาลำดับไว้ แต่ฉันยังสามารถยกเลิกได้ในภายหลังหากกระบวนการซิงค์ล้มเหลว

ดูโพสต์ของฉันที่นี่ : มีคำสั่งสำหรับการเพิ่มและลบเมื่อกระทำใน sqlalchemy

ในทำนองเดียวกันบางคนต้องการทราบว่าจะเพิ่มลำดับการสั่งซื้อไว้หรือไม่เช่นถ้าฉันเพิ่มobject1แล้วเพิ่มobject2จะobject1ได้รับการเพิ่มลงในฐานข้อมูลก่อนobject2 หรือไม่ SQLAlchemy บันทึกคำสั่งซื้อเมื่อเพิ่มวัตถุในเซสชันหรือไม่

อีกครั้งที่นี่น่าจะเป็นการใช้ flush () จะทำให้แน่ใจว่าพฤติกรรมที่ต้องการ ดังนั้นโดยสรุปการใช้ฟลัชหนึ่งครั้งคือการรับประกันการสั่งซื้อ (ฉันคิดว่า) อีกครั้งในขณะที่ยังให้ตัวเลือก "เลิกทำ" ที่ยอมรับไม่ได้ให้

Autoflush และ Autocommit

หมายเหตุ autoflush สามารถนำมาใช้เพื่อให้แน่ใจว่าแบบสอบถามดำเนินการกับฐานข้อมูลที่ได้รับการปรับปรุงเนื่องจาก sqlalchemy จะล้างออกก่อนที่จะดำเนินการค้นหา https://docs.sqlalchemy.org/en/13/orm/session_api.html#sqlalchemy.orm.session.Session.params.autoflush

Autocommit เป็นอย่างอื่นที่ฉันไม่เข้าใจอย่างสมบูรณ์ แต่ดูเหมือนว่าการใช้งานของมันจะหมดกำลังใจ: https://docs.sqlalchemy.org/en/13/orm/session_api.html#sqlalchemy.orm.session.Session.params autocommit

การใช้ความจำ

ตอนนี้คำถามเดิมต้องการทราบเกี่ยวกับผลกระทบของการลบออกและกระทำเพื่อวัตถุประสงค์ของหน่วยความจำ เนื่องจากความสามารถในการคงอยู่หรือไม่เป็นสิ่งที่ฐานข้อมูลนำเสนอ (ฉันคิดว่า) เพียงแค่ล้างควรจะเพียงพอที่จะ offload ไปยังฐานข้อมูล - แม้ว่าการกระทำไม่ควรเจ็บ (จริง ๆ แล้วอาจช่วย - ดูด้านล่าง) ถ้าคุณไม่สนใจ .

sqlalchemy ใช้การอ้างอิงที่อ่อนแอสำหรับวัตถุที่ถูกลบทิ้ง: https://docs.sqlalchemy.org/en/13/orm/session_state_management.html#session-referencing-behavior

ซึ่งหมายความว่าถ้าคุณไม่มีวัตถุที่จัดขึ้นอย่างชัดเจนเช่นในรายการหรือ dict sqlalchemy จะไม่เก็บไว้ในหน่วยความจำ

อย่างไรก็ตามคุณมีฐานข้อมูลด้านสิ่งที่ต้องกังวล สันนิษฐานว่าการชะล้างโดยไม่ส่งข้อมูลมาพร้อมกับการปรับหน่วยความจำเพื่อรักษาธุรกรรม อีกครั้งฉันยังใหม่กับสิ่งนี้ แต่นี่คือลิงค์ที่ดูเหมือนจะแนะนำตรงนี้: https://stackoverflow.com/a/15305650/764365

กล่าวอีกนัยหนึ่งว่าคอมมิทจะลดการใช้หน่วยความจำแม้ว่าจะมีการแลกเปลี่ยนระหว่างหน่วยความจำและประสิทธิภาพที่นี่ คุณอาจไม่ต้องการเปลี่ยนแปลงฐานข้อมูลทุกครั้งทีละครั้ง (ด้วยเหตุผลด้านประสิทธิภาพ) แต่การรอนานเกินไปจะเพิ่มการใช้หน่วยความจำ


1

นี่ไม่ได้ตอบคำถามดั้งเดิมอย่างเคร่งครัด แต่บางคนพูดว่าsession.autoflush = Trueคุณไม่จำเป็นต้องใช้session.flush()... และมันก็ไม่จริงเสมอไป

หากคุณต้องการที่จะใช้รหัสของวัตถุที่สร้างขึ้นใหม่ในช่วงกลางของการทำธุรกรรมที่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

นี้เป็นเพราะautoflushไม่ไม่อัตโนมัติกรอกรหัส (แม้ว่าแบบสอบถามของวัตถุที่จะซึ่งบางครั้งอาจทำให้เกิดความสับสนในขณะที่ "ทำไมงานนี้ได้ที่นี่ แต่ไม่ได้มี?" แต่snapshoeครอบคลุมอยู่แล้วส่วนนี้)


แง่มุมที่เกี่ยวข้องอย่างหนึ่งซึ่งดูเหมือนว่าสำคัญสำหรับฉันและไม่ได้กล่าวถึงจริง ๆ :

ทำไมคุณไม่กระทำตลอดเวลา? - คำตอบคืออะตอมมิ

คำพูดที่ไพเราะที่จะพูดว่า: การดำเนินการทั้งหมดจะต้องถูกประหารสำเร็จหรือจะไม่มีผลใด ๆ

ตัวอย่างเช่นหากคุณต้องการสร้าง / อัปเดต / ลบวัตถุบางอย่าง (A) จากนั้นสร้าง / อัปเดต / ลบอีก (B) แต่ถ้า (B) ล้มเหลวคุณต้องการเปลี่ยนกลับ (A) ซึ่งหมายความว่าผู้ที่ 2 การดำเนินงานเป็นอะตอม

ดังนั้นหาก (B) ต้องการผลลัพธ์ของ (A) คุณต้องการโทรหาflushหลังจาก (A) และcommitหลัง (B)

นอกจากนี้หากsession.autoflush is Trueยกเว้นกรณีที่ฉันกล่าวถึงข้างต้นหรือคำตอบอื่น ๆ ของJimboคุณไม่จำเป็นต้องโทรflushด้วยตนเอง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.